참고 자료
[책] 운영체제 아주 쉬운 세 가지 이야기
5장 프로세스 API 개요
이번 절에서는 Unix 시스템의 프로세스 생성에 관해 논의한다. Unix는 프로세스를 생성하기 위하여 fork()와 exec() 시스템 콜을 사용한다. wait()는 프로세스가 자신이 생성한 프로세스가 종료되기를 기다리기 원할 때 사용된다.
프로세스를 생성하고 제어하려면 운영체제가 어떤 인터페이스를 제공해야 하는가?
유용하고 편하게 사용하기 위해서 이 인터페이스는 어떻게 설계되어야 하는가?
5.1 fork() 시스템 콜
결과
실행이 시작될 때 프로세스는 hello world ... 메시지를 출력한다. 이 메시지에는 PID(프로세스 식별자, process identifier)가 포함된다. 해당 프로세스는 29146이라는 PID를 가진다. Unix 시스템에서는 PID를 통해 특정 프로세스를 지칭한다.
프로세스는 fork() 시스템 콜을 호출한다. 운영체제는 프로세스 생성을 위해 이 시스템 콜을 호출한다. 생성된 프로세스는 호출한 프로세스의 복사본이다. fork() 호출 직후를 보면 운영체제 입장에서는 프로그램 p1이 2개 존재한다. 새로 생성된 프로세스 즉, 자식 프로세스는main()함수 첫 부분부터 시작하지 않았다. 자식 프로세스는 fork()를 호출하면서부터 시작되었다.
자식 프로세스는 부모 프로세스와 완전히 동일하지는 않는다. 자식 프로세스는 자신의 주소 공간, 자신의 레지스터, 자신의 PC 값을 갖는다. fork() 시스템 콜의 반환 값이 서로 다르다. 부모 프로세스는 생성된 자식의 프로세스의 PID를 반환받고, 자식 프로세스는 0을 반환받는다.
참고로 반환값은 커널이 결정한다.
실행 결과는 CPU 스케줄러에 따라서 자식 프로세스 또는 부모 프로세스 실행 순서가 달라진다. 상당히 복잡하고 상황에 따라 다른 선택이 이루어지기 때문에 어느 프로세스가 먼저 실행된다라고 단정하는 것은 매우 어렵다. 이 비결정성(nondeterminism)으로 인해 멀티 쓰레드 프로그램 실행 시 다양한 문제가 발생한다.
5.2 wait() 시스템 콜
이 예제에서는 부모 프로세스는 wait() 시스템 콜을 호출하여 자식 프로세스 종료 시점까지 자신의 실행을 잠시 중지시킨다.
자식 프로세스가 종료되면 wait()는 리턴한다. wait() 호출을 위와 같이 코드에 추가하면 프로그램은 항상 동일한 결과를 출력한다.
출력 결과
출력 결과를 보면 항상 자식 프로세스가 부모 프로세스보다 먼저 실행된다. 자식 프로세스가 부모 프로세스보다 먼저 실행되면 당연히 자식 프로세스가 먼저 출력된다. 부모 프로세스가 먼저 실행되면 wait()호출을 통해서 부모 프로세스는 대기 상태로 바뀌고 자식 프로세스가 종료될 때까지 쭉 대기 상태로 기다린다.
5.3 exec() 시스템 콜
exec() 시스템 콜은 자기 자신이 아닌 다른 프로그램을 실행해야 할 때 사용된다. 8.2에서 fork() 시스템 콜은 자신의 복사본을 생성하여 실행한다. 자신의 복사본이 아닌 다른 프로그램을 실행해야 할 경우에는 exec() 시스템 콜이 그 일을 한다.
실행 결과
해당 코드에서는 자식 프로세스는 wc 프로그램을 실행하기 위해 execvp() 시스템 콜을 호출한다. execvp() 시스템 콜은 exec() 계열 함수 중 하나이다. exec() 시스템 콜은 다음과 같은 과정으로 수행된다. 실행 파일의 이름과(ex: wc)약간의 인자(ex: p3.c) 주어지면 해당 실행 파일의 정적 데이터를 읽어 들여 현재 실행 중인 프로세스의 코드와 정적 데이터 부분을 덮어 쓴다. 힙과 스택 및 프로그램 다른 주소 공간들로 새로운 프로그램의 실행을 위해 다시 초기화된다. 그런 다음 운영체제는 프로세스의 argv와 같은 인자를 전달하여 프로그램을 실행시킨다. 새로운 프로세스를 생성하지 않는다. 현재 실행 중인 프로그램(p3)을 다른 실행 중인 프로그램(wc)으로 대체하는 것이다. 자식 프로세스가 exec()을 호출한 후에는 p3.c는 전혀 실행되지 않은 것처럼 보인다. exec() 시스템 콜이 성공하게 되면 p3.c는 절대로 리턴하지 않는다.
5.4 왜, 이런 API를?
새로운 프로세스를 생성하는 간단한 작업 같은데 왜 이런 이상한 인터페이스를 사용할까?
Unix 쉘을 구현하기 위해서는 fork()와 exec()을 분리해야 한다. 그래야만 쉘이 fork()를 호출하고 exec()를 호출하기 전에 코드를 실행할 수 있다. 이때 실행하는 코드에서 프로그램의 환경을 설정하고 다양한 기능을 준비한다. 쉘은 단순한 사용자 프로그램이다. 대부분의 경우 쉘은 파일 시스템에서 실행 파일의 위치를 찾고 명령어를 실핵하기 위하여 fork()를 호출하여 자식 프로세스를 만든다. 그런 후 exec()의 변형 중 하나를 호출하여 프로그램을 실행시킨 후 wait()를 호출하여 명령어가 끝나기를 기다린다. 자식 프로세스가 종료되면 쉘은 wait()로부터 리턴하고 다시 프롬프트를 출력하고 다음 명령어를 기다린다.
이렇게 fork(), exec()을 분리함으로써 쉘은 많은 유용한 일을 조금 쉽게 할 수 있다.
실행 결과
p4를 실행하면 화면에 아무일도 일어나지 않는다. 프로그램 p4는 fork()를 호출하여 새로운 자식 프로세스를 생성하고 execvp()를 호출하여 wc 프로그램을 실행시킨다. 출력이 p4.output 파일로 재지정되었기 때문에 화면에는 아무것도 출력되지 않는다. 출력 파일을 cat해 보면 wc를 실행시켰을 때 얻을 수 있는 모든 출력이 파일에 지정되어 있는 것을 발견하게 될 것이다.
'Computer Sience > Operating System' 카테고리의 다른 글
[OS] 운영체제 아주 쉬운 세 가지 이야기 7장 스케줄링 (1) | 2025.04.07 |
---|---|
[OS] 운영체제 아주 쉬운 세 가지 이야기 6장 제한적 직접 실행 원리 (0) | 2025.04.01 |
[OS] 운영체제 아주 쉬운 세 가지 이야기 4장 프로세스의 개념 (0) | 2025.03.31 |
[OS] 운영체제 아주 쉬운 세 가지 이야기 2장 운영체제 개요 (0) | 2025.03.22 |
스레드 컨텍스트 스위칭 vs 프로세스 컨텍스트 스위칭 (0) | 2024.05.18 |