개발공부/운영체제

[OS] 좀비 프로세스 & 고아 프로세스

개발자 찐빵이 2021. 11. 12. 20:15
728x90

프로세스 생성

부모 프로세스가 자식 프로세스를 만드는 것
PCB에 저장된 pid(process identifier)값으로 프로세스를 식별한다.

UNIX에서 프로세스 생성

fork() 시스템 호출은 새로운 프로세스를 만들어내는 기능을 한다.
새로운 프로세스는 원래의 프로세스 주소 공간의 복사본을 가지고 있다.
fork() : 현재 돌아가고 있던 프로세스를 복사해서 다른 프로세스를 만든다.
exec() : 프로세스로 하여금 다른 바이너리를 가지고 새로 시작하도록 한다. 자식 프로세스는 exec을 통해 내용을 모두 바꾼다.

fork와 exec의 차이

exec()

기존 프로세스를 덮어버리게 되는 것.
유닉스 체계에 exec만 존재한다면, init 프로세스는 다른 사용자 프로그램에 덮어지게 되고, 커널이 붕괴된다.
그래서 fork가 필요하다.

fork()

현재 프로세스를 메모리상에서 복사한 자식 프로세스를 만드는 것.
자식은 부모의 변수, 스택, 힙의 내용을 복사하게 된다.
PCB도 함께 복사한다.

PCB가 자식에 복사되기 때문에 자식 프로세스는 fork() 실행 후의 위치 (부모와 동일한 위치) 에서 부터 시작하게 된다.

fork()된 자식 프로세스는 부모 프로세스가 종료되거나, 부모 프로세스에서 wait, waitpid 함수를 부르면 종료된다.

프로세스 삭제

프로세스는 마지막 statement를 실행하고 exit() 시스템 호출을 이용하여 운영체제로 하여금 삭제하도록 한다.
부모는 자식에게서 wait을 통해 상태 값을 받을 수 있다.

좀비 프로세스

프로세스가 종료되었음에도 불구하고 메모리상에서 프로세스에 대한 정보가 사라지지 않은 상태

좀비 프로세스가 생기는 이유

자식 프로세스가 종료되었지만, 부모가 wait을 통해 상태를 보고받지 못한 경우에 생긴다.
이 상황에 놓여있는 프로세스를 좀비프로세스라고 한다.

좀비 프로세스 예제

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


int main() {

    pid_t childPid;

    childPid = fork();

    if(childPid > 0) {  // 부모 프로세스
        printf("부모 PID : %ld, pid : %d\n",(long)getpid(), childPid);
        sleep(30);
        printf("부모 종료\n");
        exit(0);
    }
    else if(childPid == 0){  // 자식 코드
        printf("자식 시작 PID : %ld\n", (long)getpid());
        sleep(1);
        printf("자식 종료\n");
        exit(0);
    }
    else {  // fork 실패
        perror("fork Fail! \n");
        return -1;
    }

    return 0;
}

결과

ps aux | grep 'Z' 명령어를 사용하여 좀비 프로세스를 출력할 수 있다.

좀비 프로세스가 쌓이게 되면 리소스 유출을 야기할 수 있기 때문에 좀비 프로세스 상태를 오래 유지하지 않도록 부모 프로세스는 wait 시스템 콜 함수를 사용해 자식 프로세스의 종료 상태를 읽어들여야 한다.

좀비 프로세스를 없애는 방법

부모 프로세스가 wait 시스템 콜을 호출하여 좀비 프로세스의 종료 상태를 회수하게 되면 좀비 프로세스를 제거할 수 있다.

자식 프로스를 기다리고 종료 상태를 받아온다 : spid = wait(&status);

  1. wait을 통해서 자식 프로세스를 기다린다.
  2. wait의 상태 값을 이용해서 자식 프로세스를 평가한다.

고아 프로세스

부모가 자식 프로세스보다 먼저 종료되면 그 자식 프로세스는 고아 프로세스가 된다.

이 때, init 프로세스가 새로운 부모가 되고, 고아 프로세스를 종료시키기 위해 wait함수를 호출한다.

고아 프로세스 예제

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

int main() {

    pid_t childPid;
    int i;

    childPid = fork();

    if(childPid > 0) {  // 부모 프로세스
        printf("부모 PID : %ld, pid : %d\n",(long)getpid(), childPid);
        sleep(2);
        printf("부모 종료\n");
        exit(0);
    }
    else if(childPid == 0){  // 자식 코드
        printf("자식 시작\n");

        for(i=0;i<10;i++) {
            printf("자식 PID : %ld 부모 PID : %ld\n",(long)getpid(), (long)getppid());
            sleep(1);
        }

        printf("자식 종료\n");
        exit(0);
    }
    else {  // fork 실패
        perror("fork Fail! \n");
        return -1;
    }

    return 0;
}

결과

자식 프로세스의 원래 부모 프로세스는 PID 46797 였으나 부모 프로세스가 종료 된 후에는 init 프로세스 인 1로 바뀐 것을 볼 수 있다.

init 프로세스
유닉스 계열의 운영체제에서 부팅 과정 중 생성되는 최초의 프로세스이며 시스템이 종료될때까지 계속 살아있는 데몬 프로세스

고아 프로세스가 작업을 종료하면 init 프로세스가 wait함수를 호출하여 고아 프로세스의 종료 상태를 회수함으로써 좀비 프로세스가 되는것을 방지한다.

참고 사이트

프로세스 생성, 좀비 & 고아 프로세스
고아 & 좀비 프로세스 예제
fork and exec

반응형