프로세스의 생성과 종료

Unix 계열의 프로세스 실행은 fork, exec, wait를 나누어 보면 이해하기 쉽다. fork는 자식 프로세스를 만들고, exec 계열 함수는 현재 프로세스의 프로그램을 바꾸며, wait 계열 함수는 자식의 종료 상태 등을 회수한다.

fork와 exec는 서로 다른 단계다

Linux의 fork는 copy-on-write를 사용하므로 부모의 메모리 전체를 즉시 물리적으로 복사한다는 설명은 부정확하다. 부모와 자식은 별도의 주소 공간을 갖고, 쓰기 등이 발생할 때 필요한 페이지를 분리한다. 파일 디스크립터는 상속되며 같은 열린 파일 설명을 참조할 수 있다.

exec에 성공하면 이전 프로그램으로 돌아오지 않는다. 새 PID를 만드는 동작이 아니라 현재 프로세스의 실행 이미지를 바꾸는 동작이다.

자식의 종료 코드를 회수하기

아래는 임시 자식 셸을 만들고 종료를 기다리는 예제다. 디스크 파일이나 시스템 설정은 바꾸지 않는다.

sh -c 'exit 7' &
child_pid=$!
if wait "$child_pid"; then
  printf 'child succeeded\n'
else
  child_status=$?
  printf 'child exited with %s\n' "$child_status"
fi

출력은 child exited with 7이다. wait가 0이 아닌 값을 돌려줄 수 있으므로 종료 코드를 처리하는 분기를 둔다.

좀비와 고아 프로세스 구분하기

좀비는 실행을 끝냈지만 종료 상태가 아직 회수되지 않은 프로세스다. 실행 중인 프로그램이 계속 큰 메모리를 쓰는 상태와는 다르지만, 상태를 보관할 커널 자원은 남는다.

부모가 먼저 종료된 자식은 다른 프로세스에 재부모화될 수 있다. Linux에서는 가장 가까운 subreaper나 init이 관여한다. 부모가 종료되면 자식도 항상 함께 종료된다는 규칙은 없다. 컨테이너에서 PID 1 역할을 하는 프로그램은 자식 종료 처리까지 고려해야 한다.

자료: fork(2), execve(2), wait(2)