CS/OS

[운영체제] 3-2. 프로세스 생성

F12:) 2023. 9. 23. 22:02

이번에는 프로세스가 생성되는 과정에 대해서 자세히 다뤄보겠습니다.

 

프로세스가 생성되는 과정

프로세스가 생성되는 과정을 단계별로 알아보겠습니다. 우선 프로세스는 fork()라는 시스템 콜로 인해서 만들어집니다. 물론 Idle Process라고 불리는 프로세스는 예외입니다. 이 예외 프로세스에 대해서는 마지막에 다루겠습니다.

 

그럼 이제, fork()를 실행하면 어떠한 과정으로 프로세스가 생성되는지 알아보겠습니다.

 

1. 새로운 프로세스를 위한 PCB 생성

가장 먼저, 프로세스를 생성하기 위해서는 PCB를 생성해야겠죠?? 프로세스 하나당 PCB 1개가 존재하니 말이죠.

(PCB에 대해서 잘 모르신다면 이전 포스팅을 참고해주세요.)

 

그래서 프로세스를 생성하기 위해서 가장 먼저 PCB를 생성합니다.

 

2. 자식 프로세스에게 PID를 배정

pid란 process identifier의 준말로, 프로세스에게 배정되는 식별자를 의미합니다. PCB에서 다뤘기 때문에, 간단하게 넘어가겠습니다.

 

3. PCB의 속성들을 설정

현재 새로 생성된 PCB에는 Identifier를 제외한 다른 값들은 설정되지 않았습니다. 기본적으로, fork()를 통해서 생성된 프로세스는 기본적으로 부모 프로세스와 동일한 값으로 세팅됩니다. 따라서 그러한 값들을 설정해주는 작업을 합니다.

 

조금 더 자세하게 설명해보겠습니다. PCB에는 memory info table이 존재합니다. 여기에는 User Stack에 있는 code, data, stack이 메모리의 어느 위치에 있는지를 나타냅니다. 자식 프로세스가 생성될 때, 이 memory info table은 부모 프로세스가 가리키는 값과 같은 주소를 갖게 됩니다.

 

즉, user stack의 주소를 부모 프로세스와 자식 프로세스의 memory info table이 가리키는 것이죠.

 

물론 pid와 같이 부모 프로세스와 다르게 세팅되어야할 부분도 있지만, 여기서는 다루지 않습니다.

 

4. 연결 생성

모든 프로세스들은 부모 자식간의 연결을 맺습니다. 이러한 과정을 진행해주는 것을 의미합니다.

 

특별히, 리눅스에서는 프로세스 리스트라는 자료구조를 이용해 PCB를 연결지어 관리합니다. 여기서 만약 새로운 프로세스가 들어왔다면 해당 PCB를 프로세스 리스트에 연결하는 작업을 해야할 것입니다.

 

부모 자식간의 관계도 맺지만, 형제 관계에도 연결을 지어줘야합니다. 이러한 과정까지 스텝 4에서 관리합니다.

 

5. 프로세스가 생성됨에 따라 달라지는 자료구조 갱신

자식 프로세스가 생성됨에 따라, 갱신되어야할 값들을 세팅해줍니다. 예를 들어서, A 파일을 부모 프로세스가 관리하고 있다고 합시다. 만약 여기서 자식 프로세스가 생성된다면, 부모 프로세스의 모든 값들을 그대로 갖고 있으므로 이 자식도 A 파일을 관리할 수 있습니다. 따라서 A 파일의 관리 목록에 자식 프로세스를 추가하는 등의 과정을 진행합니다.

 

6.  User Context 생성

이제 자식 프로세스의 User Context를 생성합니다. 이 작업이 프로세스 생성 과정 중에서 가장 시간이 많이 걸리는 과정입니다. 이에 따라, 프로세스가 생성될 때 부모 프로세스의 User Context를 복사하는 것이 아니라, 부모 프로세스나 자식 프로세스의 데이터의 값이 바뀔 때, User Context를 복사하는 방법인 COW(Copy On Write) 방식을 많이 사용하고 있습니다.

 

COW를 사용한다면, 값이 변경되는 시점에만 해당 부분을 copy한다.

 

write할 때, copy한다는 개념의 COW는 프로세스 생성 시점에서 부모의 User Context를 복사하지 않습니다. 마치 지연 로딩의 개념과 유사하다고 볼 수 있습니다.

 

그렇다면 COW를 왜 사용하는거지? 라는 생각이 들 수 있습니다. 이는 fork() 보다는 exec()이라는 시스템 콜에 적용될 때, 더 효과적으로 작용합니다.

 

exec

exec은 fork와 유사하지만 다른 역할을 합니다. fork는 부모 프로세스와 동일하지만 독립적인 자식 프로세스를 생성하는 반면, exec은 실행하는 프로세스에 전혀 다른 프로세스를 덮어쓰는 것을 의미합니다. 

 

exec을 실행하면, 해당 프로세스의 memory info table에 저장되어 있는 값을 초기화합니다. 그 후, exec의 파라미터로 지정된 프로세스에 대한 정보를 보조기억장치에서 메모리로 옮깁니다. 즉, user context가 생성되는 것이죠. 생성된 user context의 주소를 memory info table에 write해줍니다. 이는, COW를 사용하지 않았다면 user context에서 참조값이 없는 좀비 context가 생길 수 있는 우려를 막아줍니다.

 

exec('new.exe')를 실행했을 때, 다음과 같이 도식화할 수 있다.

7.  생성된 자식 프로세스를 ready queue에 삽입

스텝 6까지 거쳤다면, 이는 프로세스가 new 상태에 대한 과정을 모두 진행한 것입니다. 따라서 이제, ready queue에 들어가 실행되기를 기다리는 과정을 진행해줍니다.

 

8. 부모 프로세스에게 자식의 pid를 return

자식 프로세스에 있는 PCB를 부모 프로세스에게 fork()의 결과로 return해줍니다.

 


 

Swapper(=Idle Process)

이렇게, 프로세스가 생성되는 과정에 대해서 알아봤습니다. 앞서 아주 잠깐 언급했지만, 프로세스에는 pid가 0인 특별한 프로세스가 존재합니다. 바로 swapper라고 불리는 Idle Process입니다. 이 프로세스를 제외한 모든 프로세스는 fork()에 의해서 생성됩니다. 하지만 swapper는 OS에서 직접 수동으로 생성합니다. 이 프로세스는 CPU가 모든 과정을 처리하고 놀고있을 때, pid=0에 CPU를 할당하여 빈 루프문을 계속 돌게합니다.