waitpid 함수는 wait 함수처럼 자식 프로세스를 기다릴때 사용하는 함수입니다. 즉, 자식 프로세스의 종료상태를 회수할 때 사용합니다.

하지만 waitpid 함수는 자식 프로세스가 종료될 때 까지 차단되는 것을 원하지 않을 경우, 옵션을 사용하여 차단을 방지할 수 있습니다.

그리고 기다릴 자식 프로세스를 좀더 상세히 지정할 수 있습니다.


#include <sys/wait.h>

 pid_t waitpid(pid_t pid, int *statloc , int options);

 성공 : 프로세스 ID 반환

 오류 : -1 



waitpid 함수의 첫번째 인자에 대해서 알아 보겠습니다.

 wiatpid 함수의 첫 번째 인자

 의미

  pid가 -1 일 경우  (pid ==  -1)

   임의의 자식 프로세스를 기다림

  pid가 0 보다 클 경우 (pid > 0)

   프로세스 ID가 pid인 자식 프로세스를 기다림

  pid가 -1 보다 작을 경우 (pid < -1)

  프로세스 그룹 ID가 pid의 절댓값과 같은 자식 프로세스를 기다림

 pid가 0일 경우 (pid == 0)

 waitpid를 호출한 프로세스의 프로세스 그룹 PID와 같은 프로세스 그룹 ID를 가진 프로세스를 기다림 

waitpid 함수의 오류(-1)는 지정한 pid의 프로세스 또는 프로세스 그룹이 없는 경우에 발생하며 그리고 pid가 자식 프로세스가 아닐 때 발생합니다.


두번째 인자에 대해서 알아 보겠습니다.

 

 waitpid 함수 반환 값

 두 번째 인자 인 statloc 값

 자식 프로세스가 정상적으로 종료

 프로세스 ID

 - WIFEXITED(statloc) 매크로가 true를 반환

 - 하위 8비트를 참조하여 자식 프로세스가 exit, _exit, _Exit에 넘겨준 인자값을 얻을 수 있음, WEXITSTATUS(statloc)

  자식 프로세스가 비정상적으로 종료

 프로세스 ID

 - WIFSIGNALED(statloc) 매크로가 true를 반환

 - 비정상 종료 이유를 WTERMSIG(statloc) 매크로를 사용하여 구할 수 있음

 waitpid 함수 오류  -1

 - ECHILD : 호출자의 자식 프로세스가 없는 경우

 - EINTR : 시스템 콜이 인터럽트 되었을 때


세번째 인자에 대해서 알아 보겠습니다.

 세 번째 인자로 사용가능한 상수

 의미 

 WCONTINUED

 중단 되었다가 재개된 자식 프로세스의 상태를 받음

 WNOHANG 

 기다리는 PID가 종료되지 않아서 즉시 종료 상태를 회수 할 수 없는 상황에서 호출자는 차단되지 않고 반환값으로 0을 받음

 WUNTRACED 

 중단된 자식 프로세스의 상태를 받음


아래 예제는 세 번째 인자에 아무것도 주지 않았을 때 wait 함수와 동일한 동작하는 것을 확인해 보겠습니다.

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main() { pid_t childPid; int status; childPid = fork(); if(childPid > 0) { // 부모 프로세스 int ret; printf("부모 PID : %ld, pid : %d\n",(long)getpid(), childPid); sleep(3); ret = waitpid(childPid,&status,0); // 세번째 인자에 0을 넣었으므로 wait 함수와 동일한 동작을 함 printf("부모 종료 %d %d %d\n",ret,WIFEXITED(status),WEXITSTATUS(status)); exit(0); } else if(childPid == 0){ // 자식 코드 printf("자식 시작 PID : %ld\n", (long)getpid()); sleep(8); printf("자식 종료\n"); exit(0); } else { // fork 실패 perror("fork Fail! \n"); return -1; } return 0; }


실행 결과를 보겠습니다.

부모 PID : 13444, pid : 13445

자식 시작 PID : 13445

자식 종료

부모 종료 13445 1 0 

부모 프로세스는 3초 동안 동작하고 자식 프로세스는 8초 동안 동작하는 예제 코드입니다. 하지만 결과는 자식 프로세스가 먼저 종료되고 부모 프로세스가 종료되었습니다.

waitpid 함수는 세번째 인자로 0을 주었기 때문에 자식 프로세스가 종료될 때 까지 blocking 되었기 때문입니다.

WIFEXITED 매크로는 1(true)를 리턴하였으며 WEXITSTATUS는 자식 프로세스가 0을 리턴 한 것을 알려주고 있습니다.


다음 예제는 세 번째 인자에 WNOHANG 옵션을 넣어보도록 하겠습니다.

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main() { pid_t childPid; int status; childPid = fork(); if(childPid > 0) { // 부모 프로세스 int ret; printf("부모 PID : %ld, pid : %d\n",(long)getpid(), childPid); sleep(3); ret = waitpid(childPid,&status,WNOHANG); // WNOHANG 옵션을 사용하면 자식 프로세스가 종료되지 않았을 경우 blocking 되지 않고 즉시 0값을 리턴합니다. printf("부모 종료 %d %d\n",ret,WIFEXITED(status)); exit(0); } else if(childPid == 0){ // 자식 코드 printf("자식 시작 PID : %ld\n", (long)getpid()); sleep(8); printf("자식 종료\n"); exit(0); } else { // fork 실패 perror("fork Fail! \n"); return -1; } return 0; }

실행 결과를 확인해 보겠습니다.

부모 PID : 14289, pid : 14290

자식 시작 PID : 14290

부모 종료 0 0 127

자식 종료 

waitpid 함수는 0을 리턴하였으며 WIFEXITED 매크로도 false를 리턴하였습니다. WNOHANG 옵션을 사용하였기 때문에 자식 프로세스가 종료될 때 까지 기다리지 않고 waitpid 함수가 바로 0값을 리턴하였기 때문입니다.


마지막으로 존재하지 않는 자식 프로세스의 PID를 waitpid 첫 번째 인자로 넣어 보겠습니다.

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <sys/error.h> #include <sys/string.h> int main() { pid_t childPid; int status; childPid = fork(); if(childPid > 0) { // 부모 프로세스 int ret; printf("부모 PID : %ld, pid : %d\n",(long)getpid(), childPid); sleep(3); ret = waitpid(99999,&status,WNOHANG); // pid로 99999(존재하지 않는 프로세스 ID) printf("부모 종료 %d %d %s\n",ret,errno,strerror(errno)); // 오류가 발생하였을 경우 errno값을 확인 exit(0); } else if(childPid == 0){ // 자식 코드 printf("자식 시작 PID : %ld\n", (long)getpid()); sleep(8); printf("자식 종료\n"); exit(0); } else { // fork 실패 perror("fork Fail! \n"); return -1; } return 0; }

실행 결과를 보도록 하겠습니다.

부모 PID : 17859, pid : 17860

자식 시작 PID : 17860

부모 종료 -1 10 No child processes

자식 종료 

waitpid 함수는 -1 오류 값을 반환하였습니다. 그리고 에러 넘버(errno) 10, 즉 NO child processes라는 에러를 출력하였습니다. 

99999라는 자식 프로세스가 존재하지 않는다는 의미입니다. 그 이유는 waitpid 함수의 첫 번째 인자로 존재하지 않는 99999 PID값을 입력하였기 때문입니다.


지금까지 wait 함수보다 좀더 다양한 기능을 제공하는 waitpid 함수에 대해서 알아 보았습니다.

  1. F.R.I.D.A.Y. 2021.12.07 14:51 신고

    WNOHANG 옵션을 주었을 때, 출력에 다음과 같이

    부모 PID : 14289, pid : 14290
    자식 시작 PID : 14290
    부모 종료 0 0 127
    자식 종료
    로 되어 있습니다. <부모 종료 0 0 127>로 잡혀있는데, 실제 코드에서 127을 출력하는 부분은 없는데, 127은 무슨 값으로 판단하면 될까요?

이번 포스팅에서는 고아 프로세스와 좀비 프로세스에 대해서 알아 보도록 하겠습니다.


고아 프로세스와 좀비 프로세스 정의는 아래와 같습니다.

부모 프로세스가 자식 프로세스보다 먼저 종료되면 자식 프로세스는 고아프로세스가 됩니다.

그리고 자식 프로세스가 종료되었지만 부모 프로세스가 자식 프로세스의 종료 상태를 회수하지 않았을 경우에 자식 프로세스를 좀비 프로세스라고 합니다.


1. 고아 프로세스

부모 프로세스가 자식 프로세스보다 먼저 종료되면 init 프로세스가 자식 프로세스 새로운 부모 프로세스가 됩니다.

종료되는 프로세스가 발생할 때 커널은 이 프로세스가 누구의 부모 프로세스인지 확인한 후, 커널이 자식 프로세스의 부모 프로세스 ID를 1(init 프로세스)로 바꿔 줍니다.

간단한 예제를 통해서 고아 프로세스의 부모 프로세스가 init 프로세스로 바뀌는지 확인해 보도록 하겠습니다.


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

int main() {
    
    pid_t childPid;
    int i;
    
    childPid = fork();
    
    if(childPid > 0) {  // 부모 프로세스
        printf("부모 PID : %ld, pid : %d\n",(long)getpid(), childPid);
        sleep(2);
        printf("부모 종료\n");
        exit(0);
    }
    else if(childPid == 0){  // 자식 코드
        printf("자식 시작\n");
        
        for(i=0;i<10;i++) {
            printf("자식 PID : %ld 부모 PID : %ld\n",(long)getpid(), (long)getppid());
            sleep(1);
        }
        
        printf("자식 종료\n");
        exit(0);
    }
    else {  // fork 실패
        perror("fork Fail! \n");
        return -1;
    }
    
    return 0;
}



실행 결과는 아래와 같습니다.

 

자식 프로세스의 원래 부모 프로세스는 PID 46797 였으나 부모 프로세스가 종료 된 후에는 1로 바뀐 것을 볼 수 있습니다.

init 프로세스는 유닉스 계열의 운영체제에서 부팅 과정 중 생성되는 최초의 프로세스이며 시스템이 종료될때까지 계속 살아있는 데몬 프로세스입니다.

그리고 init 프로세스의 PID는 일반적으로 1입니다. 

고아 프로세스가 작업을 종료하면 init 프로세스가 wait함수를 호출하여 고아 프로세스의 종료 상태를 회수함으로써 좀비 프로세스가 되는것을 방지합니다.


2. 좀비 프로세스

반대로 자식 프로세스가 부모 프로세스 보다 먼저 종료되는 경우가 있습니다. 

자식 프로세스가 exit 시스템 콜을 호출 하면서 종료되면 이 프로세스에 관련된 모든 메모리와 리소스가 해제되어 다른 프로세스에서 사용할 수 있게 됩니다.

자식 프로세스가 종료된 이후에 부모 프로세스가 자식 프로세스의 상태를 알고 싶을 수 있기 때문에 커널은 자식 프로세스가 종료되더라도 최소한의 정보(프로세스 ID, 프로세스 종료 상태 등)를 가지고 있게 됩니다.

부모 프로세스가 좀비 프로세스의 종료상태를 회수하게 되면(wait 시스템콜을 호출을 통하여)  좀비 프로세스는 제거됩니다.

간단한 예제 코드를 보도록 하겠습니다.


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


int main() {
    
    pid_t childPid;
    
    childPid = fork();
    
    if(childPid > 0) {  // 부모 프로세스
        printf("부모 PID : %ld, pid : %d\n",(long)getpid(), childPid);
        sleep(30);
        printf("부모 종료\n");
        exit(0);
    }
    else if(childPid == 0){  // 자식 코드
        printf("자식 시작 PID : %ld\n", (long)getpid());
        sleep(1);
        printf("자식 종료\n");
        exit(0);
    }
    else {  // fork 실패
        perror("fork Fail! \n");
        return -1;
    }
    
    return 0;
}


터미널 창에 아래와 같이 명령어를 입력합니다.

ps aux | grep 'Z' 명령어를 사용하여 좀비 프로세스를 출력합니다.


좀비 프로세스가 쌓이게 되면 리소스의 유출을 야기할 수 있기 때문에 좀비 프로세스 상태를 오래 유지되지 않도록 부모프로세스는 wait 시스템 콜 함수를 사용하여 자식 프로세스의 종료 상태를 읽어들이는 것이 필요합니다.

부모 프로세스가 fork() 함수를 사용하여 자식 프로세스를 생성하였을 때, fork() 함수가 리턴되는 시점부터 2개의 프로세스가 동작하게 됩니다.

부모 프로세스가 자식 프로세스의 종료 상태를 얻기 위해서는 wait() 함수를 사용합니다.

다른 말로 표현하면 wait() 함수를 사용하여 자식 프로세스가 종료 될 때까지 기다릴 수 있습니다.


wait() 함수는 아래와 같이 동작합니다.

1. 자식 프로세스가 동작 중이면 호출 차단이 차단되기 때문에 상태를 얻어올 때까지 대기

2. wait() 함수 호출자가 시그널을 받을 때까지 대기

3. 자식 프로세스가 종료된 상태라면 즉시 호출이 반환되어 상태를 얻음, 이 때 wait() 함수는 자식 프로세스의 프로세스 ID를 반환

4. 자식 프로세스가 없다면 호출이 즉시 반환되며, 에러값을 반환


 #include <sys/wait.h>

 pid_t wait(int *statloc);

 성공 : 프로세스 ID 반환

 오류 : -1


자식 프로세스가 정상 종료되었을 때와 비정상 종료 되었을 때, wait() 함수의 반환값과 statloc 인자값이 다르게 채워집니다.


 

 wait 함수 반환 값

 statloc  값

 자식 프로세스가 정상적으로 종료

 프로세스 ID

 - WIFEXITED(statloc) 매크로가 true를 반환

 - 하위 8비트를 참조하여 자식 프로세스가 exit, _exit, _Exit에 넘겨준 인자값을 얻을 수 있음, WEXITSTATUS(statloc)

  자식 프로세스가 비정상적으로 종료

 프로세스 ID

 - WIFSIGNALED(statloc) 매크로가 true를 반환

 - 비정상 종료 이유를 WTERMSIG(statloc) 매크로를 사용하여 구할 수 있음

 wait 함수 오류  -1

 - ECHILD : 호출자의 자식 프로세스가 없는 경우

 - EINTR : 시스템 콜이 인터럽트 되었을 때


예제를 통하여 살펴 보도록 하겠습니다.


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

int main() {
    
    pid_t childPid;
    int status,i;
    
    childPid = fork();
    
    if(childPid > 0) {  // 부모 프로세스
        pid_t waitPid;
        printf("부모 PID : %ld, pid : %d %d \n",(long)getpid(), childPid, errno);
        
        for(i=0;i<5;i++) {
            sleep(1);
        }
        
        waitPid = wait(&status);
        
        if(waitPid == -1) {
            printf("에러 넘버 : %d \n",errno);
            perror("wait 함수 오류 반환");
        }
        else {
            if(WIFEXITED(status)) {
                printf("wait : 자식 프로세스 정상 종료 %d\n",WEXITSTATUS(status));
            }
            else if(WIFSIGNALED(status)) {
                printf("wait : 자식 프로세스 비정상 종료 %d\n",WTERMSIG(status));
            }
        }
        
        printf("부모 종료 %d %d\n",waitPid,WTERMSIG(status));
    }
    else if(childPid == 0){  // 자식 프로세스
        printf("자식 PID : %ld \n",(long)getpid());

        printf("자식 종료\n");
        exit(0);
    }
    else {  // fork 실패
        perror("fork Fail! \n");
        return -1;
    }
    
    return 0;
}


fork를 한 후에, 부모 프로세스는 5초간 sleep을 하였기 때문에 wait 함수를 호출하기 전에 자식 프로세스는 종료된 상태입니다.

따라서  wait 함수를 호출하면 자식 프로세스의 상태를 즉시 반환 받을 수 있습니다.


실행 결과를 보겠습니다.

자식 프로세스는 정상 종료 되었으며 WEXISTSTATUS(status) 매크로를 사용하여 자식 프로세스가 반환한 0값을 얻어왔습니다.

부모 PID : 34989, pid : 34999 0 

자식 PID : 34999 

자식 종료

wait : 자식 프로세스 정상 종료 0

부모 종료 34999 0


이번에는 자식 프로세스에 sleep 함수로 지연을 발생시켜서 부모 프로세스가 wait() 함수를 호출하였을 때 자식 프로세스가 종료될 때까지 대기하도록 만들어 보겠습니다.


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

int main() {
    
    pid_t childPid;
    int status,i;
    
    childPid = fork();
    
    if(childPid > 0) {  // 부모 프로세스
        pid_t waitPid;
        printf("부모 PID : %ld, pid : %d %d \n",(long)getpid(), childPid, errno);
        
        waitPid = wait(&status);
        
        if(waitPid == -1) {
            printf("에러 넘버 : %d \n",errno);
            perror("wait 함수 오류 반환");
        }
        else {
            if(WIFEXITED(status)) {
                printf("wait : 자식 프로세스 정상 종료 %d\n",WEXITSTATUS(status));
            }
            else if(WIFSIGNALED(status)) {
                printf("wait : 자식 프로세스 비정상 종료 %d\n",WTERMSIG(status));
            }
        }
        
        printf("부모 종료 %d %d\n",waitPid,WTERMSIG(status));
    }
    else if(childPid == 0){  // 자식 코드
        printf("자식 PID : %ld \n",(long)getpid());
        
        for(i=0;i<5;i++) {
            sleep(1);
        }
        
        printf("자식 종료\n");
        exit(0);
    }
    else {  // fork 실패
        perror("fork Fail! \n");
        return -1;
    }
    
    return 0;
}


자식 프로세스가 5초 동안 동작하는 동안 부모 프로세스는 wait() 함수를 호출하여 자식 프로세스의 종료 상태를 얻기 위해 대기합니다.

실행 결과를 살펴 보도록 하겠습니다.

기대했던 것과 다르게 wait함수는 -1를 반환하였습니다. 에러의 원인을 찾기 위해 errno 출력과 perror 함수를 이용하였습니다.

부모 PID : 35650, pid : 35660 0 

자식 PID : 35660 

자식 종료

에러 넘버 : 4 

wait 함수 오류 반환: Interrupted system call

부모 종료 -1 126 

"Interrupted system call" 에러 문구가 wait 함수가 성공하지 못한 이유를 알려주고 있습니다.

Advanced Programming in the UNIX Environment 도서에 10.5 절에 나와 있듯이 유닉스 계열의 시스템에서는 '느린' 시스템 호출에 의해 차단되어 있는 도중에 신호를 잡으며 그 시스템호출이 가로채입니다. 이런 경우 시스템 호출 함수는 errno를 EINTR로 설정하고 오류를 반환합니다.

wait() 함수는 시스템 콜 함수이며  '느린' 시스템 콜 함수에 속합니다.


이 문제를 해결하기 위해서는 오류 반환을 검사하여 오류가 발생한 시스템 콜 함수를 다시 호출하는 방법을 사용합니다.

"Interrupted system call" 오류가 발생하더라도 wait함수를 재실행할 수 있도록 아래와 같이 코드를 수정합니다.

while문을 사용하여 wait 함수가 -1 값을 반환하고 errno 값이 EINTR일 경우에 wait함수를 재실행하도록 코드를 추가하였습니다.

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

int main() {
    
    pid_t childPid;
    int status,i;
    
    childPid = fork();
    
    if(childPid > 0) {  // 부모 프로세스
        pid_t waitPid;
        printf("부모 PID : %ld, pid : %d %d \n",(long)getpid(), childPid, errno);
        
        while((((waitPid = wait(&status)) == -1) && errno == EINTR));  // 에러 발생시 명시적으로 처리하고 wait 함수를 재호출함
        
        if(waitPid == -1) {
            printf("에러 넘버 : %d \n",errno);
            perror("wait 함수 오류 반환");
        }
        else {
            if(WIFEXITED(status)) {
                printf("wait : 자식 프로세스 정상 종료 %d\n",WEXITSTATUS(status));
            }
            else if(WIFSIGNALED(status)) {
                printf("wait : 자식 프로세스 비정상 종료 %d\n",WTERMSIG(status));
            }
        }
        
        printf("부모 종료 %d %d\n",waitPid,WTERMSIG(status));
    }
    else if(childPid == 0){  // 자식 코드
        printf("자식 PID : %ld \n",(long)getpid());
        
        for(i=0;i<5;i++) {
            sleep(1);
        }
        
        printf("자식 종료\n");
        exit(0);
    }
    else {  // fork 실패
        perror("fork Fail! \n");
        return -1;
    }
    
    return 0;
}


실행 결과를 보도록 하겠습니다.

부모 프로세스는 자식 프로세스가 5초 동안 실행되는 동안  wait 함수로 대기하였다가 자식 프로세스가 종료되는 순간 정상적으로 종료 상태를 얻어 왔습니다.

즉 자식 프로세스가 반환한 값 0을 얻은것을 확인 할 수 있습니다.

부모 PID : 37460, pid : 37470 0 

자식 PID : 37470 

자식 종료

wait : 자식 프로세스 정상 종료 0

부모 종료 37470 0 


마지막으로 자식 프로세스를 kill 명령어로 종료시켜 보도록 하겠습니다.

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

int main() {
    
    pid_t childPid;
    int status,i;
    
    childPid = fork();
    
    if(childPid > 0) {  // 부모 프로세스
        pid_t waitPid;
        printf("부모 PID : %ld, pid : %d %d \n",(long)getpid(), childPid, errno);
        
        while((((waitPid = wait(&status)) == -1) && errno == EINTR));
        
        if(waitPid == -1) {
            printf("에러 넘버 : %d \n",errno);
            perror("wait 함수 오류 반환");
        }
        else {
            if(WIFEXITED(status)) {
                printf("wait : 자식 프로세스 정상 종료 %d\n",WEXITSTATUS(status));
            }
            else if(WIFSIGNALED(status)) {
                printf("wait : 자식 프로세스 비정상 종료 %d\n",WTERMSIG(status));
            }
        }
        
        printf("부모 종료 %d %d\n",waitPid,WTERMSIG(status));
    }
    else if(childPid == 0){  // 자식 코드
        printf("자식 PID : %ld \n",(long)getpid());
        
        for(i=0;i<100;i++) { // 충분히 긴 시간 동안 자식 프로세스를 동작 시키놓고 kill 명령어로 종료시켜 봅니다
            sleep(1);
        }
        
        printf("자식 종료\n");
        exit(0);
    }
    else {  // fork 실패
        perror("fork Fail! \n");
        return -1;
    }
    
    return 0;
}


예제 코드를 실행 한 후,

자식 프로세스는 100초 동안 동작을 할 것입니다.

그리고 부모 프로세스는 wait() 함수를 통해 자식 프로세스의 종료 상태를 얻기위해 대기 중입니다.

부모 PID : 40272, pid : 40275 0 

자식 PID : 40275  


100초가 지나기 전에 터미널에서 아래와 같이 명령어를 입력하도록 하겠습니다.

40275는 자식 프로세스의 프로세스 ID입니다.

kill -9 40275 

위 명령어를 입력하는 부모 프로세스에서 호출한 wait()함수는 반환되면서 아래의 정보를 출력하게 됩니다.

부모 PID : 40272, pid : 40275 0 

자식 PID : 40275 

wait : 자식 프로세스 비정상 종료 9

부모 종료 40275 9 

자식 프로세스는 동작중에 kill 명령어에 의해서 비정상 종료 되었고 시그널 값은 9을 전달 받았습니다.


지금까지 wait함수를 사용하여 부모 프로세스가 자식 프로세스의 종료 상태(정상 또는 비정상)를 얻는 과정을 살펴 보았습니다.

그리고 시스템 콜 함수를 사용할 때 발생하는 오류에 대해서도 알아 보았습니다.

  1. ZombieS 2018.07.05 22:18

    안녕하세요.

    좀비 프로세스에 대해서 알아가다 위의 예제를 따라 해봤습니다.

    시스템 콜 인터럽트 오류가 나는 wait 콜보다 자식이 먼저 죽는 경우를 실행해 봤는데요.

    제가 지금 커널 4.15.0 버전을 사용하고 있는데 정상적으로 프로세스가 처리됩니다. 흐규

    혹시 커널의 버전에 따라 결과가 달라질 수 있나요?

일반적으로 프로세스를 종료할 때 return문을 사용합니다. 그리고 exit()함수를 사용하기도 합니다.

return 문과 exit() 함수를 사용하는 것은 시스템 입장에서 보았을 때 동일합니다.


#include <stdlib.h>

void exit(int status); 


int main()
{
    printf("This is test code\n");

    return 0;  // exit(0);과 동일
}


exit() 함수 외에도 _exit() 함수와 _Exit() 함수가 있습니다. 이 둘 함수가 exit() 함수와 다른점은 exit()함수는 atexit() 함수로 등록한 종료 핸들러가 있다면 이 핸들러를 모두 처리하고, 표준 입출력 스트림을 닫는 작업을 수행한 후 커널의 종료 작업을 실행하지만 _exit(), _Exit() 함수는 바로 커널에서 종료작업을 실행한다는 것입니다.

커널 종료 작업이라고 하면 프로세스가 사용하던 메모리를 해제하고 열어놓았던 파일의 descriptor등 을 닫는 작업 등 을 말합니다.

유닉스 계열 시스템에서 _exit() 함수와 _Exit() 함수는 같은 함수라고 생각해도 좋습니다.


#include <stdlib.h>

void _Exit(int status);

#include <unistd.h>

void _exit(int status); 


return문을 사용하던 exit(), _exit(), _Exit() 함수를 사용하던 프로세스의 종료 상태를 함수의 인자로 지정하게 됩니다. 

이 인자는 종료 상태를 부모 프로세스에게 알려줄 수 있는 값입니다.


아래 예제에서 사용된 exit() 함수에 들어 있는 인자 -1과 0 값은 부모 프로세스에게 종료 상태를 알려줄 수 있는 값이 됩니다.

부모 프로세스는 자식 프로세스의 종료 상태 값을 얻어서 자식 프로세스가 어떤 상태로 종료 되었는지를 알 수 있는 것입니다. 

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

float divide(int a, int b)
{
    if(b == 0) {  
        return -1; // 0으로 나누는 것은 에러!
    }
    
    return a/b;
}

int main()
{
    float ret = 0;
    
    ret = divide(10,5);
    if(ret == -1) { 
        printf("Error!\n");
        exit(-1); // 0으로 나누기 시도
    }
    
    printf("ret : = %f\n", ret);
    
    ret = divide(10,0);
    if(ret == -1) {  
        printf("Error!\n");
        exit(-1); // 0으로 나누기 시도
    }
    
    printf("ret : = %f\n", ret);

    exit(0);
}


하지만 자식 프로세스가 비정상적으로 종료되었을 경우에는 커널에서 비정상 종료 상태를 별도로 설정하게 됩니다.

부모 프로세스는 자식의 종료 상태(정상/비정상)를 wait() 함수 또는 waitpid() 함수로 얻을 수 있습니다.


wait() 함수 포스팅에서 자식 프로세스가 정상/비정상적으로 종료되었을 때 부모 프로세스가 어떻게 상태를 얻어 오는지 좀더 자세히 살펴 보도록 하겠습니다.


프로세스를 생성하고자 할 때 fork 함수를 사용하면 됩니다.

fork 함수를 호출하는 프로세스는 부모 프로세스가 되고 새롭게 생성되는 프로세스는 자식 프로세스가 됩니다.

fork 함수에 의해 생성된 자식 프로세스는 부모 프로세스의 메모리를 그대로 복사하여 가지게 됩니다.

그리고 fork 함수 호출 이후 코드부터 각자의 메모리를 사용하여 실행됩니다


fork 함수를 사용하기 위해서는 unistd.h를 include하면 됩니다.

fork 함수는 unistd.h 파일에 system call로 정의되어 있습니다.

// unistd.h header file

pid_t fork(void); // 성공 시 : 부모 프로세스에서는 자식 프로세스의 PID값을 반환 받음

// 자식 프로세스에서는 0 값을 반환 받음

// 실패 시 : 음수 값(-1) 반환


간단한 예제 코드를 살펴 보겠습니다.

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

int main() {
    int x;
    x = 0;
    
    fork();
    
    x = 1;
    printf("PID : %ld,  x : %d\n",getpid(), x);
    
    return 0;
}


실행 결과는 다음과 같습니다.

fork 함수 코드 이후부터는 부모 프로세스와 자식 프로세스가 각자의 x = 1, printf() 코드를 실행하였습니다.

그렇기 때문에 PID 43889(부모 프로세스)에서 x 값은 1을 출력하였고 PID 43895(자식 프로세스)에서도 x 값은 1이라고 출력하였습니다.

PID : 43889,  x : 1

PID : 43895,  x : 1 


그림을 통하여 다시 한번 보도록 하겠습니다.

fork 함수가 실행 된 직후에는 자식 프로세스 부모 프로레스와 동일한 주소 공간의 복사본을 가지게 됩니다.

fork 함수 실행 이후 , 부모와 자식 프로세스는 동일한 코드 (x = 1, printf())를 각자 메모리상에서 실행하고 있습니다.


이번에는 fork 함수의 리턴값을 검사하여 부모와 자식 코드를 각각 분리하도록 하겠습니다.

fork 함수는 부모 프로세스에게는 자식프로세스의 PID를 반환하며 자식 프로세스에게는 0을 반환합니다.

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

int main() {
    
    pid_t pid;
    
    int x;
    x = 0;
    
    pid = fork();
    
    if(pid > 0) {  // 부모 코드
        x = 1;
        printf("부모 PID : %ld,  x : %d , pid : %d\n",(long)getpid(), x, pid);
    }
    else if(pid == 0){  // 자식 코드
        x = 2;
        printf("자식 PID : %ld,  x : %d\n",(long)getpid(), x);
    }
    else {  // fork 실패
        printf("fork Fail! \n");
        return -1;
    }
    
    return 0;

}


실행 결과는 다음과 같습니다. 

pid 반환값을 검사하여 부모 프로세스와 자식 프로세스에서 실행될 코드를 별도로 작성할 수 있는것을 확인 하였습니다.

부모 PID : 46834,  x : 1 , pid : 46838

자식 PID : 46838,  x : 2 


fork 함수가 실행 된 직후에는 자식 프로세스 부모 프로레스와 동일한 주소 공간의 복사본을 가지게 됩니다.

하지만 fork 함수 이후의 코드가 pid값을 기준으로 분리되어 있습니다.

따라서 각 프로세스의 메모리 공간의 x값은 서로 달라지게 됩니다. 


지금까지 fork 함수 사용에 대해서 살펴 보았습니다.

  1. E.asiest 2019.09.04 22:56 신고

    안녕하세요!
    블로그 설명 덕분에 이해가 잘 갑니다! 감사합니다~
    실습을 해봤는데요!(vmware kali)
    왜 저는 OXOX 이렇게 랜덤으로 안나오고
    OOOOOOOOOOOOOOOOOOOOXXXXXXXXXXXXXXXXXXXXXXXXX이렇게 나뉘어서 나옵니다!!
    반복문을 1000번으로 바꾸어도 그러는데.. 무슨 이유가 있을까요??

    • 여행자를꿈꾸며 2019.09.05 17:46 신고

      안녕하세요
      글이 조금이라도 도움이되었다니 다행입니다.
      질문하신 부분에 대한 실습 코드를 볼 수 있다면 답변에 도움이 될것 같습니다.

  2. E.asiest 2019.09.05 22:43 신고

    https://easy7.tistory.com/437 제 기록용 블로그입니다!
    여기서 2. 경쟁조건 해보기 보시면
    제 오류를 보실수 있습니다 ㅠㅠ

    • 여행자를꿈꾸며 2019.09.05 23:48 신고

      기대하는 결과가 나오지 않은 이유는 스케줄링과 관계가 있어 보입니다.

      OS는 설정된 스케줄링 정책에 따라서 각 프로세스를 번갈아 가면서 일정 시간동안 조금씩 실행할 것입니다.

      실습코드를 보면 부모 프로세스와 자식 프로세스가 각각 하는 일은 for문 100번 돌면서 0과 X를 출력하는 것입니다. 이정도는 스케줄링 한싸이클안에 끝낼 수 있는 양으로 생각됩니다.

      따라서 아래 2가지 방법은 테스트를 해볼 수 있습니다.
      1. 부모와 자식 프로세스의 for문을 1000000 횟수만큼 돌려보세요 아마도 0과 X가 번갈아 출력될 것입니다.(혹시 결과가 같다면 반복문 횟수를 더 늘려보세요)

      2. 부모와 자식 프로세스에서 sleep(1) 함수를 각각 추가해 보세요 그럼 기대하는 결과를 볼 수 있을 것입니다. 프로세스가 1초 동안 쉬는 동안 스케줄링 정책에 의해 다른 프로세스가 CPU를 점유하게 되기 때문입니다.

      부모
      for(a=0;a<100; a++) {
      if(a%2 == 0) printf("0\n");
      sleep(1);
      }

      자식
      for(b =0;b<100;b++) {
      if(b%2 == 1) printf("X\n");
      sleep(1);
      }

  3. E.asiest 2019.09.09 00:24 신고

    반복문을 백만번 돌리니까 됩니다!! 정말 감사드립니다ㅎㅎ
    cpu 스케줄링의 성능이 이렇게 좋은줄 처음 알았네요ㅎㅎ



    2번째 해결책도 해보았는데요(반복문 100번, sleep(1) 추가)
    출력값이 하나씩 나오지 않고 1분 가량 후에 한번에 출력되었습니다!
    물론 OOOOOOOOOOXXXXXXXXXXXX 프로세스 경쟁하는 것은 보지 못했습니다.! 100번은 정말 한 사이클로 가능한 것 같습니다!
    10000번 sleep(1)으로 돌려봤는데.. 시간이 너무 걸려서 포기했습니다

    친절한 답변 감사드립니다!

    • 여행자를꿈꾸며 2019.09.10 20:02 신고

      2번 테스트 코드를 작성하실때,
      printf("X \n");
      printf("O \n");
      위의 printf에서 개행문자가 보이지는 않지만 아무튼 "역슬래쉬 + n"를 넣어주어야 1초에 한번씩 O, X가 번갈아 출력될 것입니다.

      그렇게 하지 않았을 때 마지막에 한꺼번에 OOOOOOXXXXXXX 가 출력되는 것은 부모와 자식 각 프로세스가 종료되면서 각 프로세스의 stdout 버퍼에 쌓여있던 것들을 flush 하고 종료되었기 때문인것 같습니다

  4. E.asiest 2019.09.10 21:12 신고

    제가 그렇습니다... 옮겨쓰면서 개행문자를 경시했어요 ㅠㅠㅠ 개행문자가 이렇게 중요할줄은 몰랐어요!!!
    개행문자 有 + 반복문 100개 -> O사이에 X하나 나왔어요!

    개행문자 有 + 반복문 100개 + sleep(1) -> 너무 착하게 O하나 X하나 O하나, X하나 잘 나옵니다!!!!!

    정말 감사합니다 고수님 ㅠㅠㅠㅠ
    궁금증이 완전 시원하게 풀렸습니다!!!!!
    종종 들러서 추천하고 가겠습니다!!

+ Recent posts