05. CPU-API ,
# 시작하며
UNIX 시스템에서 프로세서를 생성하기 위해선 fork() 와 exec()를 사용한다. wait()를 생성하면 자신이 생성한 자식프로세서의 종료를 기다린다, 자식의 프로세서가 종료된다면 그 다음 코드들이 시작된다. 위의 시스템콜(syscall)을 사용하여 프로세스를 생성하고 제어하는 방법에 알아본다. 시스템 콜에대해서는 아마 다음장에서 나오는 것으로 기억하는데, 쉽게 말해 더 낮은 레벨에서 사용할 수 있는 명령어들이다. 이 명령어를 남발하면 해당 프로세스 뿐 아니라 모든 메모리에 접근할 수 있기 때문에 매우 위험하기 때문에 분리시켜 놓은 특수한 명령어 이다. 이 명령어는 운영체제가 실행한다고 생각하면 된다.
# fork() 시스템 콜
결과를 보기전 간단하게 fork()에 대해 설명 해보겠다. 프로세서(현재 실행중인)가 fork()를 만나게되면 시스템 콜을 호출하여 운영체제의 아주 크나큰 권한으로 지정된 행동을 하는데, fork()는 새로운 프로세스를 만든다. 그런데 그냥 프로세스가 아니라 호출한 프로세서의 똑같은 복사본을 만든다. 그 똑같은 복사본은 대신 프로그램 카운터(Programe Counter == PC) 프로세서의 맨 처음이 아닌 fork()에서부터 시작된다. 자신이 생성됐던 그 코드이다.
이 프로세서는 자신만의 메모리공간과 자신만의 레지스터 PC값등을 당연히 가지게 된다. fork()는 반환값을 가지는데, 그 반환값으로는 부모 프로세스는 자식의 PID값을 반환받게 되고, 자식 프로세스는 0을 반환받게 된다. 그렇다면 자식과 부모의 rc의 값은 서로 다르게 되는 것이고 이걸 이용하여 설계한다.
if는 rc의 값에 따라 분기되고 있는데, 부모와 자식은 다른 rc값을 가지고 있기 때문에 서로 다른 if문으로 분기 돼 다른 결과를 출력하게 된다. 위의 결과와 같다. 그러나 출력 결과(순서)는 항상 동일 할 수 없다. 예를 들어 자식의 코드가 먼저 print되고 부모의 코드가 print되거나 그 반대일 수도 있다는 것인데, 이것은 CPU 스케줄러의 결정에 달리는 것이다. 이러한 비결정성(nondeterminism)을 가지기 때문에 멀티 쓰레드 프로그램 실행 시 다양한 문제가 발생한다.
# wait() 시스템 콜
비결정성을 가지는 스케줄러 때문에 나는 어떤 프로세스가 먼저 실행될지 조마조마한 상황을 대비하기 위해 wait()라는 시스템 콜이 존재한다. 즉 부모 프로세스가 자식 프로세스의 종료를 대기해야 하는 경우가 발생 할 수 있는 것이다.
wait()를 만나면 자식 프로세스가 종료되는 시점까지 자신의 실행을 잠시 중시 시킨다. 자식 프로세스가 종료되면 wait()는 리턴하게 되고 그 이후를 실행 할 수 있는 것이다. 그렇다면 wiat()시스템콜을 사용하여 자식이 실행되고 부모가 실행되는 항상 동일한 결과를 출력할 수 있을 것이다.
# exec() 시스템 콜
이 시스템 콜은 자기 자신이 아닌 다른 프로그램을 실행해야 할때 사용한다 . fork()는 현재 프로세스의 복사본을 사용한다고 했지만 exec()는 다른 프로그램을 실행 할 수 있는 것이다.
간단히 코드를 살펴보자, fork()를 만나 자식 프로세스와 부모프로세스로 분화 된다. 부모 프로세스는 wait()를 만나기 때문에 항상 자식 프로세스의 종료를 기다린다.
자식 프로세스는 execvp() 시스템 콜을 만난다. exec() 시스템 콜과 비슷한 기능을 한다. 그러면 인자로 받은, 위 코드에서는 "wc" 즉 execvp의 첫번째 매개변수의 파일을에 있는 코드와 정적 데이터를 읽어 들여 현재 실행중인(여기서는 자식 프로세스)의 코드 세그멘트와 정적 데이터 부분을 덮어 쓴다. 힙과 스택 및 프로그램 주소 공간들로 새로운 프로그램의 실행을 위해 다시 초기화 시킨다. 새로운 프로세스를 생성하지는 않는다. 다시 말해 현재 프로세스를 덮어씌운다, 마치 기생충과 같다.
현재 상황은 자식프로세스가 -> execvp를 호출한 상황이다. 기생충에 지배당해 자기 자신을 다 갉아 먹힌 자식프로세스는 사실상 껍데기만 존재한다. 없어졌다. 그렇기 때문에 execvp()이후 printf()코드는 실행되지 않는다. 그러나 만약 exec()의 명령이 실패한다면 -1을 리턴받고 그 다음 코드가 실행 될 것이다.
여튼 부모 프로세스는 자식프로세스( execvp()에 의해 완전히 새롭게 바뀐 )가 종료되면 wait()에 대해 리턴이 이루어지면서 마저 출력된다.
# 이러한 API가 만들어진 이유는?
사실 이러한 작업은 터미널에서 가장 유용하게 쓰인다고 한다. 아래와 같이 쓰인다는 것이다.
또 이렇게 exec()로 분리함으로써 쉘은 유용한 일을 조금 더 쉽게 할 수 있다.
아래의 예제는, wc라는 파일은 p3.c를 word count를 한다. 그리고 그 출력을 newfile.txt라는 파일을 만들어 저장하는 우리가 흔히 사용하는 커맨드 명령이다.
어떻게 newfile.txt에 출력이 되는 것일까? 바로 위에서 했던 exec()를 그대로 활용 하는 것이다. exec()로 표준 출력(stdout)파일을 닫고 newfile.txt을 열어놓은 후, exec()를 통하여 wc를 실행시키면 출력은 터미널화면 창이 아닌, newfile.txt 파일로 되기 때문이다.
# 끝내며
시스템 콜을 통해 프로세스가 어떻게 만들어지고 없어지는 등을 살펴보았다. 사실 교과서에는 그리 중요한 부분은 아니니 넘어가도 된다고 써 있긴 하다.
생각보다 오래 걸린다.. 아직 쉬운 단원인데 ㅠ 그래도 다시한번 머릿속에 정리되니까 좋다!
'•Compter Science > Operating System' 카테고리의 다른 글
[OS/OSTEP] 08.CPU-sched-mlfq : 멀티 레벨 피드백 큐(MLFQ) #5 (0) | 2022.04.18 |
---|---|
[OS/OSTEP] 07.CPU-sched : CPU 스케줄링 정책(scheduling policy) #4 (0) | 2022.04.18 |
[OS/OSTEP] 06.CPU-mechanisms : 제한적 직접 실행 원리 #3 (0) | 2022.04.18 |
[OS/OSTEP] 04.CPU-intro 프로세스의 개념 #1 (0) | 2022.04.17 |
[OS/OSTEP] 운영체제 공부를 시작하며 #0 (0) | 2022.04.17 |