운영체제 용어 사전
프로세스fork · exec · PCB · fork+exec

fork·exec

프로세스를 복제하고 다른 프로그램으로 덮어쓰는 두 시스템 콜. 유닉스 프로세스 생성 모델.

유닉스가 프로세스를 만드는 방식 — 복제(fork)한 뒤 덮어쓴다(exec).

pid_t pid = fork();          // 자신을 복제한다
if (pid == 0) {
    execl("/bin/ls", "ls", NULL);   // 자식이 다른 프로그램으로 바뀐다
} else {
    wait(NULL);              // 부모는 자식을 기다린다
}

fork가 한 번 호출되고 두 번 반환한다

부모에게는 자식의 PID 를 반환 자식에게는 0 을 반환

이 반환값으로 자신이 부모인지 자식인지 구분한다

왜 두 단계로 나눴나

합쳐 놓으면(예: CreateProcess) 유연성이 없다 fork 와 exec 사이에서 자식의 환경을 조정할 수 있다

  • fork();
  • // 자식에서: 표준출력을 파일로 리다이렉트, 권한 낮추기, 환경변수 설정
  • exec(...);

셸의 파이프·리다이렉션이 전부 이 틈에서 이뤄진다

copy-on-write가 비용을 없앤다

  • fork 가 주소 공간을 전부 복사하면 매우 비싸다
  • 실제로는 페이지 테이블만 복사하고 물리 페이지는 공유한다
  • 쓰기가 일어나는 순간에만 그 페이지를 복사한다

exec 로 곧바로 덮어쓸 거라면 복사 자체가 낭비이므로 이 최적화가 특히 효과적이다

좀비와 고아

  • 좀비 — 자식이 끝났는데 부모가 wait 을 안 했다

    • 종료 상태를 보관하느라 PCB 가 남는다 → PID 고갈 위험
  • 고아 — 부모가 먼저 죽었다

    • init(PID 1) 이 입양해 대신 wait 해 준다

컨테이너에서 애플리케이션이 PID 1 이 되면 좀비를 거두지 않아 쌓인다 → tini 같은 init 을 넣는 이유

PCB 에는 무엇이 들어 있나

  • PID · 부모 PID · 사용자 ID

  • 프로세스 상태 (실행·준비·대기·좀비)

  • 프로그램 카운터 · 레지스터 저장 영역

  • 페이지 테이블 포인터 (주소 공간)

  • 열린 파일 테이블 · 현재 작업 디렉터리

  • 스케줄링 정보 (우선순위 · 누적 실행 시간)

  • 시그널 처리 정보

  • fork 는 이 구조체를 복제하고 PID 만 새로 발급한다

무엇이 상속되고 무엇이 안 되나

  • 상속된다 — 열린 파일 디스크립터(오프셋까지 공유) · 환경변수 ·
    • 작업 디렉터리 · 시그널 핸들러 설정 상속 안 된다 PID · 부모 PID · 실행 시간 통계 · 대기 중인 시그널 ·

    • 파일 락

exec 를 하면 주소 공간이 통째로 교체되므로 코드·데이터·힙·스택은 사라진다 하지만 파일 디스크립터는 살아남는다 (FD_CLOEXEC 를 켜지 않는 한)

  • 셸이 리다이렉션을 fork 와 exec 사이에서 설정할 수 있는 이유

posix_spawn 과 vfork

fork 는 프로세스가 거대하면 페이지 테이블 복사만으로도 부담이 된다. 곧바로 exec 할 것이 확실하면 posix_spawn(내부적으로 최적화된 경로)이나 vfork(주소 공간을 아예 공유)를 쓴다. 자바의 ProcessBuilder가 힙이 큰 JVM 에서 vfork/posix_spawn 경로를 택하는 것도 같은 이유다.

면접 함정

  • "fork는 주소 공간을 전부 복사한다" → COW로 지연된다.
  • "좀비는 메모리를 많이 쓴다" → PCB만 남는다. 문제는 PID 고갈이다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 프로세스 — 주소공간·fork/exec·컨텍스트 스위치