본문 바로가기

개발/리눅스 프로그래밍

좀비 프로세스 방지

출처 : 아주대학교 시스템 소프트웨어 보안 강의자료

/*
* 공부한거 정리하는거라 틀린게 있을 수도 있어요...
* 내용에 틀린게 있으면 알려주세요!!
*/

  유닉스의 모든 프로세스는 종료 시에 종료 상태를 부모 프로세스에 보낸다. 부모 프로세스는 해당 프로세스를 실행한 프로세스인데, wait함수 또는 waitpid함수를 사용해 자식 프로세스의 종료 상태를 받을 수 있다. 하지만 부모 프로세스가 자식 프로세스를 받아주지 않을 경우, 자식 프로세스는 아무 작업도 하지 않지만 종료되지는 못한 채 남아있게 된다. 이를 좀비 프로세스라고 부른다. wait함수와 waitpid함수의 자세한 설명은 다음과 같다.

wait : 자식 프로세스가 종료 상태를 보낼 때까지 대기. 부모 프로세스는 block

함수 원형 pid_t wait(int *statloc)
헤더 sys/wait.h
반환 자식 프로세스의 pid
실패시 -1
인수 설명 int *statloc : 자식 프로세스의 종료 상태
 - WIFEXITED(statloc) : 정상 종료시 true
 - WIFSIGNALED(statloc) : 시그널에 의해 종료되었을 때 true
 - WIFSTOPPED(statloc) : 프로세스 중단시 true
 - WEXITSTATUS(statloc) : 정상 종료되어 반환한 값

waitpid : 옵션을 사용해 non-blocking모드로 실행 가능하도록 함. 대기하는 자식 프로세스를 특정할 수 있음

함수 원형 pid_t waitpid(pid_t pid, int *statloc, int option)
헤더 sys/wait.h
반환 자식 프로세스의 pid
실패시 -1
인수 설명 pid_t pid : 대상이 되는 자식 프로세스 pid
int *statloc : 자식 프로세스의 종료 상태(wait함수와 같음)
int option : 대기 옵션
 - WNOHANG : 대상 프로세스의 종료 상태를 바로 받을 수 없으면 바로 0을 반환
 - WUNTRACED : 중단된 자식 프로세스의 상태를 받음
 - WCONTINUED : 중단했다 재개한 자식 프로세스의 상태를 받음
 - 0 : wait와 똑같이 사용

  부모 프로세스에서 wait함수를 사용하면 부모 프로세스가 작업을 할 수 없는 문제가 있다. 그리고 자식 프로세스가 언제 종료할지 모르는 경우엔 수시로 waitpid를 실행해야 하기 때문에 낭비가 생길 수 있다. 이런 문제 없이 좀비 프로세스를 만들지 않는 방법은 두 가지가 있다. 시그널 핸들러를 사용하는 방법과 더블포크이다.

1. Signal Handler

  자식 프로세스는 종료시에 부모 프로세스에 SIGCHLD 시그널을 보낸다. 부모 프로세스는 SIGCHLD 시그널 핸들러를 만들어 핸들러 안에서 wait 또는 waitpid함수를 실행해 자식 프로세스의 종료 상태를 받아주면 자식 프로세스를 정상적으로 종료할 수 있다. 
  부모 프로세스가 Block I/O등의 이유로 Block된 상태에서 시그널을 받으면, 시그널 핸들러 함수 종료 후에 block이 풀리게 된다. 때문에 SIGCHLD 시그널 핸들러를 사용하기 위해서는 프로세스의 Block모드에 이런 상황을 고려한 설계가 필요하다. 아래는 SIGCHLD 시그널 핸들러의 예제이다.

https://github.com/jaduwvad/SystemSoftwareSecurity/blob/master/process/shandler.c

2. Double Fork

  자식 프로세스가 끝나기 전에 부모 프로세스가 먼저 종료되는 상황이 있을 수 있다. 이런 경우에 부모 프로세스는 유닉스의 init 프로세스로 바뀐다. init 프로세스는 부팅시 커널에서 실행하는 프로세스로 다른 모든 프로세스를 실행한다. init프로세스는 좀비 프로세스를 만들지 않기 위해 종료 상태를 받아오는 작업을 자동으로 실행한다. Double Fork는 이런 특징을 사용한 방법이다.
  1번 프로세스에서 fork함수를 사용해 2번 프로세스를 만들고 wait함수를 실행한다. 그리고 2번 프로세스는 다시 fork를 사용해 3번 프로세스를 만들고 곧바로 종료한다. 그럼 3번의 부모 프로세스는 init프로세스가 되어 종료 상태를 처리할 수 있고 1번 프로세스도 2번 프로세스가 바로 종료하기 때문에 바로 종료 상태를 받은 뒤 다음 작업을 진행할 수 있다. 하지만 fork함수를 1번 더 실행하기 때문에 자원이 소모된다는 단점이 있다. 아래는 더블 포크의 예제이다.

https://github.com/jaduwvad/SystemSoftwareSecurity/blob/master/process/double_fork.c