OS에서 process를 만드는 방식에 대해서 알아보자
process는 fork()라는 syscall을 통해서 child process를 생성한다.
fork를 호출한 process가 parent process가 되고, 새로 생긴 process가 child가 process이다.
int fork(void)
return을 2번 한다.
parent에서 fork를 호출하면 child에서 return 하고 parent에서도 return 한다.
이때 return 값에 따라서 parent인지 child인지를 구분할 수 있다.
0을 return 하면 child이고, child의 PID를 return 하면 parent이다.
child는 parent의 모든 부분이 복제되어 생성된다. (가상 주소, parent에서 open 한 file목록 등)
PID를 제외한 모든 부분이 같다.
parent인 경우에는 Fork()를 수행해서 얻은 pid가 child의 pid이므로 pid==0 이 아니라서
parent : x=0을 출력한다.
child인 경우에는 Fork()를 수행하면 0을 return 하므로 child : x=2를 출력한다.
fork를 수행하면 child와 parent에서 모두 return을 하는 과정을 그래프로 나타내 보았다.
위와 같은 그래프는 topological sort(위상 정렬)가 된 상태라고 볼 수 있다.
먼저 fork를 실행하면 flow가 2갈래로 나뉘게 된다.
child인 경우에는 x=2가 되고, parent 인 경우에는 x=0이 된다.
위 그래프를 vertax를 간소화해서 나타내 보았다.
그래프를 일렬로 나열하고 각 vertax의 연결을 나타내 주었다.
모든 간선이 오른쪽을 향하고 있다.
이런 경우에 topological sorting이 되었다고 말할 수 있다.
(그래프 알고리즘에 따르면 dfs의 역순을 활용한 topological sort라고 할 수 있다.)
위와 같은 경우에는 f는 반드시 e 이후에 나타나야 하기 때문에 있을 수 없는 그래프이다.
다양한 예제를 통해서 fork()를 이해해 보자
위 코드를 그래프로 나타내 보자
먼저 printf("L0\n")를 수행한다.
그리고 첫 번째 fork()가 실행되고 child 가 생긴다.
child는 parent를 그대로 복사했으므로 parent가 fork 이후에 수행될 printf(), fork(), printf() 코드를 parent와 동일하게 수행한다. (2갈래로 나뉜 상황)
두 번째 fork()가 실행된다.
이전에 만들어진 parent와 child가 각각 fork()를 실행하게 되고 다시 2갈래로 나뉘어서 총 4개의 Bye를 출력하게 된다.
L0 -> L1 -> Bye -> Bye -> L1 -> Bye -> Bye (O)
L0 -> Bye -> L1 -> Bye -> L1 -> Bye -> Bye (X)
위 코드를 그래프로 나타내 보자
먼저 첫 번째 if문에서 fork()를 수행한다.
이때 child이면 0을 반환하므로 Bye를 출력하고 종료된다.
parent라면 child의 pid 인 0이 아닌 값을 반환하므로 L1을 출력하고
두 번째 if문에서 fork()를 수행한다.
이때 child 이면 0을 반환하므로 Bye를 출력하고 종료된다.
parent라면 child의 pid 인 0이 아닌 값을 반환하므로 L2를 출력하고 if문을 빠져나와 Bye를 출력하고 종료된다.
L0 -> L1 -> Bye -> Bye -> L2 -> Bye (O)
L0 -> Bye -> L1 -> Bye -> Bye -> L2 (X)
바로 위 예제와 다르게 child에서 fork를 수행하는 경우이다. 그래프로 나타내 보자.
fork()의 return 값이 0인 경우에만 추가적인 fork()를 수행할 수 있으므로 위와 같은 그래프가 나옴을 알 수 있다.
L0 -> Bye -> L1 -> L2 -> Bye -> Bye (O)
L0 -> Bye -> L1 -> Bye -> Bye -> L2 (X)
Reaping Child Processes
process가 종료되면 종료된 프로세스에 대한 종료 상태, 연결된 다양한 OS table들이 남아있게 된다.
이를 zombie상태라고 하며 죽었지만 아직 정보가 남아있는 상태이다.
이렇게 남아있는 모든 상태정보들을 삭제해서 시스템에서 완전히 제거하는 과정을 뜻한다.
fork()가 수행되면 child 는 바로 종료된다.
그리고 parent는 while(1)에 갇히게 된다.
종료된 child를 reaping 해주어야 하는데 parent가 무한 루프에 갇혔기때문에 child가 zombie 상태로 남아있게 된다.
pid 6640이 child 이고 <defunt>는 zombie 상태를 의미한다.
kill 6639를 통해서 parent를 종료시키면 child는 누가 reaping을 시켜줄까?
=> parent가 종료되면 init이라는 process가 작동해서 zombie 상태인 child를 reaping 해주는 작업을 수행한다.
위와 같은 코드는 child가 무한루프에 갇히게 되고, parent는 종료되는 경우이다.
parent가 종료되었기 때문에 init이라는 process가 작동해서 무한루프 상태인 child를 reaping 해주는 작업이 잘 수행됨을 알 수 있다.
Creating Processes#2에는 child를 reaping하기 위해서 명시적으로 child가 종료할 때까지 기다리는 wait를 알아보는 것으로 시작할 것이다.
'CS > SystemSoftware' 카테고리의 다른 글
SystemSoftware - Cache Memory #1 (1) | 2021.06.06 |
---|---|
SystemSoftware - Creating Processes #2 (0) | 2021.06.06 |
SystemSoftware - Exceptional Control Flow #2 (0) | 2021.06.05 |
SystemSoftware - Exceptional Control Flow #1 (0) | 2021.06.05 |
SystemSoftware - Linking #2 (0) | 2021.06.04 |