본문 바로가기
Study/System Programming

wait, waitpid의 사용

by 개발새-발 2021. 10. 19.
반응형

Parent process는 wait, waitpid를 사용하여 자신이 생성한 child process들이 종료되기 전까지 기다려 줄 수 있다. 또, 자식들이 어떻게 종료되었는지와 child process가 종료 할 때의 반환한 값도 알 수 있다.

wait

wait의 사용법을 먼저 알아보자. wait는 아래와 같은 모습으로 사용할 수 있다.

  • wait(int* status)
    • 인자
      • status : 종료 상태를 담을 포인터이다. 받을 필요가 없다면 0을 입력한다.
    • 반환 값
      • pid : 성공시 종료된 child process의 pid 값
      • -1 : 실패시 -1

wait는 child process가 종료될 때까지 기다렸다가 child process가 종료되면 종료된 child process의 값을 반환한다. 만약 실패하는 경우 -1을 반환한다. 실패하는 경우 중에서 더 이상 기다릴 자식 프로세스가 없어서 -1을 반환하는 경우 errnoECHILD가 된다. status에는 child process가 어떤 방식으로 종료되었는지가 담겨있다. child process가 exit, _exit, _Exit 혹은 메인에서 return을 통해 종료하였는지 혹은 signal에 의해 종료되었는지를 알 수 있다. 이 뿐만 아니라 정상적으로 종료되었다면 exit code를, signal에 의해 종료되었다면 해당 signal을 알아낼 수 있다. 이는 wait와 같이 정의된 매크로를 통해서 쉽게 확인할 수 있다.

  • 관련 매크로
    • WIFEXITED(status) : exit, _exit, _Exit 혹은 main에서의 return으로 종료되었는지를 확인한다.
    • WEXITSTATUS(status) : WIFEXITED의 값이 참인 경우 exit code를 알아낸다.
    • WIFSIGNALED(status) : signal에 의하여 terminate 되었는지를 확인한다.
    • WTERMSIG(status) : WIFSIGNALED의 값이 참인 경우 어느 signal에 의하여 terminate 되었는지를 알아낸다.

wait 예제 1

아래 예시는 child process가 생성된 지 5초 후에 exit(0)으로 종료하는 예시이다. 또, parent process는 child process를 기다린다.

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

int main(void){

    if(fork()==0){
        printf("Child process started. pid = %d\n",getpid());
        sleep(5);
        printf("Child process end.\n");
        exit(0);    
    }

    pid_t pid = wait(0);

    printf("Waited child. pid = %d\n",pid);
    return 0;
}

위 예시를 직접 실행해보면 자식 프로세스가 종료되기 전까지 parent process의 wait 이후의 코드들이 실행되지 않는 것을 볼 수 있다. wait에서 child process가 종료하기를 기다리기 때문이다. 또한, child process가 종료된 후에 wait로부터 종료된 프로세스의 pid를 받는 것도 볼 수 있다.

$ ./wait
Child process started. pid = 4276
Child process end.
Waited child. pid = 4276

wait 예제 2

아래 예제는 기다릴 자식 프로세스가 존재하지 않아 wait가 -1을 반환하는 예시이다.

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

int main(void){
    pid_t pid = wait(0);
    printf("Got pid = %d, errno==ECHILD : %d\n",pid,errno==ECHILD);
    return 0;
}
$ ./wait
Got pid = -1, errno==ECHILD : 1

wait 예제 3

이제 wait의 status 인자를 활용할 것이다. status를 통해 child process가 어떤 방식으로 종료되었는지와 exit()을 통해 종료되었다면 exit code를, signal에 의해 종료되었다면 어느 signal에 의해 종료되었는지 알아보자.

아래 코드에서 child process는 생성 후 30초 뒤에 종료한다. parent process는 child process가 종료되기를 기다린다. 하지만 만약 30초 이내로 signal을 사용하여 child process를 terminate 하면 어떤 결과를 볼 수 있을까?

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

int main(void){

    if(fork() == 0){
        printf("child pid = %d\n",getpid());
        sleep(30);
        exit(0);
    }

    pid_t pid;
    int status = 0;
    while((pid = wait(&status)) > 0){
        printf("wait got state change of pid %d\n",pid);

        if(WIFEXITED(status)){
            printf("and it EXITED!\n");
            printf("Child exit status is %d\n",WEXITSTATUS(status));
        }

        if(WIFSIGNALED(status)){
            printf("and it SIGNALED!\n");
            printf("Child Got signal %d\n",WTERMSIG(status));
        }
    }
}

아래 실행 결과는 30초 동안 기다려 child process가 exit(0)으로 종료된 예시이다. WIFEXITED 값이 참이고 WEXITSTATUS로 자식 프로세스가 종료 시 반환한 값을 알아내는 모습을 볼 수 있다.

$ ./wait
child pid = 4454
wait got state change of pid 4454
and it EXITED!
Child exit status is 0

이번에 볼 실행 결과는 위 프로그램을 백그라운드로 돌리고, child process에게 kill 명령어를 사용하여 SIGTERM signal을 준 결과이다. signal에 의해 종료되었다는 것과 어느 signal에 의해 terminate가 이루어졌는지 확인이 가능하다는 것을 알 수 있다.

$ ./wait &
[1] 4456
$ child pid = 4457

$ kill -TERM 4457
$ wait got state change of pid 4457
and it SIGNALED!
Child Got signal 15

[1]+  Done                    ./wait

waitpid

이제 waitpid에 대하여 알아보자. wait와 달리 waitpid는 원하는 pid의 child process를 기다릴 수 있다. waitpid의 형태는 다음과 같다.

  • pid_t waitpid(pid_t pid, int *status, int options)
    • 인자
      • pid : 각각의 경우 waitpid에서의 처리가 다르다.
        • pid > 0 : pid 값이 동일한 child process를 기다린다.
        • pid == 0 : group pid (gpid)가 같은 프로세스를 기다린다.
        • pid == -1 : 임의의 child process를 기다린다.
        • pid < -1 : pid와 절댓값이 같은 group pid를 가진 자식 프로세스를 기다린다.
      • status : wait에서의 status처럼 waitpid에 의해 기다려진 프로세스의 정보를 담게 된다.
      • options : waitpid의 동작을 설정할 수 있다. 아래 값들을 참고하자. 설정하지 않을 경우 0이다.
        • WNOHANG : 기다리고자 pid의 프로세스에 대해 모두 종료되지 않았으나 기다리지 않고 waitpid 이후의 코드를 실행하고자 할 때 사용하는 옵션이다. 기다리지 않았을 경우 waitpid의 반환 값은 0이다.
        • WUNTRACED : Stop 된 proccess들에 대해서도 waitpid에서 처리를 한다.
        • WCONTINUED : Continue 된 process들에 대해서도 waitpid에서 처리를 한다.
    • 반환 값
      • pid > 0 : 성공할 때에 status가 받아진 프로세스의 pid를 반환된다.
      • 0 : WNOHANG 옵션이 적용되어 있을 때, 프로세스가 종료되거나 상태가 변하지 않아 status를 받을 수 없을 때 0을 반환한다.
      • -1 : 실패할 경우 -1을 반환한다.

wait가 종료된 child process들만 처리를 하였던 것에 비해 waitpidWUNTRACEDWCONTINUED 옵션을 통하여 child process가 Stop 하거나 다시 Continue 하는 경우도 처리를 한다. 그래서 이와 관련된 매크로도 사용할 수 있다.

  • 관련 매크로
    • WIFEXITED(status) : exit, _exit, _Exit 혹은 main에서의 return으로 종료되었는지를 확인한다.
    • WEXITSTATUS(status) : WIFEXITED의 값이 참이라면 exit code를 알아낸다.
    • WIFSIGNALED(status) : signal에 의하여 terminate 되었는지를 확인한다.
    • WTERMSIG(status) : WIFSIGNALED의 값이 참인 경우 어느 signal에 의하여 terminate 되었는지를 알아낸다.
    • WIFCONTINUED(status) : waitpid가 Stop되어있던 프로세스가 SIGCONT로 의하여 다시 실행된 경우를 처리한 것인지 확인한다.
    • WIFSTOPPED(status) : waitpid가 실행중 이던 process가 Stop 되어 발생한 경우를 처리한 것인지를 확인한다.
    • WSTOPSIG(status) : WIFSTOPPED가 참인 경우 어느 signal에 의하여 Stop 되었는지를 알아낸다.

waitpid 예제 1

waitpid는 원하는 pid의 process를 기다릴 수 있다. 아래 예제를 보자. 아래 예제에서는 child process를 3개 생성한다. 각각의 child process는 특정 시간 동안 실행하였다가 exit으로 종료한다. 이때, 배열의 i번째에 할당된 pid를 가진 프로세스는 n-i만큼 실행하다가 종료하게 된다. 즉, 배열의 앞부분에 자신의 pid 값이 존재한다면 뒤에 있는 프로세스보다 늦게 종료하게 된다. 이 경우 wait를 사용하였다면 가장 먼저 종료한 프로세스 먼저 wait에서 처리를 하지만, waitpid를 사용하여 늦게 종료된 프로세스를 먼저 처리할 수도 있다.

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

int main(void){
    const int N = 3;
    pid_t pids[N];

    for(int i=0;i<N;++i){
        if((pids[i]=fork())==0){
            printf("Child process pid = %d. Sleep for %d seconds soon.\n",getpid(),N-i);
            sleep(N-i);
            printf("pid %d woke up!\n",getpid());
            exit(0);
        }
    }

    for(int i=0;i<N;++i){
        pid_t p = waitpid(pids[i],0,0);
        printf("waitpid returned value %d\n",p); 
    }
}

아래 실행 결과에서 원하는 프로세스부터 처리 하는 것을 볼 수 있다.

$ ./wait
Child process pid = 1890. Sleep for 3 seconds soon.
Child process pid = 1891. Sleep for 2 seconds soon.
Child process pid = 1892. Sleep for 1 seconds soon.
pid 1892 woke up!
pid 1891 woke up!
pid 1890 woke up!
waitpid returned value 1890
waitpid returned value 1891
waitpid returned value 1892

waitpid 예제 2

예제 1과 거의 동일하다. waitpid의 첫 번째 인자가 -1이라는 것만 다르다. 이때 wait와 유사하게 동작한다. 먼저 종료되는 프로세스부터 reaping을 해주게 된다.

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

int main(void){
    const int N = 3;

    pid_t pids[N];

    for(int i=0;i<N;++i){
        if((pids[i]=fork())==0){
            printf("Child process pid = %d. Sleep for %d seconds soon.\n",getpid(),N-i);
            sleep(N-i);
            printf("pid %d woke up!\n",getpid());
            exit(0);
        }
    }

    for(int i=0;i<N;++i){
        pid_t p = waitpid(-1,0,0);
        printf("waitpid returned value %d\n",p); 
    }
}

아래 결과를 보면 어떤 자식 프로세스가 종료하자마자 waitpid가 값을 return 하는 것을 볼 수 있다.

$ ./wait
Child process pid = 1905. Sleep for 3 seconds soon.
Child process pid = 1906. Sleep for 2 seconds soon.
Child process pid = 1907. Sleep for 1 seconds soon.
pid 1907 woke up!
waitpid returned value 1907
pid 1906 woke up!
waitpid returned value 1906
pid 1905 woke up!
waitpid returned value 1905

waitpid 예제 3

WNOHANG 옵션을 사용해볼 것이다. 코드는 2번 예제와 유사하나 각 child process들이 sleep하는 시간이 3배가 되었다. 즉, 각 자식 프로세스들은 생성 후 9초, 6초 3초 후에 종료한다. 그리고 parent process에서는 1초에 한 번씩 waitpid를 호출한다. WNOHANG옵션이 설정되어있지 않았더라면 자식 프로세스가 종료하기 전까지 조건문을 벗어나지 않았을 것이다. 그러나 WNOHANG옵션을 사용하였기 때문에 child process가 종료되지 않았음에도 0을 반환하는 모습을 볼 수 있다.

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

int main(void){
    const int N = 3;

    pid_t pids[N];

    for(int i=0;i<N;++i){
        if((pids[i]=fork())==0){
            printf("Child process pid = %d. Sleep for %d seconds soon.\n",getpid(),3*(N-i));
            sleep(3*(N-i));
            printf("pid %d woke up!\n",getpid());
            exit(0);
        }
    }

    pid_t p = 0;
    while((p = waitpid(-1,0,WNOHANG))>-1){
        printf("waitpid returned value %d\n",p); 
        sleep(1);
    }
}
$ ./wait
waitpid returned value 0
Child process pid = 1984. Sleep for 9 seconds soon.
Child process pid = 1985. Sleep for 6 seconds soon.
Child process pid = 1986. Sleep for 3 seconds soon.
waitpid returned value 0
waitpid returned value 0
pid 1986 woke up!
waitpid returned value 1986
waitpid returned value 0
waitpid returned value 0
pid 1985 woke up!
waitpid returned value 1985
waitpid returned value 0
waitpid returned value 0
pid 1984 woke up!
waitpid returned value 1984

참고로 위 코드에서 WNOHANG 옵션을 제거했을 경우의 결과는 다음과 같다.

$ ./wait
Child process pid = 2022. Sleep for 9 seconds soon.
Child process pid = 2023. Sleep for 6 seconds soon.
Child process pid = 2024. Sleep for 3 seconds soon.
pid 2024 woke up!
waitpid returned value 2024
pid 2023 woke up!
waitpid returned value 2023
pid 2022 woke up!
waitpid returned value 2022

waitpid 예제 4

WUNTRACED 옵션과 WCONTINUED 옵션을 주어 자식 프로세스의 종료뿐만 아니라 Stop과 Continue도 waitpid에서 잡아내도록 해보자. 아래 코드에서 생성하는 child process는 생성 후 30초 후 exit()으로 종료한다. child process가 exit()으로 종료, signal에 의해 종료, 중간에 stop 되었다가 continue 되는 경우들에 대하여 살펴보자.

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

int main(void){

    if(fork() == 0){
        printf("child pid = %d\n",getpid());
        sleep(30);
        exit(0);
    }

    pid_t pid;
    int status = 0;
    while((pid = waitpid(-1,&status,WUNTRACED | WCONTINUED))>0){
         printf("wait got state change of pid %d\n",pid);

        if(WIFEXITED(status)){
            printf("and it EXITED!\n");
        }

        if(WIFSTOPPED(status)){
            printf("and it STOPPED! ( with signal %d)\n",WSTOPSIG(status));
        }

        if(WIFCONTINUED(status)){
            printf("and it CONTINUED!\n");
        }

        if(WIFSIGNALED(status)){
            printf("Child Got signal %d\n",WTERMSIG(status));
        }
    }
}

아래는 30초 후 exit()으로 종료된 결과이다.

$ ./wait1
child pid = 2138
wait got state change of pid 2138
and it EXITED!

아래는 백그라운드로 실행한 후 30초 이내에 kill 명령어로 child process에 SIGKILL signal을 보낸 결과이다.

$ ./wait &
[1] 2181
$ child pid = 2182

$ kill -9 2182
$ wait got state change of pid 2182
Child Got signal 9

[1]+  Done                    ./wait

아래는 백그라운드로 실행한 후 30초 이내에 child process을 kill 명령어로 SIGSTOP을 보내 정지시킨 후 SIGCONT를 보내 다시 실행시킨 모습이다. child process가 Stop 하고 SIGCONT에 의해 다시 실행될 때에도 waitpid가 이를 잡아내는 모습을 볼 수 있다.

$ ./wait &
[1] 2196
$ child pid = 2197

$ kill -SIGSTOP 2197
$ wait got state change of pid 2197
and it STOPPED! ( with signal 19)

$ kill -SIGCONT 2197
$ wait got state change of pid 2197
and it CONTINUED!

$ wait got state change of pid 2197
and it EXITED!

[1]+  Done                    ./wait
반응형

'Study > System Programming' 카테고리의 다른 글

lseek, pread, pwrite 사용하기  (0) 2021.11.17
open, read, write, close 사용하기  (0) 2021.11.15
fork 사용하기  (0) 2021.10.17
sigprocmask을 사용하여 signal block 하기  (0) 2021.10.16

댓글