운영체제 따라잡기

프로세스 생성과 종료

meellon 2026. 6. 22. 05:00

학습 목표

프로세스가 트리 구조로 생성된다는 점을 설명할 수 있다.

Unix에서 fork / exec / wait / exit 가 각각 어떤 역할인지 설명할 수 있다.

자식이 종료한 뒤 부모가 wait 하지 않으면 좀비, 부모가 먼저 죽으면 고아가 되는 이유를 설명할 수 있다.

psSTAT Z, <defunct> 가 무엇을 의미하는지 읽을 수 있다.

문제 상황

  • 셸에서 ls를 치면 새 프로세스가 뜨고 끝난다
    • 셸은 그대로인데 ls는 어디서 왔는지, 누가 만드는지 모른다
  • ps aux | grep Z<defunct> 프로세스가 쌓인다
    • 종료됐는데 왜 목록에 남는지, 서버 재시작 전까지 안 사라지는 이유를 모른다
  • 부모 서비스가 죽었는데 자식 워커가 PID 1 밑으로 붙는다
    • systemd가 고아를 거둔다는 말의 의미가 불명확하다
  • fork()만 연속 호출하면 fork bomb으로 시스템이 멈춘다
    • 복제가 무엇을 복사하는지, 왜 이렇게 빨리 늘어나는지 감이 없다

앞서 PCB·상태·컨텍스트 스위칭을 봤다. 이번 편은 OS가 프로세스를 어떻게 만들고, 어떻게 끝내는지 — 특히 Unix fork + exec 모델을 잡는다.

1. 프로세스는 트리로 자란다

OS는 부팅 후 init/systemd(PID 1) 하나에서 출발해, 기존 프로세스가 자식 프로세스를 만든다.

개념 설명
부모(Parent) fork·CreateProcess 등으로 자식을 만든 프로세스
자식(Child) 새로 생성된 프로세스, PCB에 PPID로 부모를 가리킴
PID 프로세스 고유 ID
PPID 부모 PID — ps -o pid,ppid,comm으로 확인
  • Linux 부팅 직후: systemdsshd, dockerd, nginx … 각각이 다시 자식을 fork
  • 한 부모, 여러 자식 가능 — 웹 서버 worker, 셸이 백그라운드 job을 띄울 때
  • Windows도 프로세스 트리 개념은 같고, 생성 API는 CreateProcess 계열
# PPID로 부모 추적
ps -o pid,ppid,comm -p $$
ps -o pid,ppid,comm --ppid 1 | head

2. fork — 주소 공간 복제

Unix/Linux에서 새 프로세스의 첫 단계는 보통 fork() 시스템 콜이다.

  • 호출 직후 부모와 자식 둘 다 fork() 다음 줄부터 실행을 계속한다
  • 자식 PCB는 부모를 복사한 뒤 PID만 새로 부여
  • 반환값으로 구분
    • 부모: 자식 PID (양수)
    • 자식: 0
    • 실패: -1

복제·공유 내용
복사 PCB 대부분, 파일 디스크립터 테이블, 환경 변수, 신호 처리 설정
공유 (초기) 코드(텍스트) 세그먼트 — 읽기 전용이라 물리 페이지 공유 (COW 전)
COW 이후 쓰기 시 Copy-On-Write로 페이지 분리 — fork 직후 전체 메모리를 즉시 복사하지 않음
독립 각자 PID, 자식 PPID = 부모 PID
pid_t pid = fork();
if (pid < 0) { perror("fork"); exit(1); }
if (pid == 0) {
    /* child */
} else {
    /* parent, pid == child PID */
}
  • fork만 하면 같은 프로그램이 두 인스턴스로 실행된다
  • 셸이 다른 명령을 실행하려면 다음 단계 exec 가 필요하다

3. exec — 프로그램 이미지 교체

exec 계열(execve, execlp …)은 현재 프로세스의 메모리 이미지를 새 실행 파일로 덮어쓴다.

특징 설명
PID 유지 프로세스 번호는 그대로, 프로그램만 바뀜
주소 공간 교체 코드·데이터·힙·스택을 새 ELF(실행 파일)로 재구성
성공 시 반환 없음 exec가 성공하면 호출 지점으로 돌아오지 않음
실패 시 -1 반환, 기존 이미지 유지

전형적인 셸 패턴:

fork()  →  자식만 exec("/bin/ls", ...)  →  ls 실행 후 exit
         →  부모는 wait()로 종료 대기  →  프롬프트 복귀
  • fork + exec = "새 프로그램을 새 프로세스로 실행"
  • exec만 쓰면 현재 셸 자신ls로 바뀐다 (exec without fork)
  • 로더(3편)가 exec 경로에서 실행 파일을 메모리에 매핑한다

4. wait와 프로세스 종료

자식이 끝나면 커널은 종료 상태(exit status) 를 PCB에 남긴다. 부모가 wait() / waitpid() 로 회수하기 전까지 자식 엔트리는 좀비 상태로 남는다.

syscall 역할
exit(status) / _exit() 프로세스 종료, 열린 FD 정리(또는 atexit), PCB에 종료 코드 기록
wait(&status) 아무 자식 하나 종료까지 블록, 종료 코드 회수
waitpid(pid, &status, opts) 특정 PID 또는 옵션(WNOHANG 등)으로 대기

종료 코드는 보통 상위 8비트 = 종료 방식, 하위 8비트 = exit code (정상 종료 시).

# 종료 코드 확인
bash -c 'exit 42'; echo $?
시나리오 부모 동작 결과
자식 종료 후 wait 즉시 회수 좀비 없음
자식 종료, 부모 바쁨 wait 미호출 좀비(Z) — PCB만 남음
부모가 먼저 종료 자식은 고아 init/systemd가 입양, 나중에 wait
  • 좀비는 CPU를 쓰지 않음 — PCB·종료 코드 슬롯만 점유
  • 수천 개 쌓이면 PID·커널 테이블 고갈로 새 프로세스 생성 실패 가능

5. 좀비와 고아 프로세스

좀비 (Zombie)

  • 자식: exit 완료 — 실행은 끝남
  • 부모: wait 안 함 — "이 자식 결과 아직 안 받음"
  • ps STAT Z 또는 defunct
  • 해결: 부모가 wait 호출, 또는 부모 프로세스 종료(커널이 자식을 init에게 넘기고 정리)

고아 (Orphan)

  • 부모가 먼저 종료 — 자식은 아직 실행 중
  • 커널이 자식의 PPID를 1(systemd) 로 변경
  • init이 주기적으로 wait 해 종료된 고아의 좀비를 정리
  • 장기 실행 데몬의 부모가 죽어도 자식이 살아 있으면 reparenting으로 트리가 재구성됨
# 좀비 확인
ps aux | awk '$8 ~ /Z/ {print}'

# 프로세스 트리
pstree -p $$
구분 실행 중? 부모 관계 전형적 STAT
정상 자식 부모 아래 S, R
좀비 아니오 (종료됨) 부모가 wait 안 함 Z
고아 PPID → 1 S, R

6. Windows와 비교

  Unix/Linux Windows
생성 fork + exec (2단계) CreateProcess (이미지 로드까지 한 번에)
종료 대기 wait / waitpid WaitForSingleObject on process handle
좀비 명시적 wait 필요 프로세스 핸들·객체 수명 모델이 다름
트리 PPID, pstree Job 객체, 부모 핸들

POSIX API를 쓰는 크로스 플랫폼 코드는 OS별 프로세스 생성 추상화(posix_spawn 등)를 쓰기도 한다.

7. 정리

  • 프로세스는 트리fork로 자식 생성, PCB에 PID·PPID 기록
  • fork 는 복제, exec 는 같은 PID에서 새 프로그램으로 교체
  • 자식 exit 후 부모 wait 이 회수 — 안 하면 좀비
  • 부모가 먼저 죽으면 고아init/systemd가 입양
  • 셸·서버 worker·CI 러너는 이 모델 위에서 생성·대기·정리를 반복한다

다음에 다룰 것

  • 스레드 — 프로세스 안의 실행 단위, fork와의 차이
  • 멀티스레드 모델, 동일 주소 공간 공유

해당 내용은 Operating System Concepts, 10/E (Avraham Silberschatz, Peter Baer Galvin, Greg Gagne) 의 내용을 기반으로 합니다.

'운영체제 따라잡기' 카테고리의 다른 글

CPU 스케줄링 기초  (0) 2026.06.24
스레드  (0) 2026.06.23
프로세스란 무엇인가  (0) 2026.06.21
운영체제 구조와 시스템 콜  (0) 2026.06.20
운영체제의 개념과 구조  (0) 2026.06.19