지난 포스트에서 Process 를 이해하기 위한 State, Context Switch, PCB, Queue, Thread 등 Process 의 개념적인 측면에서 여러가지를 이해해볼 수 있었다. 이번 포스트에서는 이 Process가 운영이 될 때 어떤식으로 관리가 되는지, 어떤식으로 생성이되고 종료가 되고, 일하는 도중에는 무슨 일들이 일어나는지 확인해보자. 참고로 운영체제에서 배우는 Process Management 는 실제에 비해 매우 간소화해서 설명하였다고 이해하면 될 것이고, 이런 느낌으로 일어나는거구나를 이해하면 될 것 같다.
1. 프로세스 생성
운영체제에서 프로세스는 부모를 통해서 생성된다. 즉, 부모가 자식 프로세스를 생성하고, 자식 프로세스가 또 자식프로세스를 생성하는 Tree 구조(계층 구조)를 형성한다. 이런 관계에서 부모 프로세스는 자식 프로세스가 exit될 때까지 기다리기도 하고, 필요에 따라 kill 시키기도 하며, 공존하며 수행되기도 한다.
자식 프로세스를 생성할 때는 부모는 OS에게 System Call 요청을 통해 자신의 주소 공간을 그대로 복사하고, 자식의 주소공간을 형성시킨다. 참고로, 부모와 자식은 메모리 공유 측면에서 어떤 과정을 밟게 되는지 확인해볼 필요가 있다. 자식 프로세스는 부모의 주소 공간을 복사해서 갖게 되는데, 완전 동일한 static한 자원이 있다면 공유되어야 메모리를 효율적으로 사용하는 것이기 때문이다.
OS는 일단 자식의 주소 공간을 형성시키지 않고, 부모의 주소 공간을 공유하도록 설계를 한다고 한다. 그러다 자식의 특정 값이 달라지거나, (당연히) 다른 일을 수행하기 위해 다른 함수를 호출하여 stack이 달라지는 등 필요에 따라 다른 Memory가 필요할 때 그제서야 부모에서 공유하던 메모리 공간의 일부를 Copy해서 갖게 된다. 이를 COW (Copy On Write: Write 발생시 Copy 수행) 기법이라 부른다.
(1) 프로세스 관리를 위한 OS Function
FORK
위에서 말했듯이, 부모는 OS에게 자식 프로세스를 만들어달라고 System Call 을 하게 되는데, 이는 OS 함수 fork()이다. fork()는 새 프로세스를 생성하고, pid 값을 부모, 자식 프로세스드에 알맞게 반환한다. 다음 sample code를 확인해보자.
위 함수는 부모 프로세스의 cmd가 수행되는 모습이다. cmd가 지속 수행이 되다가 fork()가 수행되면, OS는 자식 프로세스를 생성하여 필요한 작업을 마친 뒤, (exec을 수행한뒤-다음에 나옵니다) Ready Queue에 해당 PCB를 줄세우게 되며, 부모 프로세스 (System Call 호출로 인해 IO wait State)도 다시 Ready State로 복귀하게 된다. 각 프로세스의 수행시점은 동일하게 pid = fork() 아래부터임을 명심하자. 각 프로세스는 수행 시점이 되면 부모 프로세스는 pid = 기존 pid, 자식 프로세스는 pid = 0 을 반환받게 되어(fork시점이라서 그럼), 각자 다른 if 문을 타게 된다. 부모 프로세스는 else 문을 타서 wait 를 하든지, 추후 진행을 하든지 하면 되는 것이고, 자식 프로세스는 자신의 수행을 진행하면 되는 것이다.
EXEC
fork는 자식 프로세스를 생성하고, OS가 셋업을 도와주게하는 OS function이다. 실제 자식 프로세스를 Ready State로 만들기 위해서는 알다시피 [Process Execution] 을 통해 Memory Loading이 되어야 하는데, 이를 실행시키는 것이 OS함수 exec() 이다. 자식 프로세스가 있는 이유는 다른 목적의 일들을 수행하기 위해서이다. 따라서 당연히 부모와 똑같은 코드만을 가지고 있을 수는 없고, 새로운 주소공간을 필요로 한다.
exec() 함수는 자식 프로세스가 완전히 새로운 프로그램으로 다시 태어나게 한다고 보면 된다. 가령, 위 sample에서 child가 exec 이후에도 수행할 내역이 있다고 생각해보자.
int main(){
printf("Child runs Date Program")
exec("/bin/date", "/bin/date", 0)
printf("Parent Continues ...")
}
child 의 if statement 내부가 위 코드와 같이 진행된다면, exec 이후로는 새로운 Date Program이 수행되고, 자식 프로세스가 가르키는 Memory 공간, instruction 위치가 부모 프로세스 (공유중)에서 벗어나게 되는 것이다. 그리고 새로운 주소 공간에서 새로운 Date Program이 수행되므로, "Parent Continues ..."라는 프린트는 절대 찍힐 수 없다. instruction 으로 들어올 수가 없기 때문이다. 따라서, fork() 수행시 부모 함수를 그대로 가져온 상태인 것이고 (부모의 주소공간을 공유중), exec()를 수행하면서 Memory Loading이 진행되어 처음부터 수행되는 새 프로그램을 가져오는 것이라고 이해하면 될 것이다.
WAIT & EXIT
wait() 함수는 현재 Process State을 Waiting State (Blocked)로 변경시키는 함수이다. 즉, Interrupt를 걸어 OS가 CPU를 가져오는, Kernel Mode 전환 함수라고 생각하면 된다. 지금과 같은 상황에서는 부모 프로세스가 자식의 프로세스 종료를 기다리기 위해 wait()을 수행하는 경우를 생각해볼 수 있다.
wait 함수에 대해 간단히 생각해볼만한 점은, 이 wait Process를 깨우는 일에 대한 기능이다. 내가 생각하기엔 (뇌피셜) wait() 함수는 목적이 "부모를 Block 하고 자식이 끝나면 깨우는 Callback이 구현되어 있는"이 아니라, 그냥 현 Process State을 Waiting State으로 전환시키는 함수로 보인다. 즉, 다른 IO 작업을 할 때도 동일한 OS 함수를 사용하여 Interrupt를 수행할 것 같다. 그리고 부모-자식 관계에서 자식이 exit을 하면 output-data가 부모로 전달되게끔 설계를 진행한다고 하는데, OS가 거기에서 callback을 인식하고 부모 프로세스를 깨우는 것 같다.
exit() 함수는 말 그대로 현재 프로세스를 종료시키는 것, 죽이는 것이다. 사실, Process가 수행이 끝나면 exit을 꼭 해줘야 하지만, 우리가 코딩 할 때 exit system call은 붙이진 않는다. 그 이유는 컴파일러가 main이 종료됨을 판단하면 자동으로 프로세스를 끝내도록 exit을 수행해주기 때문이다. 이 때, 자식 프로세스일 경우, 부모 프로세스에게 output data를 보내게 된다.
프로세스 세계에서는 자식 프로세스가 반드시 먼저 종료되어야 하는데, 자식 프로세스가 비자발적으로 종료되는 경우는 다음과 같다. 1) 자식이 수행하는 task가 더이상 필요하지 않을 경우 2) kill, break 등의 강제 명렁 3) 부모가 종료되어야 하는 경우, 자식, 자식의 자식 프로세스까지 모두 단계적으로 종료한다
"프로세스는 부모 프로세스가 자식을 생성하는 방식으로 생성되며, 부모를 복사하는 OS 함수는 Fork이다"
"자식 프로세스가 새 프로그램으로 주소 공간을 형성하여 실행되는 OS 함수는 Exec이다"
"자식 프로세스는 여러가지 이유로 수행을 마치고 Exit을 수행하는데, 부모 프로세스는 이를 Wait을 통해 기다리기도 한다"
"부모는 Wait으로 기다리기도 하고, 아예 독립적으로 각자 수행되기도 하지만, 종료시에는 자식이 반드시 먼저 종료되어야 한다"
다만 개인적으로 궁금한 점들에 대해서 메모하고 넘어가겠다.
1. Q: 부모 -> 자식 생성 프로세스 생성 방식은 왜 사용하는걸까? (왜 별개로 안두고 굳이?)
2. Q: 프로세스가 이 방식으로만 생성이 되는거라면, 내가 배경화면에서 어떤 프로그램 exe를 실행시켜도, 어떤 관계 없는 프로세스에서 가져오는 것? (그럴리가 없는게 부모 프로세스에서 어떻게 코딩이 되어 있을 수 있는가?) 아니면 부모 프로세스를 직접 실행시키는 것인가?
"개인적으로 아직 자식 프로세스가 눈으로 실행되는 모습을 보지 못해서 공감이 잘 안되는 것 같다"
위 1번 질문에 대한 답을 정리해봤다. 별로 안궁금한 분들은 그냥 skip 해도 되는 내용이다.
Fork 는 위에서 언급하였듯이, 부모 프로세스에서 자식 프로세스로 Memory 자체를 Copy 하는 과정이다 (내가 이해한 바로는 Copy는 진행하지만, exec 되기 전까지는 부모의 Code 공간 내에서 수행되고 있다). 이렇게 자료구조의 모든 contents들을 그대로 Copy 하는 과정을 Deep Copy라고 한다. 이런 Deep Copy 과정은 OS overhead도 큰 편이고, 자원 소모율이 높은 편이다. 도대체 이 이상한 방법을 왜 쓰는걸까?
초창기에 유닉스는 굉장히 단순한 OS였다고 한다. 그러다보니 굉장히 중요한 IPC(다음이야기)를 하는 방법이 없었다고 한다. 각 프로세스가 IPC를 하는 방법이 없다보니, IPC를 할 수 있는 부모 / 자식 프로세스 설계가 시작되었던 것이다. 부모 프로세스가 자식에게 공유 (복사해서 줌) 해줄 수 있는 공간이 마련되어 있기 때문에, 부모 / 자식 프로세스 설계 방식으로 시작되어서 지금까지 유지되었던 것이라고 한다. 그냥 관행적으로 이 방식이 4,50년 동안 굳어졌고, 리눅스OS를 만든 사람은 유닉스의 방식을 그대로 인계하겠다고 해서 현재까지도 모든 프로세스는 Parent / Child 방식으로 생성되는 것이라 한다. 즉... 관행일 뿐 딱히 이유는 없다고 한다 ㅋㅋㅋ
그렇다면 어차피 써야하는거, 성능을 개선하는 방향이 중요한 것인데, 그 중 하나가 처음에 언급했던 COW 방식이다.
또한, Deep Copy의 반대로 Shallow Copy도 있는데 이는 Data 들은 Copy 하지 않고, 해당 Data 를 가르키는 Pointer 만 복사하는 것이다. 하지만 이 또한 Data를 Share 하는 측면이므로, IPC를 완전히 수행시켜주지 못한다. (Sync 문제)
2. 프로세스간의 협력
아직 프로세스의 생성영역이 공감이 잘 되지 않았어도, 추후 더 잘 이해할 수 있기 때문에, 처음에는 정리만 하고 넘어가도 된다고 생각한다. 이제 프로세스간의 협력에 대해 알아보겠다.
프로세스는 굉장히 독립적인 객체이다. 부모가 자식을 kill해야 하는 특정한 경우를 제외하면, Process 간에는 서로의 수행에 영향을 주지 못한다 (CPU 핸들링 제외). 하지만 경우에 따라서는 프로세스간 협력을 해야 실행이 되는 것들도 당연히 존재한다. 이럴 경우 프로세스 간 정보를 주고받기 위해 사용되는 방법은 Inter Process Communication (IPC) 이라고 부른다.
(1) Message Passing
Process A에서 Process B에게 메세지를 전달하는 행위 자체를 말한다. 프로세스간 직접 통신은 불가능하기 때문에(첫 포스트 내용 : 모든 프로세스는 IO에 직접 접근할 수 없다), 다음과 같은 방법들로 운영체제를 통해서 전달한다.
Direct Communication
다음 그림과 같이 Process A에서 전달받는 Process의 이름(Proces B)을 직접 message에 명시하는 것이다. 대상이 정해져있기 때문에 함부로 다른 Process들이 fetch 할 수 없다.
Indirect Communication
위 그림과 같이 Mailbox, Port 를 통해 간접적으로 전달한다. Port 같은 경우 Network 통신 등에 자주 사용되는데, 이는 Indirect Communication인 것을 확인할 수 있으며, 해당 포트에 대기중인 명령 수신 가능한 Process 가 받도록 하는 것이다. Port 번호는 Process간 통신을 하는 Linker Number 라고 생각하는게 맞고, 이와 PID를 헷갈리면 안된다.
(2) Shared Memory
Process간의 주소 공간을 공유하면서 협력이 이루어지기도 한다. 일반적으로는 당연히 각자의 주소 공간을 가지고 있지만, 겹칠 수 있는 영역이 있는 것으로 판단되는 프로세스들은 일부 영역을 공유하도록 처음에 Mapping을 진행해주기도 한다 (이렇게 Shard Memory를 사용하는 경우 항상 Synchronization이 필수인데, 이는 다음에 얘기해보도록 하자). 이 역시 OS를 통해서 매핑을 받게 된다.
참고로 쓰레드는 Process 내에서 일손들의 협력을 말한다. 당연히 특정 프로세스의 주소공간을 완전히 공유하면서 쓰레드들이 점유하고 있기 때문에 프로세스간 데이터 협력 (IPC) 보다 훨씬 협력이 쉽다.
프로세스가 어떻게 생성되고, 어떻게 관리되는지, 프로세스간 어떠한 방식으로 협력이 진행되기도 하는지 확인할 수 있는 포스트다. 이제 운영체제의 꽃인 CPU Scheduling (및 여러 Scheduling)에 대해서 다음 포스트에 배워보도록 하자.
----------------
모든 출처:
1.이화여자대학교 반효경 교수님 운영체제 수업 (14학년도 1학기 수업)
http://www.kocw.net/home/m/search/kemView.do?kemId=1046323
운영체제
운영체제는 컴퓨터 하드웨어 바로 위에 설치되는 소프트웨어 계층으로서 모든 컴퓨터 시스템의 필수적인 부분이다. 본 강좌에서는 이와 같은 운영체제의 개념과 역할, 운영체제를 구성하는 각
www.kocw.net
2. 서울대학교 홍성수 교수님(RTOS Lab) 운영체제 수업 (S-DS 수업 자료)
'CS 이론 > 운영체제' 카테고리의 다른 글
[운영체제 -4] Process Synchronization(병행 제어)과 Semaphore (0) | 2023.03.28 |
---|---|
[운영체제 -3] OS CPU Scheduling (0) | 2023.03.28 |
[운영체제 -1] Process 와 Thread (0) | 2023.03.28 |
[운영체제] 개요 (0) | 2023.03.28 |
[IPC 체험 -1] (0) | 2022.11.07 |