본문 바로가기

프로그래밍/C

[function] wait 함수 알아보기

📌 매뉴얼 (Linux)

더보기

NAME
       wait - wait for process to change state
: 프로세스가 상태를 변경할 때까지 기다리기


SYNOPSIS
       #include <sys/types.h>
       #include <sys/wait.h>
       pid_t wait(int *wstatus);

DESCRIPTION
       All  of  these  system  calls  are  used  to  wait for state changes in a child of the calling
       process, and obtain information about the child whose state has changed.  A  state  change  is
       considered  to  be:  the child terminated; the child was stopped by a signal; or the child was
       resumed by a signal.  In the case of a terminated child, performing a wait allows  the  system
       to  release the resources associated with the child; if a wait is not performed, then the ter‐
       minated child remains in a "zombie" state (see NOTES below).

       If a child has already changed state, then these calls return  immediately.   Otherwise,  they
       block  until  either  a  child changes state or a signal handler interrupts the call (assuming
       that system calls are not automatically restarted using the SA_RESTART flag of  sigaction(2)).
       In  the  remainder  of  this  page, a child whose state has changed and which has not yet been
       waited upon by one of these system calls is termed waitable.

   wait() and waitpid()
       The wait() system call suspends execution of the calling thread until one of its children ter‐
       minates.  The call wait(&wstatus) is equivalent to:
           waitpid(-1, &wstatus, 0);

 

       The  waitpid() system call suspends execution of the calling thread until a child specified by
       pid argument has changed state.  By default, waitpid() waits only for terminated children, but
       this behavior is modifiable via the options argument, as described below.

       The value of pid can be:
       < -1   meaning  wait  for  any  child  process whose process group ID is equal to the absolute
              value of pid.
       -1     meaning wait for any child process.
       0      meaning wait for any child process whose process group ID is equal to that of the call‐
              ing process at the time of the call to waitpid().
       > 0    meaning wait for the child whose process ID is equal to the value of pid.

: 이 모든 시스템 호출은 호출 과정의 자식에서 상태 변경을 대기하고 상태가 변경된 child에 대한

정보를 얻는 데 사용됩니다. 상태 변경은 다음과 같이 간주됩니다. child가 종료되거나,

신호에 의해 child가 중지되거나, 신호에 의해 다시 시작됩니다. 종료된 child의 경우,

대기를 수행하면 시스템이 child과 관련된 리소스를 해제할 수 있고, 대기를 수행하지 않으면

종료된 child는 "좀비" 상태로 유지됩니다(아래 참고 참조).

child가 이미 상태를 변경한 경우 이러한 호출은 즉시 반환됩니다.

그렇지 않으면 child이 상태를 변경하거나 신호 처리기가 호출을 중단할 때까지

(시그액션(2)의 SA_RESTART 플래그를 사용하여 시스템 호출이 자동으로

다시 시작되지 않는다고 가정할 때) 차단됩니다.
이 페이지의 나머지 부분에서는 상태가 변경되어 아직 이러한 시스템 호출 중 하나가

대기하지 않은 child를 대기 가능 상태라고 합니다.

wait() and waitpid()
wait() 시스템 호출은 하위 호출 중 하나가 종료될 때까지 호출 스레드의 실행을 중지합니다.

호출 wait(&wstatus)는 다음과 같습니다:
waitpid (- 1, &wstatus, 0);

waitpid() 시스템 호출은 pid 인수에 의해 지정된 child이 상태가 변경될 때까지 호출 스레드의 실행을 중지합니다.

기본적으로 waitpid()는 종료된 child만 대기하지만, 이 동작은 아래 설명된 대로 옵션 인수를 통해 수정할 수 있습니다.

pid 값은 다음과 같습니다:
< -1은 프로세스 그룹 ID가 pid의 절대값과 동일한 하위 프로세스를 대기한다는 것을 의미합니다.
-1은 모든 자식 프로세스를 기다릴 것을 의미합니다.
0은 호출 시 프로세스 그룹 ID가 호출 프로세스 ID와 동일한 하위 프로세스를 waitpid()로 기다리는 것을 의미합니다.
> 0은 프로세스 ID가 pid 값과 동일한 child를 기다린다는 뜻입니다.

 

RETURN VALUE
       wait(): on success, returns the process ID of the terminated child; on error, -1 is returned.
       Each of these calls sets errno to an appropriate value in the case of an error.

: 성공 시 종료된 자식의 프로세스 ID를 반환하고, 오류 시 -1을 반환합니다.

이러한 호출은 오류가 발생할 경우 errno를 적절한 값으로 설정합니다.

📌 함수 설명

wait 함수는 부모 프로세스가 자식 프로세스가 종료될 때까지는 아무 일도 하지 않고 기다려주도록 하는 함수이다.

또한, 자식 프로세스가 완전히 종료되면 자식 프로세스에 대한 정보를 부모 프로세스가 얻을 수 없으므로,

wait 함수에서는 자식 프로세스에 대한 어떠한 정보를 status 포인터를 통해 저장하여준다.


[ Zombie Process 란?]

 

자, 우선 자식 프로세스가 부모 프로세스보다 먼저 종료되었을 때,

커널이 자식 프로세스를 특수한 상태로 만들어주는데, 이를 'Zombie process' 라 한다.

말 그대로, 죽지 않고 살아있는, 실행은 종료되었지만 아직 삭제되지 않은 프로세스이다.

이는 리소스도 회수되었지만 시스템 프로세스 테이블엔 남아 있는 상태의 프로세스인데,

자식 프로세스가 exit()을 통해 종료하면 이 상태가 된다.

 

이 zombie process는 부모 프로세스가 wait()함수를 호출할 때까지 남아있게 되는데,

결론적으로 '종료되었지만 부모 프로세스가 wait()를 호출하지 않아 남아있는 프로세스'인

zombie process는 부모가 wait()를 호출하면 테이블에 있던 데이터와 PID를 운영체제에 반환한다.

 

+) zombie process는 시스템에 문제를 주나요?

: 이미 리소스가 반납되었기에, 시스템 리소스를 소모하지 않아 문제가 되진 않지만,

할당받은 PID는 반납하지 않았기에, 2의 15승인 32768개의 PID (리눅스 기준)를 좀비 프로세스가

대부분 차지하게 되는 상황이 오면 일반 프로세스에 실행을 방해하는 문제가 발생할 수도 있다.

 

< Zombie Process 찾는 명령어 >

ps -ef | grep defunct | grep -v grep

ps aux | egrep "Z|defunct"

top -b -n 1 | grep zombie

 

위 세가지 shell 명령어로 zombie process를 찾을 수 있는데,

1, 2번째 방법으로는 해당 프로세스 자체를 찾고, 3번째 방법으로 개수만 파악할 수도 있다.


함수 원형을 보면, pid_t wait(int *status); 이다. 

반환값은 pid_t 이고, 매개변수로는 정수 포인터를 받는다.

우선, 반환값부터 보자면, 종료된 자식 프로세스의 프로세스 ID를 return 하고, 실패하면 -1을 반환한다.

(자식이 죽었는데 좀비가 되고 자식 식별번호라도 부모가 알아가야 자식이 눈감는  스토리가... ㅠㅠ)

그리고, 매개변수인 status는 포인터로 전달받는데, 이 부분이 NULL이 아니면

해당 포인터에 자식 프로세스의 추가 정보가 저장된다. (추가 정보가 필요없다면 NULL 사용)

 

Status에 저장된 비트를 해석하기 위한 매크로들이 존재하고 다음과 같다.

WIFEXITED / WIFSIGNALED / WIFSTOPPED / WIFCONTINUED /

WEXITSTATUS / WTERMSIG / WSTOPSIG / WCOREDUMP

-> 정상 종료시, status에 있는 하위 8비트에는 0이 저장 + 상위 8비트에는 프로세스 종료 시그널 번호 저장

 비정상 종료시, status에 있는 하위 8비트에는 프로세스 종료 시그널 번호 저장 + 상위 8비트에는 0이 저장

이 저장된 비트를 바탕으로 위의 매크로들이 해석해준다는 점을 알아두면 된다.


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

int main(void)
{
    int status;
    
    if(fork() == 0)
    {
        printf("I'm child\n");
    }
    else
    {
        printf("I'm parent\n");
        wait(&status);
        printf("CHILD ENDED\n");
    }
    printf("**********\n");
    return (0);
}

 

위의 코드는 단순하게 fork()를 통해 부모와 자식 노드가 생성된 뒤, wait로 부모가 자식을 기다리는 과정을 볼 수 있다.

우선, parent는 else의 명령을 실행하고, I'm parent를 출력한다. 그리고 wait를 만나면, 자식 프로세스가 return (0)을 만나

끝날 때까지, 기다려준다. 그리고 자식 프로세스가 출력할 두줄을 출력한 뒤에서야 부모 프로세스의 나머지 줄이 출력된다.

 

출력 - std_output)

I'm parent
I'm child
**********
CHILD ENDED
**********

 

프로세스의 흐름을 출력을 통해 쉽게 이해할 수 있다.


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

int main(void)
{
    int status;
    pid_t pid, result_pid;
    if((pid = fork()) == 0)
    {
        exit(7);
    }
    printf("fork된 child의 pid : %d\n", pid);
    result_pid = wait(&status);
    if(WIFEXITED(status))
    {
        printf("exit된 child의 pid : %d\n", result_pid);
        printf("status 값 : %x\n", status);
        printf("매크로를 통한 exit status : %d\n", WEXITSTATUS(status));
    }
    else
    {
    	printf("비정상적 종료\n");
    }
    return (0);
}

 

출력 - std_output)

fork된 child의 pid : 8143
exit된 child의 pid : 8143
status 값 : 700
매크로를 통한 exit status : 7

 

fork 의 결과도 pid에 저장해주고, wait의 결과도 result_pid에 저장하여 서로 비교해주면 동일함을 볼 수 있다.

그리고, 자식 프로세스는 exit(7)으로 종료시켜주는데, 이에 따라 status에는 상위 8비트에 7이 저장되고

하위 8비트에 0이 저장되었다. 

 

그럼 그 status 값을 확인해보자. 

 

if문 조건으로 WIFEXITED를 넣었는데, 이는 exit로 정상 종료되면 참, 비정상 종료면 거짓을 반환하는 매크로다.

따라서 정상종료 시, 해당 문구들을 printf로 출력하는데, 여기서 status 값들을 확인할 수 있다.

 

우선 %x를 통해 16진수로 출력해보면, 700이 나오는 것을 볼 수 있다. (상위 8비트 : 7 / 하위 8비트 : 0)

그 후, WEXITSTATUS 매크로로 종료 코드를 담아 제공하면 7이 나오는 것을 볼 수 있다.


참고)

https://blog.naver.com/skout123/50133478563

https://jobdong7757.tistory.com/102

https://velog.io/@shinkoh98/wait-%ED%95%A8%EC%88%98

https://blogshine.tistory.com/98

https://wildeveloperetrain.tistory.com/180

'프로그래밍 > C' 카테고리의 다른 글

[library] substr 구현하기  (0) 2024.06.18
[function] waitpid 함수 알아보기  (2) 2024.06.13
[function] unlink 함수 알아보기  (2) 2024.06.07
[function] pipe 함수 알아보기  (0) 2024.06.06
[function] fork 함수 알아보기  (2) 2024.06.04
Recent Posts
Popular Posts
Recent Comments