The Abstraction: The Process
OS가 사용자에게 제공하는 가장 기본적은 추상화(abstraction)인 process에 대해 다룰 것이다.
- 프로세스(Process)
실행중인 프로그램(running program)
- Time sharing
사용자가 여러 프로세스를 동시에 사용할 수 있도록 CPU를 할당하는 것.
한 프로세스를 실행할 때 다른 프로세스를 잠시 멈추기를 반복하는 기술 (OS가 CPU를 virtualizing 하는 방법 중 하나)
- Mechanism vs Policy
(이 둘을 명확하게 구분할 필요는 없어 보이지만, 교재에서는 종종 구분해서 언급한다.)
mechanism: 어떤 일을 어떻게 할 것인가를 결정.
context switch 등을 low-level machinery mechanism이라고 한다. (예: context switching은 time-sharing mechanism)
policy: 무엇을 할 것인가를 결정.
scheduling 등은 high-level policy라고 한다.
더 자세한 이해를 위해 프로세스의 상태를 나타내는 정보인 Machine state에 대해 다룰 것이다.
프로세스는 address space라는 가상 공간인 memory에 올라간다.
register는 프로세스의 구성요소 중 하나이다. instruction의 주소를 가리키는 PC(Program Counter = IP:Instruction Pointer)와 함수 인자, 지역변수, return address를 다루기 위해 Stack을 가리키는 Stack pointer(= frame pointer)가 포함된다.
- Process API
실제 OS에서 이름이 다를 수 있지만 기능은 비슷하게 구현되어 있는 API들이다.
- Create: 새로운 프로세스 생성
- Destroy: 프로세스를 강제로 중지
- Wait: 실행 중인 프로세스를 강제로 멈춤
- Miscellaneous Control: 프로세스를 죽이거나 기다리게 하는 것과는 약간 다른 잡다한 기능들
- Status: 프로세스에 대한 정보
- Process Creation
프로그램을 실행시켜 프로세스를 생성하는 과정은 다음과 같은 과정으로 진행된다.
- 먼저 OS는 프로그램을 실행하기위해
code
와static data
를memory
에 load한다. 프로그램은 disk 같은 저장소에 executable format으로 저장되어있다. 이런 프로그램을 memeory로 올리는 것이다.
- 다음으로 메모리에
Run-time stack
(stack)을 할당한다. 이 stack에지역변수
, 함수인자
(argc, argv 등),return 주소
등을 담는다. main() 함수의 호출을 위한 준비이다.
- 다음으로 OS는 메모리 공간에
Heap
공간을 할당한다.
- 이후 OS는
I/O
와 같은task initialize
작업을 진행한다.File Descriptors
3개를 여는 것 등이 포함된다.
- 위 준비 과정이 끝나면
main()
이라고 불리는 entry point로 진입해 프로그램을 실행한다.
- Process States
- Running: 프로세스가 실행 중인 상태. instruction을 수행 중인 상태이다. (Memory <-> register 값을 쓰거나 읽거나 연산은 running)
- Ready: 잠시 실행을 멈춘 상태. (run 할 준비) (메모리에는 올라왔는데 CPU가 없어서 ready 상태)
- Blocked: ready와는 다른 정지 상태이다. 예를 들어 프로세스가 disk로
I/O
요청을 보내면 blocked
- Process State Transition
프로세스의 상태 변화를 의미한다.
Running → Ready 상태 변화를 Deschedule이라고 하며, 그 반대의 경우는 Schedule이라고 한다.
- Ex.1
과 이 CPU만 사용하는 경우이다. 가 Running 상태 은 Ready 상태이다. 이 끝나면 이 Ready → Ruuning으로 상태 변화를 한다.
- Ex.2
이번엔 I/O 요청을 포함한 상태 변화다. 이 Running 상태에서 I/O 요청을 하면 Blocked 상태가 된고, 이후 이 스케쥴 된다. 의 I/O 작업이 끝나면 Blocked → Ready로 바뀐다.
- Data Structures
프로세스가 사용하는 Data Structure들이다.
// the registers xv6 will save and restore
// to stop and subsequently restart a process
struct context {
int eip;
int esp;
int ebx;
int ecx;
int edx;
int esi;
int edi;
int ebp;
};
프로세스가 멈췄을 때 register 정보를 저장하기 위한 register context
가 있다. 다음 챕터에서 context switching과 함께 다룬다.
// the different states a process can be in
enum proc_state { UNUSED, EMBRYO, SLEEPING,
RUNNABLE, RUNNING, ZOMBIE };
// RUNABLE = READY
// SLEEPING = BLOCKED
프로세스 상태를 저장하는 proc_state이다.
// the information xv6 tracks about each process
// including its register context and state
struct proc {
char *mem; // Start of process memory
uint sz; // Size of process memory
char *kstack; // Bottom of kernel stack
// for this process
enum proc_state state; // Process state
int pid; // Process ID
struct proc *parent; // Parent process
void *chan; // If non-zero, sleeping on chan
int killed; // If non-zero, have been killed
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
struct context context; // Switch here to run process
struct trapframe *tf; // Trap frame for the
// current interrupt
};
프로세스의 구조체이다. 앞서 언급한 context, state와 pid, kstack 등을 포함하고 있다.
- PCB
프로세스의 정보를 저장하기 위해 PCB
(Process Control Block)라는 data structure를 사용한다.
Interlude: Process API
fork
fork()
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) { printf("hello world (pid:%d)\n", (int) getpid()); int rc = fork(); if (rc < 0) { // fork failed; exit fprintf(stderr, "fork failed\n"); exit(1); } else if (rc == 0) { // child (new process) printf("hello, I am child (pid:%d)\n", (int) getpid()); } else { // parent goes down this path (main) printf("hello, I am parent of %d (pid:%d)\n", rc, (int) getpid()); } return 0; }
fork()
를 호출하면 프로세스를 그대로 복사해 프로세스를 생성한다.
(address space나 register 같은 모든 정보 + DATA, CODE, HEAP, STACK. 단, return 값이 다름)
이렇게 생선된 프로세스가 자식 프로세스이며, 생성에 성공하면 0을 return한다.
output
prompt> ./p1 hello world (pid:29146) hello, I am child (pid:29147) hello, I am parent of 29147 (pid:29146)
fork() 함수는 비결정론적이다. 그래서 wait() 함수를 사용한다.
wait
wait()
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main(int argc, char *argv[]) { printf("hello world (pid:%d)\n", (int) getpid()); int rc = fork(); if (rc < 0) { // fork failed; exit fprintf(stderr, "fork failed\n"); exit(1); } else if (rc == 0) { // child (new process) printf("hello, I am child (pid:%d)\n", (int) getpid()); } else { // parent goes down this path (main) int wc = wait(NULL); printf("hello, I am parent of %d (wc:%d) (pid:%d)\n", , rc, wc, (int) getpid()); } return 0; }
wait()
함수를 호출하면 자식 프로세스의 동작이 끝날 때까지 부모 프로세스가 기다린다.
자식 프로세스의 PID를 return한다.
return 값은
위 예시는 fork() 함수의 return 값이 0이면 자식 프로세스라는 점을 이용해 분기문을 만든 예시이다.
output
prompt> ./p2 hello world (pid:29266) hello, I am child (pid:29267) hello, I am parent of 29267 (wc:29267) (pid:29266)
→ child 프로세스의 동작이 끝난 뒤 부모 프로세스가 동작한다.
fork() 함수를 호출하면 부모 프로세스의 정보를 그대로 복사하기 때문에 위 처럼 if-else 문을 이용해 다른 동작으로 하도록 유도할 수 있다.
물론 이렇게 if-else문을 이용하지 않고 새로운 명령을 수행하도록 프로그래밍할 수도 있다.
exec
exec()
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/wait.h> int main(int argc, char *argv[]) { printf("hello world (pid:%d)\n", (int) getpid()); int rc = fork(); if (rc < 0) { // fork failed; exit fprintf(stderr, "fork failed\n"); exit(1); } else if (rc == 0) { // child (new process) printf("hello, I am child (pid:%d)\n", (int) getpid()); char *myargs[3]; myargs[0] = strdup("wc"); // program: "wc" (word count) myargs[1] = strdup("p3.c"); // argument: file to count myargs[2] = NULL; // marks end of array execvp(myargs[0], myargs); // runs word count printf("this shouldn’t print out"); } else { // parent goes down this path (main) int wc = wait(NULL); printf("hello, I am parent of %d (wc:%d) (pid:%d)\n", rc, wc, (int) getpid()); } return 0; }
위 코드처럼 exec()
계열의 함수를 이용해 자식 프로세스가 다른 명령을 수행하도록 만들 수 있다.
하지만 exec 계열의 함수는 code와 static data를 load해서 자신의 code segment와 data segment에 overwrite한다. 따라서 exec() 계열의 함수 뒤에 있는 명령어는 절대로 실행되지 않는다.
예) 위 코드의 printf("this shouldn’t print out");
Uploaded by Notion2Tistory v1.1.0