Notice
Recent Posts
Recent Comments
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

topcue

[OS] Process 본문

Operating System

[OS] Process

topcue 2021. 9. 3. 16:02

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

프로그램을 실행시켜 프로세스를 생성하는 과정은 다음과 같은 과정으로 진행된다.

  1. 먼저 OS는 프로그램을 실행하기위해 codestatic datamemory에 load한다. 프로그램은 disk 같은 저장소에 executable format으로 저장되어있다. 이런 프로그램을 memeory로 올리는 것이다.
  1. 다음으로 메모리에 Run-time stack(stack)을 할당한다. 이 stack에 지역변수, 함수 인자(argc, argv 등), return 주소 등을 담는다. main() 함수의 호출을 위한 준비이다.
  1. 다음으로 OS는 메모리 공간에 Heap 공간을 할당한다.
  1. 이후 OS는 I/O와 같은 task initialize 작업을 진행한다. File Descriptors 3개를 여는 것 등이 포함된다.
  1. 위 준비 과정이 끝나면 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

Process0Process_0Process1Process_1이 CPU만 사용하는 경우이다. Process0Process_0가 Running 상태 Process1Process_1은 Ready 상태이다. Process0Process_0이 끝나면 Process1Process_1이 Ready → Ruuning으로 상태 변화를 한다.

  • Ex.2

이번엔 I/O 요청을 포함한 상태 변화다. Process0Process_0이 Running 상태에서 I/O 요청을 하면 Blocked 상태가 된고, 이후 Process1Process_1이 스케쥴 된다. Process1Process_1의 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 계열의 함수는 codestatic data를 load해서 자신의 code segment와 data segment에 overwrite한다. 따라서 exec() 계열의 함수 뒤에 있는 명령어는 절대로 실행되지 않는다.

예) 위 코드의 printf("this shouldn’t print out");


'Operating System' 카테고리의 다른 글

[OS] Segmentation  (0) 2021.09.03
[OS] Address Space & Traslation  (0) 2021.09.03
[OS] Scheduling  (0) 2021.09.03
[OS] LDE  (0) 2021.09.03
[OS] Introduction to Operating Systems  (0) 2021.09.03
Comments