학습 목표
프로세스가 트리 구조로 생성된다는 점을 설명할 수 있다.
Unix에서 fork / exec / wait / exit 가 각각 어떤 역할인지 설명할 수 있다.
자식이 종료한 뒤 부모가 wait 하지 않으면 좀비, 부모가 먼저 죽으면 고아가 되는 이유를 설명할 수 있다.
ps의 STAT 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 부팅 직후:
systemd→sshd,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로 바뀐다 (execwithout 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안 함 — "이 자식 결과 아직 안 받음" psSTATZ또는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 |