프로세스를 만들고 제어하는 기술들 - fork, exec, wait 완전 정복
지금까지는 예외와 프로세스 개념을 이론적으로 살펴봤는데요. 이제는 직접 프로세스를 만들고 관리하는 시스템 호출들을 본격적으로 다룰 차례입니다.
이번 절에서는 CSAPP 8.4절 내용을 바탕으로 프로세스의 생성, 종료 및 회수, 실행 이미지 교체, 부모/자식 관계 처리 등을 모두 정리합니다. 이를 통해 운영체제가 프로세스를 생성, 실행, 종료하는 과정을 실제 코드로 확인하게 됩니다.
프로세스 ID 관련 시스템 호출 (getpid, getppid)
프로세스는 항상 고유한 PID(Process ID)를 가지며, 부모-자식 관계 역시 이 PID를 통해 식별됩니다. C 언어에서는 이러한 PID 정보를 얻기 위해 getpid()
와 getppid()
함수를 사용할 수 있습니다.
// getpid(): 현재 프로세스의 PID를 반환하는 함수
pid_t getpid(void);
// getppid(): 부모 프로세스의 PID를 반환하는 함수
pid_t getppid(void);
프로세스 생성 (fork)
fork()
는 실행 중인 프로세스를 그대로 복제하여 자식 프로세스를 생성합니다. C 언어에서는 이 함수를 직접 호출해 새로운 프로세스를 만들 수 있습니다.
// fork(): 현재 프로세스를 복제하여 자식 프로세스를 생성하는 함수
// 부모 프로세스에는 자식의 PID를, 자식 프로세스에는 0을 반환 (실패 시 -1 반환)
pid_t pid = fork();
동작 결과
반환값 | 의미 |
0 | 자식 프로세스 |
양수 (자식 PID) | 부모 프로세스 |
-1 | 실패 |
부모/자식 프로세스는 거의 완벽히 동일한 복사본으로 시작되며, PID와 fork()
의 반환값만 달라집니다.
fork() 사용 예시
pid_t pid = fork();
if (pid == 0) {
// 자식 프로세스일 때
printf("I am child, pid = %d\n", getpid());
} else {
// 부모 프로세스일 때
printf("I am parent, child pid = %d\n", pid);
}
프로세스 종료 (exit)
exit()
는 프로세스를 종료하고, 커널에 종료 상태 코드를 남기는 함수입니다. 이 상태 코드는 이후 부모 프로세스가 wait()
함수를 통해 수거(회수) 하게 됩니다. 또한, exit()는 운영체제에 종료 사실을 알리고, 열린 파일이나 메모리 등 자원을 정리하도록 요청하는 역할을 합니다.
// exit(status): 현재 프로세스를 종료시키는 함수
// status 값은 부모 프로세스가 자식의 종료 상태를 확인할 때 사용됨
// 종료 후 열린 파일, 메모리 등은 커널이 정리
void exit(int status);
자식 프로세스 청소 (wait, waitpid)
자식 프로세스가 종료되었지만 부모가 아직 수거하지 않아 커널에 정보가 남아 있는 상태를 좀비 프로세스(zombie process)라고 합니다. 이런 좀비 상태를 방지하려면, wait()
함수를 통해 커널로부터 자식의 종료 상태 코드를 회수해야 합니다.
int status;
pid_t pid = wait(&status); // 종료된 아무 자식 프로세스를 수거할 때까지 대기
// 종료된 자식의 PID를 반환
// 종료 상태는 status에 저장됨 (WIFEXITED 등으로 해석 가능)
또는 waitpid()
함수를 사용하면, 종료된 자식 중 특정 PID를 지정해 회수할 수 있습니다.
pid_t pid = waitpid(child_pid, &status, 0); // 특정 자식 프로세스 수거
프로세스 재우기 (sleep, pause)
프로세스를 재우는 함수들도 존재하는데요. 시간 기반으로 대기하게 하거나, 특정 시그널을 기다리게 할 때 이용할 수 있습니다.
sleep(3); // 현재 프로세스를 3초 동안 일시 정지 (지연)
pause(); // 시그널을 받을 때까지 무기한 대기 (신호가 와야 깨어남)
다른 프로그램 실행 (execve, exec 계열)
exec()
계열 함수는 현재 프로세스의 실행 이미지 자체를 교체합니다. 즉, 새로운 프로그램이 이 자리를 차지하고, 이전 코드는 완전히 사라지게 되는 것이죠.
execve("/bin/ls", argv, environ); // 새로운 프로그램(/bin/ls)으로 현재 프로세스를 덮어씀
// argv: 전달할 인자 목록 (예: {"ls", "-l", NULL})
// environ: 환경 변수 목록 (전역변수 extern char **environ 사용)
execve()
는 성공하면 기존 프로세스를 새로운 프로그램으로 완전히 대체하기 때문에, 호출한 함수로 절대 돌아오지 않습니다. 하지만 실패할 경우에는 errno를 설정하고 -1을 반환합니다.
exec 계열 함수
함수 | 설명 |
execl() | 인자 나열형 |
execv() | 인자 배열형 |
execle() | 환경변수 포함 |
execvp() | PATH 검색 포함 |
execve() | 가장 일반적인 호출 함수 |
새로운 프로세스 실행 (fork + exec)
새로운 프로세스를 실행할 때는 fork()
와 exec()
함수를 함께 사용하는 패턴이 가장 일반적입니다. fork()
로 자식 프로세스를 만든 뒤, 자식 프로세스 안에서 exec()
로 새로운 프로그램을 실행하는 방식입니다.
이를 C 코드 예시로 나타내면 다음과 같습니다.
pid_t pid = fork();
if (pid == 0) {
// 자식 프로세스: 새로운 프로그램 실행
execve("/bin/ls", argv, environ);
perror("execve failed"); // execve 실패 시에만 실행됨
exit(1);
} else if (pid > 0) {
// 부모 프로세스: 자식 기다림
wait(NULL);
}
쉘(shell)도 명령어 실행 시 fork → exec → wait 구조로 동작합니다.
핵심 요약
호출 | 역할 |
fork() | 현재 프로세스를 복제 |
getpid(), getppid() | 프로세스 관계 확인 |
exit() | 프로세스 종료 |
wait(), waitpid() | 자식 회수 (좀비 프로세스 방지) |
sleep(), pause() | 프로세스 일시 정지 |
exec*() | 새 프로그램으로 이미지 교체 |
'크래프톤 정글 > 컴퓨터구조(CSAPP)' 카테고리의 다른 글
[CSAPP 8장 완전 정복] 8.6 비지역 점프 - setjmp, longjmp로 흐름 전환하기 (0) | 2025.04.21 |
---|---|
[CSAPP 8장 완전 정복] 8.5 시그널, 비동기 흐름의 시작 (0) | 2025.04.21 |
[CSAPP 8장 완전 정복] 8.1~8.3 예외와 프로세스, 시스템의 흐름을 바꾸는 힘 (0) | 2025.04.19 |
[CSAPP 7장 완전 정복] 7.14~7.15 오브젝트 파일 분석과 실행 흐름 마무리 (0) | 2025.04.18 |
[CSAPP 7장 완전 정복] 7.13 라이브러리 삽입 완전 이해하기 (0) | 2025.04.18 |