본문 바로가기

진리는어디에

[Linux] 좀비 프로세스 죽이기

좀비 프로세스란?

자식 프로세스가 exit 시스템 콜을 호출하여 종료 되면 프로세스에 관련된 모든 리소스가 해제되어 다른 프로세스에서 사용 할 있게 되지만, 자식 프로세스가 종료 되더라도 부모 프로세스에서 자식 프로세스의 상태를 알고 싶을 수도 있기 때문에 커널은 자식 프로세스가 종료 되더라도 프로세스 아이디, 종료 상태 등의 최소한의 정보를 프로세스 테이블에 유지 한다.

이미 프로세스의 리소스는 모두 회수 되었지만 시스템의 프로세스 테이블에 남아 있는 프로세스를 "defunct" 또는 "dead" 상태 프로세스라고 하며 일반적으로 "좀비" 프로세스라고 더 잘 알려져 있다.

이 좀비 프로세스를 없애기 위해서는 부모 프로세스가 죽거나, 부모 프로세스에서 wait 또는 waitpid 시스템 콜을 호출하면 된다. 자식 프로세스를 fork하고 자식 프로세스가 죽었을 때 부모 프로세스에서 처리하는 방법은 fork를 다루는 다른 포스트에서 자세하게 다루도록 하고, 오늘은 어떠한 이유로 부모 프로세스가 자식 프로세스를 깔끔하게 처리하지 못해 좀비 프로세스가 생성 되었을 경우 어떻게 좀비 프로세스를 죽일 수 있는지에 대해 알아 보도록 한다.

좀비 프로세스 만들기

정상적인 상황에서라면 좀비 프로세스를 좀 처럼 보기 힘드므로 실습을 위해 좀비 프로세스를 만드는 프로그램을 간단하게 만들어 보자. 위에서 언급한것 처럼 좀비 프로세스를 만들기 위해 부모 프로세스가 자식 프로세스를 생성 후 자식 프로세스가 죽고난 뒤 wait 또는 waitpid 시스템콜을 의도적으로 호출 하지 않는 예다.

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

int main()
{
    pid_t child_pid = fork();
    if(0 == child_pid) // 자식 프로세스
    {
        std::cout << "child started(pid:" << getpid() << ")" << std::endl;
        std::cout << "child finished(pid:" << getpid() << ")" << std::endl;
        exit(0);
    }
    else // 부모 프로세스
    {
        std::cout << "parent started(pid:" << getpid() << ", child_pid:" << child_pid << ")" << std::endl;
        //int child_exit_status = 0;
        //wait(&child_exit_status);
        sleep(60);
        std::cout << "parent finished(pid:" << getpid() << ", child_pid:" << child_pid << ")" << std::endl;
        exit(0);
    }
    return 0;
}

좀비 프로세스 찾기

위 코드를 컴파일 한 후 백그라운드로 실행 시키고 ps 커맨드로 프로세스 상태를 확인하면 좀비 상태에 있는 프로세스를 찾을 수 있다.

$ ps aux | egrep "Z|defunct"

- 백그라운드로 프로그램을 실행 하는 이유는 60초 sleep을 걸어 놔서 60초 동안 터미널의 입력을 먹어 버리기 때문. 터미널을 하나 더 띄워서 테스트 해도 된다 -

$ ./a.out &
parent started(pid:270, child_pid:271)
child started(pid:271)
child finished(pid:271)
$ ps aux|egrep "Z|defunct"
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
kukuta     271  0.0  0.0      0     0 tty1     Z    22:58   0:00      |   \_ [a.out] <defunct>
kukuta     273  0.0  0.0  14372   872 tty1     R    22:58   0:00      \_ grep -E --color=auto Z|defunct

STAT 컬럼의 Z 또는 COMMAND 컬럼의 <defunct>가 좀비 프로세스임을 가리킨다.

좀비 프로세스 죽이기

좀비 프로세스의 pid도 알았으니 이제 kill 커맨드를 이용해 죽이기만 하면된다.

...미안하다...거짓말이다...

아무리 kill 명령어를 날려도 좀비 프로세스는 이미 죽었기 때문에(리소스 반환 및 기타 프로세스 종료 과정이 완료 되었다는 뜻) 죽지 않는다. 대신 부모 프로세스에게 SIGCHLD 시그널을 날려 자식 프로세스가 죽었음을 알려 깔끔하게 프로세스 테이블에서 지울 수 있도록 해야 한다. 시그널을 날리기 위해서는 부모 프로세스의 pid가 필요하다. 아래 커맨드는 자식 프로세스의 pid를 이용해 부모 프로세스의 pid를 찾아 준다. PPID 컬럼이 부모 프로세스의 pid 다.

$ ps -f <child_pid>
$ ps -f 271
UID        PID  PPID  C STIME TTY      STAT   TIME CMD
kukuta     271   270  0 23:01 tty1     Z      0:00 [a.out] <defunct>

부모 프로세스의 아이디를 얻었으면 아래 커맨드를 이용해 부모 프로세스에게 SIGCHLD 시그널을 보내자.

$ kill -s SIGCHLD <parent_pid>

...미안하다...또 거짓이다...

위 방법이 먹힐 수도 있지만 100% 좀비 프로세스를 처리 할 수 있다고 장담하지는 못한다. 위 방법은 부모 프로세스가 SIGCHLD 시그널 핸들러를 등록 했을때만 가능하다.

위의 방법으로도 좀비 프로세스가 죽지 않는다면 부모 프로세스를 죽이는 수 밖에 없다. 다만 부모 프로세스를 죽인다는 것은 하위의 모든 자식 프로세스들을 죽인다는 뜻이 되므로 사이드 이펙트를 신중하게 고려 해야만 한다.

부록 1. 같이 읽으면 좋은 글

유익한 글이었다면 공감(❤) 버튼 꾹!! 추가 문의 사항은 댓글로!!