좀비 프로세스와 고아 프로세스는 모두 부모와 자식 프로세스의 관계에서 발생한다.
유닉스·리눅스 계열에서는 프로세스가 다른 프로세스를 생성할 수 있다.
프로세스가 종료되면 운영체제가 즉시 모든 정보를 지울 것 같지만 실제로는 그렇지 않다. 부모 프로세스가 자식의 종료 결과를 확인할 수 있도록 일부 정보를 남겨둔다. 이 종료 처리 과정을 이해하면 좀비 프로세스와 고아 프로세스의 차이도 쉽게 구분할 수 있다.
이 글은 Unix/Linux 환경을 기준으로 설명한다. 예시는 C++로 작성하지만 fork(), wait(), waitpid()는 ISO C++ 표준이 아닌 POSIX API다.
1. 핵심 차이
| 좀비 프로세스 | 고아 프로세스 | |
| 자식의 상태 | 이미 종료됨 | 실행 중 |
| 부모의 상태 | 살아 있지만 종료 정보를 회수하지 않음 | 자식보다 먼저 종료됨 |
| CPU 실행 | 실행하지 않음 | 계속 실행할 수 있음 |
| 남아 있는 자원 | PID와 종료 상태 등 최소 정보 | 실행에 필요한 프로세스 자원 |
| 처리 방법 | 부모가 wait()로 회수 | 다른 프로세스에 재부모화 |
한 문장으로 정리하면 다음과 같다.
- 좀비 프로세스는 죽었지만 부모가 종료 정보를 회수하지 않은 자식이다.
- 고아 프로세스는 부모를 잃었지만 아직 살아서 실행 중인 자식이다.
2. 자식 프로세스는 어떻게 생성되는가?
Unix/Linux에서는 프로세스가 fork()를 호출해 자식 프로세스를 만들 수 있다.
#include <iostream>
#include <unistd.h>
int main()
{
const pid_t pid = fork();
if (pid < 0)
{
std::cerr << "fork failed\n";
return 1;
}
if (pid == 0)
{
std::cout << "child process\n";
}
else
{
std::cout << "parent process, child pid: "
<< pid << '\n';
}
}
fork()의 반환값은 실행 중인 프로세스에 따라 다르다.
- 음수면 자식 생성에 실패한 것이다.
- 0이면 현재 실행 경로는 자식 프로세스다.
- 양수면 현재 실행 경로는 부모 프로세스이며, 반환값은 자식의 PID다.
fork() 이후에는 부모와 자식이 각각 실행된다. 어느 쪽이 먼저 실행될지는 스케줄러가 결정하므로 실행 순서는 보장되지 않는다.
3. 자식이 종료되면 어떤 일이 일어나는가?
자식이 exit()를 호출하거나 main()에서 반환하면 실행이 끝난다. 운영체제는 코드, 데이터, 힙, 스택, 열린 파일 등 자식이 사용하던 대부분의 자원을 정리한다.
그러나 운영체제 커널이 관리하는 커널 영역의 데이터 바로 제거하지는 않는다.
- 자식의 PID
- 정상 종료 여부
- 종료 코드
- 종료시킨 시그널
- 일부 자원 사용 정보
부모가 자식 작업의 성공 여부를 확인해야 할 수 있기 때문이다. 부모는 wait() 또는 waitpid()를 호출해 이 정보를 가져간다. 이를 자식 프로세스를 회수한다(reap)고 표현한다.
자식 실행
→ 자식의 exit()
→ 대부분의 자원 반환
→ 종료 정보 보존
→ 부모의 wait()
→ 종료 정보 제거
이 흐름에서 부모가 마지막 wait()를 수행하지 않으면 좀비 프로세스가 발생한다.
4. 좀비 프로세스
정의
좀비 프로세스(Zombie Process)는 자식이 이미 종료됐지만 부모가 종료 상태를 회수하지 않아 프로세스 테이블에 정보가 남아 있는 상태다.
프로세스 테이블은 운영체제가 프로세스들을 관리하기 위한 정보의 집합이며, 각 프로세스의 상태와 자원 정보를 담는 단위를 개념적으로 PCB라고 한다.
부모 프로세스: 실행 중, wait()를 호출하지 않음
│
└── 자식 프로세스: 이미 종료됨
│
└── 종료 정보가 남음
↓
좀비 프로세스
좀비는 실행 중인가?
아니다. 좀비는 이미 실행을 끝냈으므로 CPU에서 명령어를 실행하지 않는다. 코드, 힙, 스택과 같은 실행 자원도 대부분 반환됐다. 부모에게 전달할 PID와 종료 상태 등 최소한의 커널 정보만 남아 있다.
왜 문제가 되는가?
좀비 하나가 차지하는 자원은 크지 않다. 문제는 계속 누적될 때 발생한다. 좀비가 프로세스 테이블 항목과 PID를 점유하므로 대량으로 쌓이면 새로운 프로세스를 생성하지 못할 수 있다.
게임 서버가 작업용 자식 프로세스를 반복해서 생성하면서 종료 상태를 회수하지 않는다면 시간이 지날수록 좀비가 누적될 수 있다.
좀비 프로세스 예제
#include <chrono>
#include <iostream>
#include <thread>
#include <unistd.h>
int main()
{
const pid_t pid = fork();
if (pid < 0)
{
std::cerr << "fork failed\n";
return 1;
}
if (pid == 0)
{
std::cout << "child exits\n";
return 0;
}
// 부모가 wait()를 호출하지 않고 살아 있다.
std::cout << "child pid: " << pid << '\n';
std::this_thread::sleep_for(std::chrono::seconds(30));
}
자식은 바로 종료하지만 부모는 30초 동안 자식의 상태를 회수하지 않는다. 이 시간에 자식은 좀비 상태로 남을 수 있다.
Linux에서는 다음과 같이 확인할 수 있다.
ps -o pid,ppid,state,stat,cmd -p <자식_PID>
상태가 Z로 표시되거나 명령 이름에 <defunct>가 붙으면 좀비 상태다.
좀비 프로세스 해결 방법
부모가 wait() 또는 waitpid()를 호출해야 한다.
#include <sys/wait.h>
int status = 0;
const pid_t result = waitpid(childPid, &status, 0);
if (result == childPid && WIFEXITED(status))
{
const int exitCode = WEXITSTATUS(status);
}
status는 종료 코드 자체가 아니다. 정상 종료 여부를 WIFEXITED로 확인한 뒤 WEXITSTATUS로 종료 코드를 가져와야 한다.
5. 고아 프로세스
정의
고아 프로세스(Orphan Process)는 자식이 아직 실행 중인데 부모가 먼저 종료된 상태다.
부모 프로세스: 먼저 종료됨
│
└── 자식 프로세스: 계속 실행 중
↓
고아 프로세스
고아 프로세스는 누가 관리하는가?
운영체제는 고아 프로세스를 새로운 부모에게 재부모화(reparenting)한다. 전통적인 Unix 설명에서는 PID 1인 init 프로세스(컴퓨터가 부팅되면 가장 먼저 실행되는 사용자 공간 프로세스)가 새로운 부모가 된다. 현대 Linux에서는 systemd가 PID 1인 경우가 많다.
더 정확히는 가장 가까운 서브리퍼(subreaper)가 고아를 먼저 넘겨받을 수 있으며, 적절한 서브리퍼가 없다면 PID 1에 재부모화된다.
*서브리퍼(Subreaper)는 리눅스 커널(Linux Kernel 3.4 이상)에서 부모 프로세스가 죽어 고립된 자식(또는 자손) 프로세스를 대신 거두어 관리(입양)하는 보조 관리자 프로세스를 뜻함.
재부모화 전
부모 A
└── 자식 B
부모 A가 종료된 후
PID 1 또는 서브리퍼
└── 자식 B
새 부모는 자식이 나중에 종료했을 때 wait()를 호출해 종료 상태를 회수한다.
고아 프로세스 예제
#include <chrono>
#include <iostream>
#include <thread>
#include <unistd.h>
int main()
{
const pid_t pid = fork();
if (pid < 0)
{
std::cerr << "fork failed\n";
return 1;
}
if (pid > 0)
{
std::cout << "parent exits\n";
return 0;
}
std::cout << "before ppid: " << getppid() << '\n';
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "after ppid: " << getppid() << '\n';
}
부모가 먼저 종료된 후 자식에서 getppid()를 다시 호출하면 부모 PID가 달라질 수 있다.
고아 프로세스는 문제인가?
고아 프로세스 자체는 일반적으로 오류가 아니다. 새로운 부모가 지정되고 자식은 정상적으로 실행할 수 있다. 과거에는 부모와 분리된 데몬 프로세스를 만들 때 이런 특성을 활용하기도 했다.
다만 부모 서버가 비정상 종료했는데 작업 프로세스가 계속 실행되면 의도하지 않은 중복 작업이나 자원 점유가 발생할 수 있다. 실무에서는 서비스 관리자, 프로세스 그룹, 종료 시그널과 타임아웃을 이용해 부모와 자식의 종료 정책을 명확히 설계해야 한다.
좀비 프로세스 vs 고아 프로세스
좀비 프로세스는 방치하면 문제가 될 수 있지만, 고아 프로세스는 운영체제가 정상적으로 처리하도록 설계되어 있기 때문에 일반적으로는 문제가 아니다.
마무리
자식이 먼저 종료 + 부모가 회수하지 않음 = 좀비 프로세스
부모가 먼저 종료 + 자식이 계속 실행함 = 고아 프로세스
두 개념을 암기하기보다 프로세스의 종료 흐름을 이해하는 것이 중요하다. 자식의 exit()은 실행을 끝내고 종료 정보를 남긴다.
부모의 wait()는 그 정보를 회수해 자식을 완전히 정리한다. 부모가 회수하지 않으면 좀비가 되고, 부모가 먼저 사라지면 실행 중인 자식은 고아가 되어 새로운 부모에게 재부모화된다.
참고 자료
'CS > 운영체제' 카테고리의 다른 글
| 스택 오버플로우 (0) | 2026.07.29 |
|---|---|
| 교착 상태 (Deadlock) 해결법 — 예방, 회피, 검출 후 회복 (0) | 2026.07.26 |
| 교착 상태 (Deadlock) — 자원 할당 그래프와 네 가지 발생조건 (0) | 2026.07.26 |
| std::mutex와 Windows API Mutex의 차이 (0) | 2026.07.26 |
| 프로세스 동기화 기법 — 뮤텍스 락과 세마포 (0) | 2026.07.26 |