OS,  Study

[OS] fork: Process 생성 및 종료

Linux에서 fork()는 자식 process를 생성하는데 쓰이는 system call 함수다.

#include <unistd.h>

pid_t fork(void);
ArgumentDescription
void
ReturnDescription
0Child process인 경우
0보다 큰 경우Parent process인 경우
-1비정상적 종료가 발생한 경우
#include <stdio.h>
#include <unistd.h>

int main() { 
    fork();
    printf("Hello\n");
    return 0;
}

위 코드를 수행하게 되면 둘 다 동일하게 printf("Hello\n");를 호출하게 되는데 이는 race condition 때문에 누가 먼저 호출하는지 알 수 없다.


Process 종료

생성된 process는 exit 함수 library를 호출해서 작업을 종료하게 되며, 만약 비정상 종료를 할 경우 signal을 호출하게 된다.

정상적인 종료를 할 경우엔

  • Open한 file descriptor close
  • Memory de-allocation
  • exit 함수는 standard I/O 정리 routine을 수행 후 _exit을 호출
  • C compiler는 main 함수에서 return하는 경우 자동으로 exit 함수 호출되도록 코드를 생성

Example (zombie process 생성)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    pid_t pid;
    pid = fork();

    if(pid == 0) {
        for (int i=0; i<3; i++) {
            printf("\t\t\tchild\n");
            sleep(1);
        }   
        exit(0);
    }   
    while(1) {
        printf("parent\n");
        sleep(1);
    }   

    return 0;
}
$ ./a.out 
 parent
             child
 parent
             child
 parent
             child
 parent
 parent

위 코드를 수행시키면 위와 같이 3초가 지나면 child process는 exit(0) 호출 뒤에 더 이상 "child"라는 단어를 출력하지 않게 된다. 이 때 child process는 죽은 것이 아니라 TASK_RUNNING state에서 EXIT_ZOMBIE state로 변하게 된다. Parent가 child의 종료 값을 wait(0)로 가져가야 몸체를 파괴 할 수 있다. 위 코드는 wait(0)를 사용하지 않고 exit(0)만을 사용했기 때문에 child process는 죽지 않는 상태가 된다.

$ ps -ef | grep a.out
shumin   18961 18373  0 23:37 pts/24   00:00:00 ./a.out
shumin   18962 18961  0 23:37 pts/24   00:00:00 [a.out] <defunct>

실제로 ps 명령어를 통해 보면 child process인 18962가 죽지 남아 있는 것을 볼 수 있다.


Zombie Process vs. Orphaned Process

Zombie process: child process가 종료했으나 parent process의 “wait” 처리가 끝나지 않은 child process를 말한다. 즉, parent process 입장에서 child process의 종료 상태를 회수하지 못한 경우다. 이 때 Kernel은 process ID, status, CPU 사용 시간 등 정보를 가지고 있다.
Parent process가 zombie process의 종료 상태를 회수하게 되면 (wait system call을 통해) zombie process는 제거된다.

Orphaned process: 고아 프로세스라고 불리며, child process가 종료하기 전에 parent process가 수행을 마친 경우의 child process를 말한다. 이 때 Kernel은 child process를 찾아서 “init” process의 child가 되도록 한다.

Example (no concurrency)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    pid_t pid;
    pid = fork();

    if(pid == 0) {
        for (int i=0; i<3; i++) {
            printf("\t\t\tchild\n");
            sleep(1);
        }   
        exit(7);
    }   
    wait(0);
    while(1) {
        printf("parent\n");
        sleep(1);
    }   

    return 0;
}
$ ./a.out 
             child
             child
             child
 parent
 parent
 parent
 parent

위 코드는 child process가 3초동안 동작한 뒤에 exit(7)을 통해 종료하고 난 뒤에 parent process가 wait(0)에서 깨어나서 동작하는 프로그램이다. 이런 경우 zombie process가 되는 것을 막을 수 있지만 concurrency가 보장되지 못하여 multi process의 의미를 상실하게 된다.


Signal

Child process가 종료 될 때 exit 함수를 호출하면서 zombie process가 되는데, 이 때 parent process PID를 통해 signal (SIGCHLD)을 보내게 되는데, default로는 SIGCHLD -> SIG_IGN로 변경하게 되어있다.

SIG_IGN: signal을 무시하도록 하는 신호

따라서 parent process가 asynchronous하게 SIG_IGN 신호 대신 wait를 호출시켜서 zombie process를 수거해주는 코드가 필요하다.

Example (zombie process 제거)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

void sig(int sig_no) {
    printf("sig(%d)\n", sig_no);
    printf("PID: %d\n", getpid());
    wait(0);
}

int main() {
    pid_t pid;
    printf("Parent PID: %d\n", getpid());

    signal(SIGCHLD, sig);
    pid = fork();

    if(pid == 0) {
        printf("Child PID: %d\n", getpid());
        for (int i=0; i<3; i++) {
            printf("\t\t\tchild\n");
            sleep(1);
        }   
        exit(7);
    }   
    while(1) {
        printf("parent\n");
        sleep(1);
    }   

    return 0;
}
 Parent PID: 16384
 Child PID: 16385
             child
             child
             child
 sig(17)
 PID: 16384
 parent
 parent
 parent
 parent
 parent

따라서 기존에 wait()를 그냥 사용하게 되면 parent process가 child process가 죽을 때 까지 기다리는 문제가 있었는데, signal을 통해서 concurrency를 살릴 수 있게 됐다.

Leave a Reply

Your email address will not be published. Required fields are marked *