부모 프로세스가 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 버전을 사용하고 있는데 정상적으로 프로세스가 처리됩니다. 흐규

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

+ Recent posts