Linux - fork(), waitpid()에 timeout 적용하는 방법 및 예제

JS · 05 Jan 2020

Linux C++ 프로그래밍에서 fork(), waitpid() 예제를 소개합니다. waitpid()를 호출하면 child process가 종료될 때까지 block되는데, Timeout을 적용하여 Child의 응답이 없을 때 기다리지 않는 예제도 소개하려고 합니다.

Linux system을 깊이 알지 못하기 때문에 잘못된 부분이 있을 수 있습니다.

fork()

fork()는 Parent의 프로세스를 복제하여 Child process를 생성하는 API입니다.

fork()를 사용하는 간단한 예제입니다.

#include <iostream>
#include <unistd.h>

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

    if (pid < 0) {
        fprintf(stderr, "Fork failed!\n");
        exit(-1);
    }

    if (pid == 0) {
        // child process
        printf("(%ld) I am the child, pid: %d, getpid(): %d\n",
                time(0), pid, getpid());
        sleep(2);
        printf("(%ld) Done from child\n", time(0));
        exit(0);
    } else {
        // parent process
        printf("(%ld) I am the parent, pid=%d\n", time(0), pid);
        sleep(4);
        printf("(%ld) Done from parent\n", time(0));
        exit(0);
    }
}

실행 결과입니다. 로그 맨 앞은 시스템 시간이며 단위가 초 입니다.

(1578224091) I am the parent, pid=24139
(1578224091) I am the child, pid: 0, getpid(): 24139
(1578224093) Done from child
(1578224095) Done from parent

fork()가 성공하면 parent process는 child의 pid가 리턴하며, child process는 0이 리턴됩니다. 메모리 부족 등으로 fork()가 실패하면 -1이 리턴됩니다.

waitpid()

waitpid()는 parent가 child가 종료되었는지 기다릴 때 사용합니다.

다음은 parent가 waitpid()로 child process가 종료될 때까지 기다리는 예제입니다.

#include <iostream>
#include <unistd.h>
#include <sys/wait.h>

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

    if (pid < 0) {
        fprintf(stderr, "Fork failed!\n");
        exit(-1);
    }

    if (pid == 0) {
        // child process
        printf("(%ld) I am the child, pid: %d, getpid(): %d\n",
                time(0), pid, getpid());
        sleep(2);
        printf("(%ld) Done from child\n", time(0));
        exit(0);
    } else {
        // parent process
        printf("(%ld) I am the parent, pid=%d\n", time(0), pid);

        int state;
        pid_t got_pid = waitpid(pid, &state, 0);  // 1

        printf("(%ld) got_pid=%d\n", time(0), got_pid);   // 2
        printf("(%ld) WIFEXITED: %d\n", time(0), WIFEXITED(state));  // 3
        printf("(%ld) WEXITSTATUS: %d\n", time(0), WEXITSTATUS(state)); // 4
        printf("(%ld) Done from parent\n", time(0));
        exit(0);
    }
}
  1. waitpid()의 인자로, 기다리는 child의 pid와, 리턴받을 상태, option을 전달합니다. child 프로세스의 상태가 변경되면(꼭 종료가 아니더라도) waitpid()는 리턴됩니다.
  2. 정상적으로 리턴되면 child의 pid가 리턴됩니다. 그렇지 않으면 -1이 리턴됩니다.
  3. WIFEXITED는 state에서 child가 종료되었는지를 확인하는 매크로입니다. 종료되었다면 true를 리턴합니다.
  4. WEXITSTATUS는 exit status를 리턴합니다.

결과

(1578224731) I am the parent, pid=25194
(1578224731) I am the child, pid: 0, getpid(): 25194
(1578224733) Done from child
(1578224733) got_pid=25194
(1578224733) WIFEXITED: 1
(1578224733) WEXITSTATUS: 0
(1578224733) Done from parent

waitpid의 status 및 option에 대한 자세한 내용은 man이나 waitpid를 참고해주세요.

waitpid()에 Timeout 적용 (1)

Signal을 받아 child가 종료되었는지 확인 후, waitpid()를 호출하는 방법이 있습니다.

다음 코드는 linuxprogrammingblog에서 소개된 코드를 보기 좋게 고쳤습니다.

#include <iostream>
#include <unistd.h>
#include <sys/wait.h>

static pid_t fork_child() {
    int p = fork ();
    if (p == -1) {
        exit (1);
    }

    if (p == 0) {
        printf ("(%ld) child: sleeping...\n", time(0));
        sleep (10);
        printf ("(%ld) child: exiting\n", time(0));
        exit (0);
    }

    return p;
}

int main() {
    sigset_t mask;
    sigset_t orig_mask;
    struct timespec timeout;
    timeout.tv_sec = 5;
    timeout.tv_nsec = 0;

    sigemptyset (&mask);
    sigaddset (&mask, SIGCHLD);
    if (sigprocmask(SIG_BLOCK, &mask, &orig_mask) < 0) {
        perror ("sigprocmask");
        return 1;
    }

    pid_t pid = fork_child();
    do {
        if (sigtimedwait(&mask, NULL, &timeout) < 0) {
            if (errno == EINTR) {
                // Interrupted by a signal other than SIGCHLD.
                continue;
            } else if (errno == EAGAIN) {
                printf ("(%ld) Timeout, killing child\n", time(0));
                kill (pid, SIGKILL);
            } else {
                printf ("(%ld) sigtimedwait\n", time(0));
                return 1;
            }
        }
        break;
    } while (1);

    int state;
    if (waitpid(pid, &state, 0) < 0) {
        printf("(%ld) WIFEXITED: %d\n", time(0), WIFEXITED(state));
        printf("(%ld) WEXITSTATUS: %d\n", time(0), WEXITSTATUS(state));
        printf("(%ld) Done from parent\n", time(0));
        return 1;
    }
}

결과

(1578225826) child: sleeping...
(1578225831) Timeout, killing child

waitpid()에 Timeout 적용 (2)

Alarm을 이용하여 Timeout이 되면 handler에서 child process를 kill하는 방법입니다.

(AweSome Life에서 소개된 코드를 약간 수정하였습니다.)

#include <iostream>
#include <unistd.h>
#include <sys/wait.h>

int child_pid = 0;
void timer_handler(int pid) {
  printf("(%ld) time is over, child will be killed\n", time(0));
  kill(child_pid, SIGKILL);
}

int main() {
    signal(SIGALRM, timer_handler);

    pid_t pid = fork();
    child_pid = pid;

    if (pid == 0) { // child process
        printf("(%ld) I am the child, return from fork=%d\n",
                time(0), pid);
        sleep(20);
        printf("(%ld) Done from child\n", time(0));
    } else { // parent process
        printf("(%ld) I am the parent, child pid=%d\n",
                time(0), pid);

        alarm(5);

        int state;
        waitpid(pid, &state, 0);
        printf("(%ld) WIFEXITED: %d\n", time(0), WIFEXITED(state));
        printf("(%ld) WEXITSTATUS: %d\n", time(0), WEXITSTATUS(state));
        printf("(%ld) Done from parent\n", time(0));
        exit(0);
    }
}

결과

(1578226328) I am the parent, child pid=27412
(1578226328) I am the child, return from fork=0
(1578226333) time is over, child will be killed
(1578226333) WIFEXITED: 0
(1578226333) WEXITSTATUS: 0
(1578226333) Done from parent

참고