프로세스 생성 기법
운영체제는 새로운 프로세스를 생성하기 위해 보통 두 가지 주요 시스템 호출을 사용한다. 하나는 fork()
로 부모 프로세스를 복제하여 자식을 만들고, 다른 하나는 exec()
로 새로운 프로그램으로 메모리 공간을 교체하는 방식이다. 이 글에서는 fork()
와 exec()
가 어떻게 동작하는지, 왜 이런 방식으로 자식 프로세스가 만들어지고 실행되는지 좀 더 자세히 살펴본다.
1. fork()
— 부모 프로세스 복제
1.1 개념
fork()
는 부모 프로세스를 복제해 새로운 자식 프로세스를 생성하는 시스템 호출이다.- 자식 프로세스는 부모 프로세스의 대부분 리소스(메모리, 열린 파일 디스크립터, 환경 변수 등)를 상속받는다.
- PID(프로세스 식별자)는 새로 할당되므로, 부모와 자식은 서로 다른 PID를 가진다.
1.2 반환값과 흐름
fork()
를 호출하면, 부모 프로세스에는 자식의 PID가 반환되고, 자식 프로세스에는 0이 반환된다. 이렇게 두 프로세스가 동일한 코드 지점에서 실행을 이어가지만, 반환값을 통해 분기 처리가 가능하다.
pid_t pid = fork();
if (pid < 0) {
// fork 실패
} else if (pid == 0) {
// 여기는 자식 프로세스 흐름
} else {
// 여기는 부모 프로세스 흐름
}
1.3 Copy-on-Write 최적화
일부 운영체제(예: Linux)는 fork()
호출 시 메모리 페이지를 실제로 즉시 복사하지 않고, Copy-on-Write(COW) 기법을 사용한다.
- 동작 원리: 부모와 자식이 동일한 물리 메모리를 공유하면서 읽기만 할 때는 복사하지 않고, 어느 한 쪽에서 쓰기 연산이 발생하면 그 시점에 해당 페이지만 실제 복사한다.
- 장점:
fork()
호출 직후 바로exec()
를 진행하는 경우, 대량의 메모리를 복사할 필요가 없어 시스템 자원과 시간을 절약할 수 있다.
(그림 출처: 혼자 공부하는 컴퓨터 구조 + 운영체제)
2. exec()
— 새로운 프로그램으로 갈아입기
2.1 개념
exec()
는 현재 프로세스의 메모리 공간(코드·데이터·스택 등)을 새로운 프로그램으로 덮어쓰는 시스템 호출이다.- 여러 변형(
execl
,execv
,execvp
, 등)이 존재하며, 프로그램 경로와 인자 전달 방식 등에 차이가 있다.
2.2 동작 과정
- 프로세스(일반적으로 자식) 가
exec()
계열 함수를 호출한다. - 이전 코드와 데이터는 사라지고, 지정한 프로그램의 코드와 데이터를 메모리에 로드한다.
- 프로세스의 PID는 유지되지만, 실행 이미지가 완전히 바뀌어 새 프로그램을 시작한다.
- 성공 시
exec()
호출 이후의 코드는 절대 실행되지 않는다(프로그램이 새로 시작되므로).
2.3 메모리 영역 교체와 초기화
- 코드 영역: 새 프로그램의 기계어 코드로 교체
- 데이터 영역: 전역 변수, 초기화된 데이터, BSS(미초기화 데이터) 등이 새롭게 설정
- 스택 영역: 새 프로그램의 스택 초기 상태로 재구성
- 힙 영역: 새로 시작하므로 기존 동적 할당 정보는 무효화(초기화)
- 열린 파일: 일반적으로 기존에 열려 있던 파일 디스크립터 중 일부는 그대로 유지 가능(표준 입출력 등)
(그림 출처: 혼자 공부하는 컴퓨터 구조 + 운영체제)
3. 예시: bash 셸에서 ls
실행
- 사용자가 셸(bash)에서
ls
명령을 입력한다. - 셸 프로세스는
fork()
를 호출하여 자신의 복제본(자식 프로세스)을 만든다. - 새로 만들어진 자식 프로세스는
exec()
를 호출해ls
프로그램을 메모리에 로드한다. - 자식 프로세스는 이제 셸 코드가 아닌
ls
코드를 수행하면서 디렉토리 목록을 출력한다. - 자식 프로세스는 작업을 마치고 종료(
exit()
), 부모(셸)는 기존 코드로 돌아가 다음 명령을 기다린다.
(그림 출처: 혼자 공부하는 컴퓨터 구조 + 운영체제)
4. 부모 프로세스, 자식 프로세스가 동일 코드를 실행하는 경우
fork()
후에 자식 프로세스가 exec()
를 호출하지 않는 상황도 있다. 이 경우 자식 프로세스는 부모와 동일한 코드를 동시에 병렬로 수행하게 된다.
- 대표적 사례: 서버 프로그램에서 여러
fork()
호출을 통해 자식 프로세스를 다수 생성하고, 자식들도 동일한 로직(예: 클라이언트 연결 처리)을 병렬로 수행. - 주의: 전역 변수, 파일 핸들 등의 자원은 서로 별도로 존재(복사되거나 COW 공유)하므로, 동기화/충돌 처리를 고려해야 한다.
5. 프로세스 계층 구조와 반복
운영체제에서 부모가 자식을 생성하고(= fork()
), 자식이 새로운 프로그램을 로드해 실행(exec()
)하면서, 프로세스 계층 구조가 형성된다.
- 부모 프로세스 → 자식 프로세스 → (또 다른) 자식 프로세스 …
- 각 자식은 필요에 따라 새 프로그램을 로드해 별도의 작업을 수행할 수 있다.
결과적으로, 부팅 시점에 생성된 최초 프로세스(PID 1)에서 시작해, 무수히 많은 프로세스들이 이러한 fork()
& exec()
과정을 반복함으로써 다양한 프로그램이 동시에 실행된다.
6. 요약
fork()
- 부모 프로세스를 복제하여 자식 프로세스를 생성하는 시스템 호출.
- 부모의 메모리·파일 디스크립터·환경 등을 상속받는다.
- Copy-on-Write 최적화를 통해 효율적 메모리 사용 가능.
exec()
- 현재 프로세스(대개 자식)의 메모리 공간을 새로운 프로그램으로 교체.
- PID는 그대로 유지되지만, 실행 코드와 데이터는 전혀 다른 내용으로 덮어쓰여진다.
- 실행 흐름
fork()
직후에는 부모·자식이 동일 코드를 실행한다(반환값으로 분기).- 자식이 곧바로
exec()
를 호출해 새 프로그램으로 옷을 갈아입으면 새로운 프로세스로 동작한다. exec()
를 사용하지 않으면 부모와 동일 코드를 병렬로 실행하기도 한다.
참고 자료
- 혼자 공부하는 컴퓨터 구조 + 운영체제
- Operating System Concepts (A. Silberschatz, P.B. Galvin, G. Gagne)
- Modern Operating Systems (A.S. Tanenbaum, H. Bos)
- Linux
fork()
manual, Linuxexec()
manual
'운영체제 > 프로세스 상태와 계층 구조' 카테고리의 다른 글
프로세스 계층 구조 (Process Hierarchy) (0) | 2025.01.18 |
---|---|
프로세스 상태 (0) | 2025.01.16 |