본문 바로가기

프로그래밍(TA, AA)/C C++

[시스템프로그래밍] 프로세스 생성과 실행

유닉스에서 프로세스는 사용자가 명령행에서 직접 프로그램을 실행해 생성하는 경우도 있지만, 프로그램 안에서 다른 프로그램을 실행해 생성하는 경우도 있습니다. 이렇게 프로그램에서 다른 프로그램을 실행해 새로운 프로세스를 생성할 때는 system, fork, vfork 함수를 사용합니다. 


 기능

 함수원형

 프로그램 실행

 int system(const char *string);

 프로세스 생성

 pid_t fork(void);

 pid_t vfork(void);


유닉스 프로세스가 종료되면 해당 프로세스가 어떻게 종료되었는지를 나타내는 종료 상태를 저장합니다. 자식 프로세스는 부모 프로세스에 자신이 어떻게 종료되었는지를 알리는 종료 상태값을 리턴할 수 있습니다. 일반적으로 종료 상태값이 0이면 정상적으로 종료했음을 의미합니다. 종료 상태값이 0이 아니면 오류가 발생했음을 의미합니다. 프로그램을 종료할 때는 exit, atext, _exit 함수를 사용합니다.


 기능

 함수원형

 프로세스 종료

 void exit(int status);

 void _exit(int status);

 종료시 수행할 작업 지정

 int atexit(void (*func)(void));


함수명이 'exec'로 시작하는 exec 함수군이 있습니다. exec 함수군은 인자로 받은 다른 프로그램을 자신을 호출한 프로세스의 메모리에 덮어씁니다. 따라서 프로세스가 수행 중이던 기존 프로그램은 중지되어 없어지고 새로 덮어쓴 프로그램이 실행됩니다. exec 함수군은 fork 함수와 연결해 fork로 생성한 자식 프로세스가 새로운 프로그램을 실행하도록 할때도 사용합니다.


 기능

 함수원형

 프로세스 실행

 int execl(const char *path, const char *arg0, ..., const char *argn, (char *)0);

 int execv(const char *path, char *const argv[]);

 int execle(const char *path, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]);

 int execve(const char *path, char *const argv[], char *const envp[]);

 int execlp(const char *file, const char *arg0, ..., const char *argn, (char *)0);

 int execvp(const char *file, char *const argv[]);


fork 함수로 자식 프로세스를 생성하면 부모 프로세스와 자식 프로세스는 순서에 관계없이 실행되고, 먼저 실행을 마친 프로세스는 종료합니다. 이때 좀비 프로세스 같은 불안정 상태의 프로세스가 발생하는데, 이를 방지하려면 아래 함수를 사용해 부모 프로세스와 자식 프로세스를 동기화해야 합니다.


 기능

 함수원형

 임의의 자식 프로세스의

 상태값 구하기

 pid_t wait(int *stat_loc);

 특정 프로세스의 상태값 구하기

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



프로세스 생성


프로세스는 현재 실행 중인 프로그램을 의미합니다. 프로세스는 사용자가 명령행에서 직접 프로그램을 실행해 생성하는 경우도 있지만, 프로그램 안에서 다른 프로그램을 실행해 생성하는 경우도 있습니다. 예를 들어, 기능별로 별도의 프로그램으로 구성되어 있는 업무용  소프트웨어에서 보고서 작성을 위해 출력 프로그램을 실행할 수 있습니다. 또한 인터넷을 통해 서비스를 제공하도록 다른 서비스 프로그램을 동작시키려면 새로운 프로세스를 생성해야 합니다. 이렇게 새로운 프로세스를 생성할 때 system, fork, vfork 함수를 사용합니다.


system 함수는 프로그램 안에서 새로운 프로그램을 실행하는 가장 간단한 방법입니다. 그러나 system 함수는 명령을 실행하기 위해 쉘까지 동작시키므로 비효율적입니다. 따라서 system 함수를 남용하는 것은 바람직하지 않습니다.


프로그램 실행 : system(3)

#include <stdlib.h>

int system(const char *string);

system 함수는 기존 명령이나 실행 파일명을 인자로 받아 쉘에 전달합니다. 쉘은 내부적으로 새 프로세스를 생성해 인자로 받은 명령을 실행하고, 해당 명령의 실행이 끝날 때까지 기다렸다가 종료 상태를 리턴합니다.



유닉스에서 프로세스를 생성해 프로그램을 실행하는 대표적인 방법은 fork 함수를 사용하는 것입니다. fork 함수는 새로운 프로세스를 생성합니다. fork 함수가 생성한 새로운 프로세스를 자식 프로세스라고 합니다. 한편 fork 함수를 호출한 프로세스는 부모 프로세스가 됩니다. fork 함수가 리턴하면 부모 프로세스와 자식 프로세스가 동시에 동작하는데, 어느 프로세스가 먼저 실행될지는 알 수 없습니다. 유닉스 운영체제의 스케줄리에 따라 처리 순서가 달라집니다.



fork 함수의 특징

부모 프로세스에서 fork 함수를 호출하면 새로운 프로세스를 생성합니다. fork 함수로 생성한 자식 프로세스의 메모리 공간은 부모 프로세스의 메모리 공간을 그대로 복사해 만듭니다. fork 함수는 부모 프로세스에는 자식 프로세스의 PID를 리턴하고, 자식 프로세스에는 0을 리턴합니다.


자식 프로세스는 부모 프로세스의 메모리를 복사하는 것뿐만 아니라 부모로부터 다양한 속성을 상속받습니다. 자식 프로세스가 상속받는 대표적인 속성은 다음과 같습니다.


  • 실제 사용자 ID(RUID), 유효 사용자 ID(EUID), 실제 그룹 ID(RGID), 유효 그룹 ID(EGID)
  • 환경 변수
  • 열린 파일 기술자
  • 시그널 처리 설정
  • setuid, setgid 설정
  • 현재 작업 디렉토리
  • unmask 설정값
  • 사용 가능한 자원 제한


자식 프로세스는 부모 프로세스의 송석을 대부분 상속받지만, 부모 프로세스와 다른 점도 있습니다.


  • 자식 프로세스는 유일한 프로세스 ID를 갖는다.
  • 자식 프로세스는 부모 프로세스의 PPID와 다른 자신만의 PPID를 갖는다. 다시 말해, 자식 프로세스를 호출한 부모 프로세스가 자식 프로세스의 PPID로 설정된다.
  • 자식 프로세스는 부모 프로세스가 연 파일 기술자에 대한 사본을 갖고 있다. 따라서 부모 프로세스와 자식 프로세스가 같은 파일의 오프셋을 공유하고 있는 상태가 되므로 읽거나 쓸 때 주의해야 한다.
  • 자식 프로세스는 부모 프로세스가 설정한 프로세스 잠금, 파일 잠금, 기타 메모리 잠금 등은 상속하지 않는다.
  • 자식 프로세스의 tms 구조체 값은 0으로 초기화된다. 다시 말해, 프로세스 실행 시간을 측정하는 기준값이 새로 설정된다.



프로세스 생성: fork(2)

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

pid_t fork(void);

fork 함수는 인자를 받지 않습니다. fork 함수는 수행을 성공하면 부모 프로세스에는 자식 프로세스의 ID를, 자식 프로세스에는 0을 리턴합니다. 실패하면 -1을 리턴합니다.


fork 함수를 사용해 자식 프로세스를 생성하고 PID와 PPID를 출력해보겠습니다.


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

int main(void) {
    pid_t pid;

    switch(pid = fork()) {
        case -1 : /* fork failed */
            perror("fork");
            exit(1);
            break;
        case 0 : /* child process */
            printf("Child Process - MyPID:%d, My Parent's PID:%d\n",
                    (int)getpid(), (int)getppid());
            break;
        default: /* parent process */
            printf("Parent process - My PID:%d, My Parent's PID:%d, My Child's PDI:%d\n",
                    (int)getpid(), (int)getppid(), (int)pid);
            break;
    }

    printf("End of fork\n");

    return 0;
}

[root@dev-web unix_system]# ./ex6_2.out

Parent process - My PID:21084, My Parent's PID:8690, My Child's PDI:21085

End of fork

Child Process - MyPID:21085, My Parent's PID:21084

End of fork

  • 09행: fork 함수를 호출한다.
  • 10행~13행: 리턴값이 -1이면, 오류가 발생한 것이므로 10행의 case -1 절에서 처리하고 프로그램을 종료한다.
  • 14행~16행: 리턴값이 0이면 자식 프로세스에 리턴한 것이므로, 14행의 case 0 절에서 처리한다. 15행~16행은 자식 프로세스에서 수행되며, 자신의 PID와 부모 프로세스의 ID(PPID)를 검색해 출력합니다.
  • 24행: 자식 프로세스와 부모 프로세스가 각각 실행하므로 두 번 출력된다.
  • 실행 결과를 보면 자식 프로세스에서 출력한 부모 프로세스의 ID와 부모 프로세스에서 출력한 자신의 PID가 동일함을 알 수 있다. 또한 부모 프로세스는 fork 함수의 리턴값으로 자식 프로세스의 ID를 받는데, 이를 출력하면 자식 프로세스가 출력한 PID와 같음을 알 수 있다.


프로세스 생성: vfork(2)

vfork 함수도 fork 함수처럼 새로운 프로세스를 생성합니다. 그러나 fork 함수와 달리 부모 프로세스의 메모리 공간을 모두 복사하지는 않습니다. 이는 vfork 함수로 생성한 자식 프로세스는 exec 함수군을 사용해 새로운 작업을 수행할 때 효율적입니다. 그러나 vfork 함수는 표주는 아니므로 시스템에서 지원하는지 확인하고 사용해야 합니다.



프로세스 종료


유닉스는 프로세스가 종료되면 해당 프로세스가 어떻게 종료되었는지를 나타내는 종료 상태(exit status)를 저장합니다. 부모 프로세스는 저장된 종료 상태 정보를 사용해 자식 프로세스가 어떻게 종료되었는지 알 수 있습니다. 자식 프로세스는 부모 프로세스에 자신이 어떻게 종료되었는지 알리는 종료 상태값을 리턴할 수 있습니다. 일반적으로 종료 상태값이 0이면 프로세스가 정상적으로 종료했음을 의미합니다(종료 상태값 0으로 exit 함수를 호출했거나 main 함수에서 0을 리턴). 함수 종료 상태값이 0이 아니면 오류가 발생했음을 의미합니다. 보통 오류가 발생하면 1을 리턴합니다. 이 절에서는 프로그램을 종료할 때 사용하는 함수를 살펴보기로 하겠습니다.



프로세스 종료 함수


exit, atexit, _exit 함수는 프로세스 종료와 관련이 있습니다. exit 함수는 프로세스를 종료할 때 사용하는 기본 함수입니다. atexit 함수를 사용하면 프로세스를 종료할 때 수행할 작업을 지정할 수 있습니다. _exit 함수는 일반적으로 프로그램에서 직접 호출하지 않습니다.


프로그램 종료: exit(2)

#include <stdlib.h>

void exit(int status);

exit 함수는 프로세스를 종료시키고 부모 프로세스에 종료 상태값을 전달합니다. 이때 atexit 함수로 예약한 함수들을 지정된 순서와 역순으로 모두 실행합니다. 만일 atexit 함수로 예약한 함수가 수행 도중에 문제가 발생해 리턴하지 못할 경우 exit 함수의 나머지 과정도 수행되지 않습니다. exit 함수는 프로세스가 사용 중이던 모든 표준 입출력 스트림에 데이터가 남아 있으면 이를 모두 기록하고, 열려 있는 스트림을 모두 닫습니다. 그 다음 tmpfile 함수로 생성한 임시 파일을 모두 삭제하고, _exit 함수를 호출합니다. _exit 함수는 시스템 호출로, 프로세스가 사용하던 모든 자원을 반납합니다. 이는 exit 함수가 C 표준 함수기 때문에 시스템에 독립적인 기능만 수행하고, 시스템과 관련된 기능은 시스템 호출에서 처리하도록 해야하기 때문입니다.



프로그램 종료시 수행할 작업 예약 : atexit(3)

#include <stdlib.h>

int atexit(void (*func)(void));

atexit 함수는 프로세스가 종료할 때 수행할 기능을 예약합니다. atexit 함수는 인자로 함수의 포인터를 받습니다. atexit의 인자로 지정하는 함수는 인자와 리턴값이 없는 함수입니다. atexit 함수로 예약할 수 있는 함수의 개수는 여유 메모리에 따라 달라지며, sysconf 함수의 _SC_ATEXIT_MAX 항목으로 검색할 수 있습니다.


atexit 함수로 종료 시 수행할 함수 두 개를 예약한 후 exit 함수를 호출해보겠습니다.

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

void cleanup1(void) {
    printf("Cleanup 1 is called.\n");
}

void cleanup2(void) {
    printf("Cleanup 2 is called.\n");
}

void main(void) {
    atexit(cleanup1);
    atexit(cleanup2);

    exit(0);
}

[root@dev-web ch06]# ./ex6_3.out

Cleanup 2 is called.

Cleanup 1 is called.


  • 04행~10행: 프로세스 종료 시 수행할 작업을 지정한 함수들의 정의다. 여기서는 간단하게 출력문 하나뿐이지만 실제로는 프로세스를 종료할 때 반드시 수행해야 할 작업을 지정해야 한다. 예를 들어, 로그를 남기거나 관리자에게 메시지를 보내거나 데이터를 저장하는 등의 작업을 수행할 수 있다.
  • 13행~14행: atexit 함수를 사용해 종료 시 수행할 함수를 등록한다.
  • 16행: exit 함수로 프로세스를 종료한다.
  • 실행 결과를 보면 함수가 예약 순서와 반대로 호출 되었음을 알 수 있다.


프로세스 종료: _exit(2)

_exit 함수는 일반적으로 프로그램에서 직접 사용하지 않고 exit 함수에서 내부적으로 호출합니다. _exit 함수는 시스템 호출로, 프로세스를 종료할 때 다음과 같은 과정을 통해 시스템 관련 자원을 정리합니다.


(1) 모든 파일 기술자를 닫는다.

(2) 부모 프로세스에 종료 상태를 알린다.

(3) 자식 프로세스들에 SIGHUP 시그널을 보낸다.

(4) 부모 프로세스에 SIGCHLD 시그널을 보낸다.

(5) 프로세스 간 통신에 사용한 자원을 반납한다.



exec 함수군 활용


함수명이 exec로 시작하는 함수들이 있는데, 이들 함수를 exec 함수군이라고 합니다. exec 함수군을 사용해 명령이나 실행 파일을 실행할 수 있습니다. exec 함수군은 인자로 받은 다른 프로그램을 자신을 호출한 프로세스의 메모리에 덮어씁니다. 따라서 프로세스가 수행 중이던 기존 프로그램은 중지되어 없어지고 새로 덮어쓴 프로그램이 실행됩니다. exec 함수군을 호출한 프로세스 자체가 바뀌므로, exec 함수를 호출해 성공하면 리턴값이 없습니다. exec 함수군은 fork 함수와 연결해 fork로 생성한 자식 프로세스가 새로운 프로그램을 실행하도록 할 때 유용합니다. 이 절에서는 exec 함수군의 종류와 사용방법을 알아보겠습니다. 



exec 함수군의 함수 형태


exec 함수군은 path나 file에 지정한 명령이나 실행 파일을 실행합니다. 이때 arg나 envp로 시작하는 인자를 path나 file에 지정한 파일의 main 함수에 전달합니다. 각 함수별로 경로명까지 지정하거나 단순히 실행 파일명만 지정하는 등 차이가 있고, 인자를 전달하는 형태에도 차이가 있습니다. 각 함수별로 기능을 살펴보겠습니다.


  • execl: path에 지정한 경로명의 파일을 실행하며 arg0~argn을 인자로 전달한다. 관례적으로 arg0에는 실행 파일명을 지정한다. execl 함수의 마지막 인자로는 인자의 끝을 의미하는 NULL 포인터((char *)0)를 지정해야 합니다. path에 지정하는 경로명은 절대 경로나 상대 경로 모두 사용할 수 있습니다.
  • execv: path에 지정한 경로명에 있는 파일을 실행하며 argv를 인자로 전달한다. argv는 포인터 배열입니다. 이 배열의 마지막에는 NULL 문자열을 저장해야 한다.
  • execle: path에 지정한 경로명의 파일을 실행하며 arg0~argn과 envp를 인자로 전달한다. envp를 인자로 전달한다. envp에는 새로운 환경 변수를 설정할 수 있다. arg0~argn을 포인터로 지정하므로, 마지막 값은 NULL 포인터로 지정해야 한다. envp는 포인터 배열이다. 이 배열의 마지막에는 NULL 문자열을 저장해야 한다.
  • execlp: file에 지정한 파일을 실행하며 arg0~argn만 인자로 전달한다. 파일은 이 함수를 호출한 프로세스의 검색 경로에서 찾는다. arg0~argn은 포인터로 지정한다. execl 함수의 마지막 인자는 NULL 포인터로 지정해야 한다.
  • execvp: file에 지정한 파일을 실행하며 argv를 인자로 전달한다. argv는 포인터 배열이다. 이 배열의 마지막에는 NULL 문자열을 저장해야 한다.



exec 함수군과 fork 함수


앞서 fork 함수로 새로운 프로세스를 생성하는 방법을 배웠습니다. fork 함수로 생성한 자식 프로세스에서도 부모 프로세스와 같은 코드를 수행했습니다. 물론 case로 부모 프로세스와 자식 프로세스가 수행할 코드를 분리하기는 했지만 기본적으로 같은 프로그램입니다. 그러나 자식 프로세스에서 exec 함수군을 호출하면 자식 프로세스는 부모 프로세스로부터 복사한 프로그램과는 다른 명령이나 프로그램을 실행할 수 있습니다. 예를 들어, 쉘에서 어떤 명령이나 파일을 실행하면, 쉘은 fork 함수로 자식 프로세스를 만들고, 사용자가 입력한 명령이나 파일을 exec 함수군을 사용해 자식 프로세스에서 실행합니다. 이렇게 부모 프로세스와 자식 프로세스가 각기 다른 작업을 수행해야 할 때 fork와 exec 함수를 함께 사용해야 합니다.


fork 함수로 자식 프로세스를 만드록, exec 함수를 사용해 자식 프로세스는 다른 명령을 실행하도록 만들어 보겠습니다.

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

int main(void) {
    pid_t pid;

    switch(pid = fork()) {
        case -1: /* fork failed */
            perror("fork");
            exit(1);
            break;
        case 0: /* child process */
            printf("--> Child Process\n");
            if(execlp("ls", "ls", "-a", (char *)NULL) == -1) {
                perror("execlp");
                exit(1);
            }
            exit(0);
            break;
        default: /* parent process */
            printf("--> Parent process - My PID:%d\n", (int)getpid());
            break;
    }

    return 0;
}



프로세스 동기화


fork 함수로 자식 프로세스를 생성하면 부모 프로세스와 자식 프로세스는 순서에 관계없이 실행되고, 먼저 실행을 마친 프로세스는 종료합니다. 그러나 부모 프로세스와 자식 프로세스 사이의 종료 절차가 제대로 진행되지 않을 때가 있습니다. 이때 좀비 프로세스(zombie process)같은 불안정 상태의 프로세스가 발생하는데, 이를 방지하려면 부모 프로세스와 자식 프로세스를 동기화해야 합니다. 이 절에는 좀비 프로세스가 발생하는 경우를 살펴보고, 좀비 프로세스 발생을 방지하는 프로세스 동기화 방법에 대해 살펴보겠습니다.



좀비 프로세스


정상적인 프로세스 종료 과정이라면, 자식 프로세스가 종료를 위해 부모 프로세스에 종료 상태 정보를 보내고, 부모 프로세스는 이 정보를 받으면 프로세스 테이블에서 자식 프로세스를 삭제합니다. 그러나 자식 프로세스가 모든 자원을 반납했어도 부모 프로세스가 종료상태 정보를 얻어가지 않거나 자식 프로세스보다 먼저 종료하는 경우가 발생합니다.


실행을 종료한 후 자원을 반납한 자식 프로세스의 종료 상태 정보를 부모 프로세스가 얻어가지 않는 경우에는 좀비 프로세스가 발생합니다. 좀비 프로세스는 프로세스 테이블에만 존재합니다. 좀비 프로세스는 일반적인 방법으로 제거할 수 없으며, 부모 프로세스가 wait 관련 함수를 호출해야 사라집니다.


만일 자식 프로세스보다 부모 프로세스가 먼저 종료하면 자식 프로세스들은 고아 프로세스가 됩니다. 이들 고아 프로세스는 init(PID 1) 프로세스의 자식 프로세스로 등록됩니다.



프로세스 동기화


부모 프로세스와 자식 프로세스를 동기화하려면 부모 프로세스는 자식 프로세스가 종료할 때까지 기다려야 합니다. 자식 프로세스의 실행이 완전히 끝나기를 기다렸다가 종료 상태를 확인하는 함수는 wait, waitpid입니다.


프로세스 동기화: wait(3)

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *stat_loc);

wait 함수는 자식 프로세스가 종료할 때까지 부모 프로세스를 기다리게 합니다. 자식 프로세스의 종료 상태는 stat_loc에 지정한 주소에 저장됩니다. stat_loc에 NULL을 지정할 수도 있습니다. 만약 부모 프로세스가 wait 함수를 호출하기 전에 자식 프로세스가 종료하면 wait 함수는 즉시 리턴합니다. wait 함수의 리턴값은 자식 프로세스의 ID입니다. wait 함수의 리턴값이 -1이면 살아있는 자식 프로세스가 하나도 없다는 의미입니다.


fork 함수로 자식 프로세스를 생성하고, wait 함수를 사용해 자식 프로세스가 종료할 때까지 부모 프로세스를 기다리게 해보겠습니다.

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

int main(void){
    int status;
    pid_t pid;

    switch(pid = fork()) {
        case -1: /* fork failed */
            perror("fork");
            exit(1);
            break;
        case 0: /* child process */
            printf("--> Child Process\n");
            exit(2);
            break;
        default: /* parent process */
            while(wait(&status) != pid)
                continue;
            printf("--> Parent process\n");
            printf("Status: %d, %x\n", status, status);
            printf("Child process Exit Status:%d\n", status >> 8);
            break;
    }

    return 0;
}

[root@dev-web ch06]# ./ex6_8.out

--> Child Process

--> Parent process

Status: 512, 200

Child process Exit Status:2


  • 11행: fork 함수를 호출해 새로운 프로세스를 생성한다.
  • 16행~19행: 자식 프로세스는 출력을 하나 하고, 바로 exit 함수를 호출해 종료한다. 종료 상태값으로 2를 지정한다.
  • 21행~22행: 부모 프로세스가 수행되는 부분에 wait 함수를 추가한다. 이렇게 하면 자식 프로세스가 17행을 수행하고 종료할 때까지 부모 프로세스가 기다린다. 자식 프로세스가 보낸 종료 상태값은 status에 저장된다.
  • 23행~25행: 21행에서 자식 프로세스가 종료할 때까지 기다리므로, 항상 자식 프로세스의 결과가 먼저 출력되고 23행~25행의 결과가 출력됩니다.
  • 실행 결과를 보면 24행의 결과는 512(10진수), 0x0200(16진수)으로 출력됨을 알 수 있다. 자식 프로세스가 전달한 값은 부모 프로세스에 왼쪽으로 한바이트 이동해 전달된다. 이를 제대로 출력하려면 25행처럼 오른쪽으로 8비트 이동시켜야 한다. 25행 출력한 결과는 종료 상태값이 2가 된다.


특정 자식 프로세스와 동기화: waitpid(3)

#include <sys/types.h>
#include <sys/wait.h>

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

wait 함수는 자식 프로세스가 여러 개일 경우 아무 자식 프로세스나 종료하면 리턴합니다. 이와 달리 waitpid 함수는 특정 PID의 자식 프로세스가 종료하기를 기다립니다. waitpid함수는 pid로 지정한 자식 프로세스가 종료하기를 기다리며, 자식 프로세스의 종료 상태값을 stat_loc에 저장하고, options의 조건에 따라 리턴합니다.


첫 번째 인자인 pid에 지정할 수 있는 값과 그 의미는 다음과 같습니다.

  • -1보다 작은 경우: pid의 절댓값과 같은 프로세스 그룹 ID에 속한 자식 프로세스 중 임의의 프로세스의 상태값을 요청한다.
  • -1인 경우: wait 함수처럼 임의의 자식 프로세스의 상태값을 요청한다.
  • 0인 경우: 함수를 호출한 프로세스와 같은 프로세스 그룹에 속한 임의의 프로세스의 상태값을 요청한다.
  • 0보다 큰 경우: 지정한 pid에 해당하는 프로세스의 상태값을 요청한다.

세 번째 인자인 options에 지정할 수 있는 값은 <sys/wait.h>에 정의되어 있으며, OR 연산으로 연결해 지정할 수 있습니다.

  • WCONTINUED: 수행 중인 자식 프로세스의 상태값이 리턴된다.
  • WNOHANG: pid로 지정한 자식 프로세스의 상태값을 즉시 리턴받을 수 없어도 이를 호출한 프로세스의 실행을 블록하지 않고 다른 작업을 수행하게 된다.
  • WNOWAIT: 상태값을 리턴한 프로세스가 대기 상태로 머물 수 있도록 한다.
  • WUNTRACED: 실행을 중단한 자식 프로세스의 상태값을 리턴한다. 실행이 중되었으므로 더이상 상태값을 리턴하지 않는다.

waitpid 함수를 사용해 fork 함수로 생성한 자식 프로세스가 종료하기를 기다리게 만들어보겠습니다.

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

int main(void) {
    int status;
    pid_t pid;

    if((pid = fork()) < 0) { /* fork failed */
        perror("fork");
        exit(1);
    }

    if(pid == 0) { /* child process */
        printf("--> Child process\n");
        sleep(3);
        exit(3);
    }

    printf("--> Parent process\n");

    while(waitpid(pid, &status, WNOHANG) == 0) {
        printf("Parent still wait...\n");
        sleep(1);
    }

    printf("Child Exit Status: %d\n", status>>8);

    return 0;
}