티스토리 뷰

Study/OS

[Process] Process Creation

생각많은 소심남 2013. 6. 20. 21:22

이전 포스트에서는 Process의 scheduling 과정을 살펴봤었고, 이번에는 어떤 과정을 거쳐서 Process가 생성되는지를 알아보려고 한다. 

우선 이걸 다루기 전에 OS에서 Process가 어떤 구조를 가지고 있는지를 간단하게 살펴볼 필요가 있다. 

Process의 구조형태는 일종의 트리 형태를 지닌다. 다른 말로 표현하면 모든 node Process들은 parent와 child를 가진다는 것이다. 그런데 만약 그렇다면 가장 상단에 있는 node는 누가 만드는 것일까? 창조론을 믿는 건 아니지만, 그렇다고 형태가 있는 한 무에서 유가 창조된다고는 생각하지 않는다.  Process Creation에서는 창조주역할을 하는 Process가 있다. 우리가 이전 포스트를 살펴보면서 PCB의 어딘가에는 서로 다른 Process를 구별할 수 있는 id가 있다고 했었다. 즉 Process id가 0번인 Process가 이 창조주역할을 한다. 그리고 이 0번 Process는 OS에서 자체적으로 생성되는데 linux에서는 /init에 있는 main.c에서 이 과정을 살펴볼 수 있다. 

static noinline void __init_refok rest_init(void)
  2 {
  3 	int pid;
  4 
  5 	rcu_scheduler_starting();
  6 	/*
  7 	 * We need to spawn init first so that it obtains pid 1, however
  8 	 * the init task will end up wanting to create kthreads, which, if
  9 	 * we schedule it before we create kthreadd, will OOPS.
 10 	 */
 11 	kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
 12 	numa_default_policy();
 13 	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
 14 	rcu_read_lock();
 15 	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
 16 	rcu_read_unlock();
 17 	complete(&kthreadd_done);
 18 
 19 	/*
 20 	 * The boot idle thread must execute schedule()
 21 	 * at least once to get things moving:
 22 	 */
 23 	init_idle_bootup_task(current);
 24 	preempt_enable_no_resched();
 25 	schedule();
 26 
 27 	/* Call into cpu_idle with preempt disabled */
 28 	preempt_disable();
 29 	cpu_idle();
 30 }


저 볼딕처리된 부분이 바로 pid=0인 idle Process가 생성되는 곳이다. 이 idle Process가 이름만 보면 항상 멈춰있을 듯한 Process인거 같지만 실제로는 항상 대기중인 상태에 있다가 시스템의 원할한 동작을 위해 다른 프로세스들을 옮기는 작업을 수행한다. 저번에도 이런 것과 비슷한 역할을 하는게 mid-term scheduler라고 했었는데 역시 이 idle process는 자기 밑의 process를 Swap시키는 역할을 한다. 그래서 다른 말로 Swapper라고도 한다. 조금더 설명하자면 Process의 상태에 따라서 Memory에 올라가 있는 Process를 디스크로 swap in/out 시키는 역할을 한다. 이제 여기서 0번 Process가 할일을 마치면 다음으로는 pid=1인 Process가 생성된다. 이게 바로 init 이라는 이름을 가진 Process 이고 user에게 필요한 시스템 설정을 수행한다. 보통 이 내용은 inittab이라는 파일에 정의되어 있는데 여기서부터 사용자가 설정한 Process들을 생성할 수 있다. 그래서 일반적으로는 다음과 같은 tree형태를 취하게 된다.



 보통 process를 생성하는데 가장 핵심적인 함수는 바로 fork()와 exec(), wait() 이 세가지다. 이걸 통해서 child Process를 생성할 수 있고, child Process내에서 원하는 작업을 실행시킬 수 있게끔 할 수 있다. 그래서 Fork()를 수행한 다음에는 다음과 같은 과정을 거치게 된다.



우선 fork의 역할은 새로운 process를 만들어내는 일종의 system call이고, 현재 상태의 process와 pid만 다르고 완벽히 똑같은 process를 복제한다. 이 새 Process는 생성되면서 그 Process만의 data section이나 heap stack을 가지게 되면서 새로운 pid를 할당 받게 된다. linux에서는 이런 역할을 하는 fork(),vfork(), __clone()이 구현되어 있으며,  do_fork()를 거치면서 새로운 process가 생성되는 과정을 취하고 있다. 여담으로 vfork()는 유추했다시피 virtual fork()이며, 기존의 Fork()가 process 각자의 영역을 가지는 것과 다르게 복제하면서 영역을 공유하는 점에서 차이가 나타난다.



자 그럼 한번 예제를 통해서 fork가 제대로 동작하는지를 확인해보려고 한다. 다음과 같이 fork를 test할 수 있는 간단한 프로그램이 있다.

  1 #include <sys/types.h>
  2 #include <unistd.h>
  3 #include <stdio.h>
  4 
  5 int main(){
  6 	pid_t pid;
  7 	char *message;
  8 	int n;
  9 
 10 	printf("fork test start!!\n");
 11 	pid = fork();
 12 
 13 	switch(pid){
 14 		case -1:
 15 			perror("fork failed");
 16 			exit(1);
 17 
 18 		case 0:
 19 			message = "This process is child process";
 20 			n = 5;
 21 			break;
 22 
 23 		default:
 24 			message = "This process is parent process";
 25 			n = 3;
 26 			break;
 27 
 28 	}
 29 
 30 	for(; n > 0; n--){
 31 		puts(message);
 32 		sleep(1);
 33 	}
 34 
 35 	exit(0);
 36 }


정상적으로 수행된다면 pid가 0번인 child process는 5번 수행될 것이고, 본래 fork()를 수행한 Parent Process는 3번만 위 메세지를 출력할 것이다. 결과는 다음과 같다.



잘보면 지금 Parent Process가 shell을 수행하고 있음에도 child Process가 문구를 출력한 것을 확인할 수 있다. 이로써 Parent와 child가 concurrent하게 실행되고 있었음을 확인할 수 있다. 그래서 보통은 Child Process가 끝나기를 Parent Process 가 기다리다가 다 끝나고 난후에 비로소 기존에 suspend 되었던 작업을 이어서 한다. 


다음 명령어는 실행 관련 system call인 exec()이다. 보통은 단일로 쓴다기 보다는 Path나 명령어를 삽입한 형태인 execl()이나 execlp(), execle()같은 형태로 사용된다. 역시 이 명령어도 다음과 같은 예제를 통해서 동작을 확인할 수 있다.

  1 #include <unistd.h>
  2 #include <stdio.h>
  3 
  4 int main(){
  5 	printf("Running ps with execlp\n");
  6 	execlp("ps", "ps", "-ax", 0);
  7 	printf("Done!\n");
  8 
  9 	exit(0);
 10 }


위에서 표현한 것처럼 ps 명령어에 -ax 옵션을 줘서 현재 실행되고 있는 process들이 나열될 것이다.



여기서도 잘 보면 pid=1인 init 이 보인다. 0번인 swapper가 안보이는데 그건 현재 수행되고 있는 것이 아닌 항상 idle 상태에 있기 때문에 보이지 않는다.


그럼 마지막인 wait()에 대한 내용만 남았다. Wait()는 말그대로 기다리게 하는 함수로써 보통 Parent Process는 child Process가 수행을 완료할 때까지 hold가 되는데 이때 사용되는 system call이다. 이 wait는 Parent가 아닌 child에서 호출되는데 끝나고 현재 수행이 완료된 Process의 pid값을 return 해준다. 그래서 이 값을 보고 완료 Process의 Parent가 다시 수행할 수 있게끔 알려주는 형태가 되겠다. 

그래서 위에서 소개한 세가지 함수들을 조합해보면 다음과 같은 그림이 나오게 된다.


아무튼 Parent가 종료되기 위해서는 항상 child Process의 상태를 보고 있어야 한다. 가령 Parent가 먼저 죽으면 child Process가 말 그대로 부모없는 놈(?)이 되어버리기에 항상 이를 고려하고 Process가 관리되어야 한다. 참고로 wait에서도 정상적으로 child process가 종료되었는지 혹은 어떤 문제가 생겨서 종료되었는지를 알 수 있는 exit_code field가 존재한다. 이 부분이 더 궁금한 사람은 직접 /include/sys/wait.h 파일을 살펴보면 확인할수 있겠다. 

역시 이부분도 예제가 필요할 듯 하다. 앞에서 다뤘던 fork예제에 wait만 추가된 형태이다.

1 #include <sys/types.h> 2 #include <sys/wait.h> 3 #include <unistd.h> 4 #include <stdio.h> 5 6 int main() 7 { 8 pid_t pid; 9 char *message; 10 int n; 11 int exit_code; 12 13 printf("test program start!\n"); 14 pid = fork(); // 프로세스 생성 15 16 switch(pid) 17 { 18 case -1: 19 perror("fork failed!"); 20 exit(1); 21 22 case 0: 23 message = "This is child process"; 24 n = 5; 25 exit_code = 37; 26 break; 27 28 default: 29 message = "This is parent process"; 30 n = 3; 31 exit_code = 0; 32 break; 33 } 34 35 for(; n > 0; n--){ 36 puts(message); 37 sleep(1); 38 } 39 40 if(pid != 0){ // <- Parent Process일때 41 int stat_val; 42 pid_t child_pid; 43 44 child_pid = wait(&stat_val); // Wait의 return 값을 child_pid로 지정 45 46 printf("child process has finished: PID = %d\n", child_pid); 47 48 if(WIFEXITED(stat_val))     //exit_code가 &stat_val로 들어감 49 printf("Child process exited with code %d\n", WEXITSTATUS(stat_val)); 50 else 51 printf("Child terminated abnormally!\n"); 52 } 53 54 exit(exit_code); 55 }


그래서 다음과 동작하는 것을 확인할 수 있다.



지금까지 Process Creation에 대한 내용을 간단한 예제와 함께 살펴보았다. Process도 일종의 실행단위이고 user가 computation을 활용하기 위해서는 반드시 Process의 구조를 알고 어떻게 생성하는지에 대해서 알아야 된다고 생성한다. 여기있는 내용이 깊은 내용이 아니라서 조금 더 찾아볼 여지가 있으니 구글링을 통해서 조금더 찾아보면 좋을 거 같다.


Reference:

- Operating System Concepts : 3.3 Operations on Processes

- Swapper의 역할 : http://wiki.kldp.org/KoreanDoc/html/Boot_Process-KLDP/swapper.html

- init 의 역할 : http://weezzle.net/1818

'Study > OS' 카테고리의 다른 글

[OS] Context Switch  (0) 2013.06.29
[OS] POSIX Thread  (0) 2013.06.25
[Process] Inter Process Communication (IPC)  (5) 2013.06.21
[Memory] Memory Addressing  (2) 2013.06.20
[Process] Process Scheduling  (1) 2013.06.20
[Process] Process / Process Control Block  (2) 2013.06.19
[Study] POSIX  (0) 2013.02.21
댓글