이화여자대학교 반효경 교수님의 2014년 Operating System 강의를 시청한 후 정리한 내용이다.
http://kocw.net/home/cview.do?cid=3646706b4347ef09
프로세스는 부모 프로세스로부터 생성되어 부모-자식 관계가 형성되어 트리 형태의 계층을 이룬다(모든 프로세스의 조상은 init
이다). 프로세스의 생성 시 커널의 도움을 받는다. 생성된 자식 프로세스는 OS의 자원을 받아서 실행되는데, 이때 부모와의 자원 공유 여부에 따라 3가지로 나뉜다.
시스템 콜 fork()
를 사용하여 자식 프로세스를 생성하면, 자식의 메모리 공간에 부모의 binary나 data들을 그대로 복사한다. 그러나 자식 프로세스가 부모와 전혀 다르게 실행되길 바란다면 exec()
시스템 콜을 통해 자식의 메모리 공간에 새로운 프로그램의 binary를 올릴 수 있다.
(자식)프로세스는 exit()
시스템 콜을 이용하여, 종료 상태를 부모에게 넘기고 메모리와 버퍼 등의 자원을 반납하지만, 종료 상태와 PID는 커널에 아직 존재한다(task_struct). 이때 부모는 wait()
시스템 콜을 호출하여 종료 상태를 받고 자식이 종료된다. 이와 같이 프로세스가 자발적으로 종료할 수도 있지만, 모든 일을 끝내지 못하고 비자발적으로 종료하는 경우가 존재하는데, 부모가 자식을 종료하는 상황은 아래와 같은 것들이 있다.
위 3번째의 경우는 고아 프로세스(Orphan Process)
에 관한 내용이다. 고아 프로세스는 자식 프로세스보다 부모 프로세스가 먼저 exit()하여 부모를 잃는 프로세스를 말하는데, 이때 init이 해당 자식을 입양한 후 init 내부에서 주기적으로 호출되는 wait()을 통해 자식이 종료된다. 전체적으로 부모가 종료되기 전에 그 아래의 자식도 종료되는 흐름인데, 이를 cascading termination
이라 한다.
이 외에도 Interrupt(kill, break)
로 인해 비자발적으로 종료될 수 있는데, 이때 abort()
시스템 콜을 호출하여 프로세스가 비정상적으로 종료된다. 항상 부모 프로세스가 종료하기 전에, 자식 프로세스가 먼저 종료한다.
Copy On Write: 먼저 자식 프로세스를 생성할 때에는 바로 부모의 복사본을 만들지 않고 서로 공유하다가, 자식과 부모가 서로 다른 코드를 실행하여 서로 다른 스택을 생성하거나 데이터가 변동되는 시점에 부모의 복사본을 자식에 copy한다.
fork()
는 프로세스의 메모리를 복제하여 binary가 동일한 자식 프로세스를 생성하는 시스템 콜이다. 함수의 반환 값은 부모 관점에서 자식의 PID를, 자식 관점에서 0을 반환한다. 복사 생성된 자식은 부모의 context를 물려받아 PC가 fork()함수 호출 이후를 가리키므로 그 시점부터 실행하게 된다.
exec()
시스템 콜을 호출하면 해당 프로세스에는 인자로 지정한 새로운 바이너리가 메모리에 덮어쓰이며, 새로운 프로세스로 실행한다. 만약 임의의 바이너리를 실행하는 프로세스를 생성하고 싶다면, fork()를 호출하여 새로운 프로세스를 생성하고, exec()을 이용하여 원하는 바이너리를 실행하도록 구현할 수 있다.
자식이 완전히 종료하기 위해서는 부모 프로세스는 자식의 종료 상태를 받아야 한다. wait()
시스템 콜은 자식의 종료 상태를 받을 때까지 block하는 함수이고, 자식이 종료하면 해당 함수는 종료된 프로세스의 PID를 반환한다. init은 고아 프로세스를 거두어 종료시키기 위해 주기적으로 wait() 시스템 콜을 호출하고 있다.
프로세스를 종료할 때는 exit()
시스템 콜을 사용하는데, 프로그래머가 명시를 하지 않아도 컴파일러가 main() 함수의 끝에 해당 시스템 콜을 추가한다. 프로세는 이 함수의 인자를 이용하여 부모에게 종료 상태를 전달할 수 있다.
기본적으로 프로세스는 자원을 얻기 위해 서로 경쟁하지만, 필요한 경우에 자원을 공유할 수 있다. 이때 사용하는 프로세스 간 협력 메커니즘 중 대표적으로 IPC가 있다.