리눅스 fork
IT 위키
- 현재 프로세스를 복제해 자식 프로세스를 만드는 시스템 호출
유닉스·리눅스에서 새 프로세스는 오직 기존 프로세스를 복제해서만 만들어진다. 부팅 직후의 init(또는 systemd) 하나에서 시작해 fork 를 거듭한 결과가 지금의 프로세스 트리다.
- 호출한 프로세스(부모)의 메모리 이미지, 열린 파일 디스크립터, 환경 변수를 그대로 복사해 자식 프로세스를 만든다.
- 자식은 새로운 PID 를 받고, 자식의 PPID 는 부모의 PID 가 된다.
- 반환값이 둘이다. 이것이 fork 의 특징이다.
- 부모에게는 자식의 PID 를 돌려준다.
- 자식에게는 0 을 돌려준다.
- 실패하면 −1 을 돌려준다.
- 실제 구현은 메모리를 통째로 복사하지 않고 쓰기 시 복사(Copy-on-Write)를 써서, 어느 쪽이든 메모리를 고칠 때 비로소 복사한다.
pid_t pid = fork();
if (pid < 0) { /* 실패 */ }
else if (pid == 0) { /* 자식 프로세스 */ }
else { /* 부모 프로세스, pid 는 자식의 PID */ }
- fork 는 프로세스를 복제만 한다. 자식은 여전히 부모와 같은 프로그램을 실행한다.
- exec(execve, execl, execvp …) 는 새 프로세스를 만들지 않고 현재 프로세스의 메모리 이미지를 다른 프로그램으로 덮어쓴다. PID 는 그대로다.
- 그래서 셸이 명령을 실행하는 과정은 fork 로 자신을 복제한 뒤 자식에서 exec 로 대상 프로그램을 덮어쓰는 형태다.
셸(bash, PID 1000)
└ fork() → 자식(PID 1001, 여전히 bash)
└ exec("/bin/ls") → 같은 PID 1001 이 ls 가 된다
- wait() / waitpid() : 부모가 자식의 종료를 기다리고 종료 상태를 회수한다.
- 좀비 프로세스 : 자식은 끝났는데 부모가 종료 상태를 회수하지 않아 프로세스 표에만 남은 상태.
ps에Z또는defunct로 보인다. - 고아 프로세스 : 부모가 먼저 죽은 자식. init(PID 1)이 입양해 뒤처리를 맡는다.
- 포크 폭탄 : fork 를 무한히 반복해 프로세스 표를 고갈시키는 공격.
/etc/security/limits.conf의nproc으로 사용자별 프로세스 수를 제한해 막는다. - vfork : 메모리를 복사하지 않고 곧바로 exec 하는 용도의 변형. clone 은 리눅스가 스레드까지 포괄해 제공하는 더 일반적인 시스템 호출이다.
