본문 바로가기

운영체제/반효경 교수님 - 운영체제 강의

운영체제 4강 - 프로세스 관리

현재 포스팅은 반효경 교수님의 운영체제 강의 + 추가적인 내용을 바탕으로 정리된 글입니다 :)

1.  프로세스의 개념

프로세스실행 중인 프로그램(program in execution)을 뜻합니다.

 

디스크에 실행 파일 형태로 존재하던 프로그램이 메모리에 올라가서 실행되기 시작하면 비로소 생명력을 갖는 프로세스가 되며, 이러한 프로세스는 CPU를 획득해 자신의 코드를 수행하기도 하고, 때로는 CPU를 반환(운영체제에게 제어권)하고 입출력 작업을 수행하기도 합니다.

 

프로세스 문맥이란 프로세스가 현재 어떤 상태에서 수행되고 있는지 정확히 규명하기 위해 필요한 정보들을 의미합니다.

이러한 프로세스의 문맥에는 해당 프로세스의 주소 공간(코드,데이터,스택 상태)을 비롯해 레지스터에서 어떤 값을 가지고 있었는지와 시스템 콜 등을 통해 커널에서 수행한 일의 상태, 그 프로세스에 관해 커널이 관리하고 있는 각종 정보들을 포함하게 됩니다.

 

프로세스 문맥을 크게 세가지로 분류하면 하드웨어 문맥, 프로세스의 주소 공간, 커널상의 문맥으로 나누어 볼 수 있습니다.

 

하드웨어 문맥CPU의 수행 상태를 나타내는 것으로 프로그램 카운터 값과 각종 레지스트리에 저장하고 있는 값등을 의미합니다.

 

프로세스의 주소 공간은 프로세스가 실행되는데 필요한 메모리 공간(코드, 데이터, 스택 영역)으로 코드 영역엔 프로그램 실행 코드를, 데이터 영역은 전역 변수 같은 프로세스의 정적 데이터를, 스택 영역은 함수 호출 정보나 지역 변수 등의 동적 데이터를 저장하는 데 사용됩니다.

 

커널상의 문맥은 운영체제가 프로세스 관리를 위해 필요로 하는 정보를 포함합니다. 이 정보에는 프로세스의 상태(실행,준비,대기), 프로세스가 소유한 자원, 커널이 프로세스를 스케쥴링하기 위해 필요한 정보(프로세스의 우선순위, CPU 사용시간)를 포함합니다. 이러한 정보는 주로 PCB(커널 데이터 영역의 자료구조)라는 데이터 구조에 저장되며, 커널 스택 또한 문맥을 구성하는 중요한 요소가 됩니다.

 

그렇다면 이러한 프로세스 문맥은 왜 필요할까요????

 

프로세스가 시작해서 종료할 때까지 CPU에서 명령을 한꺼번에 수행하면 좋겠지만,  여러 프로세스가 함께 수행되는 것처럼 보이게 하는 시분할 시스템을 예로 들자면  타이머 인터럽트에 의해 짧은 시간 동안 CPU를 사용한 후 빼앗겼다가 추후에 다시 CPU를 획득하는 방식으로 CPU 관리가 이루어집니다.

 

따라서 CPU를 다시 획득해 명령의 수행을 재개하는 시점이 되면 이전의 CPU 보유 시기에 어느 부분까지 명령을 수행했는지 직전 수행 시점의 정확한 상태를 재현할 필요가 있습니다!! 따라서 이를 위해 필요한 정보가 프로세스의 문맥인 것입니다.


2. 프로세스의 상태

프로세스의 상태는 실행(Running), 준비(Ready), 봉쇄(blocked, wait, sleep)의 세 가지로 구분할 수 있습니다.

 

실행 상태프로세스가 CPU를 할당받아 실제로 명령어(기계어)를 수행하는 상태를 말합니다.

 

예를 들어 실행 상태에서 타이머 인터럽트가 발생하면, 우선 CPU의 제어권은 운영체제로 넘어가게 됩니다. 이때 운영체제는 타이머 인터럽트 처리 루틴으로 가서, 수행 중이던 프로세스의 문맥을 저장하고, 준비 상태에 있는 프로세스 중 하나를 선택해 CPU 제어권을 넘기게 됩니다.(기존 프로세스는 준비 상태가 되면서 새롭게 CPU를 할당받은 프로세스는 실행 상태가 됨) 

이렇게 실행 중이던 프로세스의 문맥을 저장하고, 새로운 프로세스의 문맥을 불러오는 과정을 Context Switching(문맥 교환)이라고 합니다.

 

Context Switching이 일어나는 경우는 타이머 인터럽트 외에도, 실행 상태에 있던 프로세스가 입출력 요청으로 봉쇄 상태로 바뀌는 경우를 들 수 있습니다!

이때 준비 상태에 있는 프로세스들 중에서 CPU를 할당받을 프로세스를 선택한 후 실제로 CPU 제어권을 넘겨받는 과정을 Dispatch라고 합니다.

 

준비 상태프로세스가 CPU를 할당받기 위해 대기하는 상태를 말합니다. 이 상태의 프로세스들은 CPU만 할당받으면 곧바로 명령어를 수행할 수 있는 준비가 되어있습니다. 그러나 입출력 요청 등으로 오랫동안 기다려야 하는 작업이 있는 프로세스들은 이 상태에 머물지 못하며 봉쇄 상태가 됩니다.

 

봉쇄 상태는 CPU를 할당받더라도 당장 명령을 실행할 수 없는 프로세스의 상태를 말합니다(ex: 입출력 작업)

 

이 밖에도 프로세스가 생성 중이거나 종료 중인 일시적인 상태를 각각 시작(new), 완료(terminated) 상태라 부릅니다.

 

시작 상태는 프로세스가 시작되어 그 프로세스를 위한 각종 자료구조는 생성되었지만, 아직 메모리 획득을 승인받지 못한 상태를 뜻합니다.

완료 상태는 프로세스가 종료되었지만, 운영체제가 그 프로세스와 관련된 자료구조를 완전히 정리하지 못한 상태를 뜻합니다.

 

위 내용을 바탕으로 입출력을 요청한 프로세스의 상태 변화가 어떻게 일어나는지 간단한 예를 통해 살펴보겠습니다 :)

 

프로세스가 실행 상태에 있다는 것은 CPU를 할당받아 기계어 명령을 하나씩 수행해 나가는 것을 의미합니다. 그런데 경우에 따라서는 디스크에서 파일을 읽어와야 하는 등 커널의 함수를 호출해야 할 수 있습니다.

 

이때 만약 읽어온 결과가 있어야 후속 명령을 수행할 수 있는 상황에서 현재 입출력 작업(파일을 읽어오는 것)을 호출한 프로세스에게 계속 CPU를 할당하는 것은 상당히 비효율적입니다.

 

따라서 운영체제에서는 입출력이 완료될 때까지 현재 프로세스의 CPU를 반환하고, 해당 프로세스의 상태를 실행 상태에서 봉쇄 상태(block state)로 바꾸게 됩니다. 그러면 CPU를 기다리는 준비 상태의 프로세스들 중에서 CPU 스케쥴러가 적절한 프로세스를 하나 선정해 CPU를 할당하게 됩니다. 이후 준비 상태의 프로세스는 CPU를 할당받았으니 실행 상태가 되겠죠!

 

그렇다면 디스크 입출력을 요청한 , 즉 Block 상태가 된 프로세스는 어떠한 상태 변화를 가질까요??

입출력을 요청한 프로세스는 디스크 입출력을 기다리는 큐에 줄 서 있다가, 자기 차례가 되어 디스크 컨트롤러로 부터 서비스를 받고 나면(저장 매체에서 원하는 데이터를 로컬 버퍼로 읽어 오고 나면), 디스크 컨트롤러가 인터럽트를 발생시켜 입출력이 완료되었다는 사실을 알립니다.

 

자기 차례??? 이 말 되게 모호하지 않나요...? 저는 이 용어가 이해가 되지 않아 해당 부분에 대해 찾아보았고, 간단히 설명드리겠습니다

 

우선 입출력 요청을 기다리는 프로세스가 바로 하나의 프로세스일까요??? 실제 컴퓨터 동작 방식을 생각해 보면 상당히 많은 프로세스가 입출력을 큐에서 기다리고 있을 것입니다. 이러한 큐에서는 일반적으로 요청(ex: 입출력)이 먼저 들어온 순서(FIFO) 대로 또는 운영체제의 스케쥴링 정책에 따라 순서가 결정되며, 이러한 순서에 맞게 디스크 컨트롤러는 하나씩 요청을 처리합니다.

 

즉 큐에 넣는 이유를 보자면, 많은 프로세스들이 동시에 입출력을 요청하거나, 앞선 입출력 요청이 처리되지 않은 상태에서 자신의 입출력 요청을 보낼 수 있습니다. 하지만 하드웨어 자원(여기서는 디스크)은 한 번에 한 가지 작업만 수행할 수 있습니다. 따라서 입출력 요청을 일정한 순서에 따라 처리하도록 큐에 넣는 것입니다.

 

CPU는 어떤 프로세스를 실행하고 있다가 인터럽트가 발생한 것을 확인하고 그에 대응하는 루틴을 수행합니다.

이 루틴이 진행되는 동안 CPU에서 수행되던 프로세스의 상태는 사용자 모드 실행 상태에서 커널 모드 실행 상태로 바뀝니다. 비록 인터럽트 처리루틴이 직전에 실행 중이던 프로세스와 무관한 업무이지만, 일반적으로 직전 프로세스의 문맥에서 실행된 것으로 간주합니다.

 

즉, 프로세스가 실행되던 중 인터럽트가 발생하면, 인터럽트가 발생한 원인과 상관없이 인터럽트를 당한 프로세스가 사용자 모드에서 실행되다가 커널모드로 진입한 것으로 간주하게 되는 것입니다.

이렇게 디스크 컨트롤러가 발생시킨 인터럽트는 입출력이 완료된 프로세스의 상태를 block에서 ready 상태로 바꾼 후 장치의 로컬 버퍼에 있는 내용을 메모리로 이동시키는 일련의 엄무를 수행합니다.

 

이렇게 인터럽트 처리가 끝나면 인터럽트 처리루틴 이전에 수행되던 프로세스에게 CPU를 다시 할당해 그 프로세스의 직전 수행 시점 이후의 코드가 실행되는 것이죠!

 

이때 특정 상황에 따라(흔하지 않음), 인터럽트를 당한 프로세스에게 CPU를 다시 할당하지 않고, 입출력이 완료된 프로세스가 우선순위가 높은 경우 Context Switching을 통해 CPU 제어권을 넘겨주는 경우도 있습니다.


3.  프로세스 제어 블록

프로세스 제어 블록(Process Control Block:PCB)운영체제가 시스템 내의 프로세스들을 관리하기 위해 프로세스마다 유지하는 정보들을 담는 커널 내(데이터 영역)의 자료 구조를 뜻합니다.

 

프로세스 제어 블록은 다음과 같이 구성되어 있습니다.

  • 포인터 : 스케쥴링 큐의 다음 프로세스나 이전 프로세스를 가리키는 역할
  • 프로세스 상태 :  생성, 준비, 실행, 대기, 완료, 보류 등 프로세스가 현재 어떠한 상태에 있는지를 나타냄
  • 프로세스 구분자(Process number) : 운영체제 내에 여러 프로세스가 있기에, 이 들을 구분하기 위한 구분자
  • 프로그램 카운터(PC) : 프로세스가 준비 상태에서 실행 상태로 변경되었을 때, PC를 통해 다음 실행될 명령어의 위치를 알 수 있음
  • 프로그램 우선순위 : CPU 스케쥴러가 준비 상태에 있는 프로세스 중 실행 상태로 옮겨야 할 프로세스를 선택할 때 프로세스의 우선순위를 기준으로 삼기에 저장
  • 각종 레지스터 정보 : 프로세스가 실행 중에 사용했던 레지스터 정보(ex : 누산기(Accumulator), 색인 레지스터, 스택 포인터) 등이 저장되며 이는 이전 실행할 때 사용했던 레지스터 값들을 저장해야 다음에 실행할 수 있기에 보관
  • 메모리 관리 정보 : 프로세스가 메모리의 어디에 있는지 알아야 하기에 메모리 위치 정보가 필요하고, 메모리 보호를 위해 사용되는 경계 레지스터 및 한계 레지스터 값 등이 저장된다. 이 외 세그멘테이션 테이블, 페이지 테이블 등의 정보도 보관된다.
  • 계정 정보 : 계정 번호, CPU 할당 시간, CPU 사용 시간 등이 저장됨
  • PPID와 CCID : 부모 프로세스 구분자(Paraent PID)와 자식 프로세스 구분자(Child PID) 정보를 보관

4.  문맥 교환(Context Switching)

Context Switching이란 하나의 사용자 프로세스로 부터 다른 사용자 프로세스로 CPU의 제어권이 이양되는 과정을 뜻합니다.

 

앞서 설명한 예시에서 타이머 인터럽트가 발생하면 원래 수행 중이던 프로세스는 준비 상태로 바뀌고, 새롭게 CPU를 할당받은 프로세스는 실행 상태가 되었던 것 기억하시나요??

 

이러한 Context Switching 과정 중, 원래 CPU를 점유하고 있던 프로세스는 프로그램 카운터 값 등의 프로세스의 문맥을 PCB에 저장하고, 새롭게 CPU를 할당받을 프로세스는 예전에 저장했던 자신의 문맥을 PCB로부터 실제 하드웨어로 복원시키는 과정을 거칩니다.

 

Context Switching은 타이머 인터럽트뿐 아니라, 실행 중이던 프로세스가 입출력 요청이나 다른 조건을 충족하지 못해, CPU를 회수당하고 block 상태가 되는 경우에도 발생할 수 있습니다.

 

프로세스가 실행 상태일 때 시스템 콜이나 인터럽트가 발생하면, CPU의 제어권이 운영체제로 넘어와 원래 실행 중이던 프로세스의 업무를 잠시 멈추고 운영체제 커널의 코드가 실행됩니다. 이러한 경우에도 CPU의 실행 위치 등 프로세스의 문맥 중 일부를 PCB에 저장하게 되지만 이러한 과정을 문맥교환이라고 하지 않습니다.

이는 하나의 프로세스의 실행모드 만이 사용자 모드에서 커널 모드로 바뀌는 것일 뿐, CPU를 점유하는 프로세스가 다른 사용자 프로세스로 변경되는 것이 아니기 때문입니다!

 

즉 타이머 인터럽트가 발생하거나, 프로세스가 입출력 요청 시스템 콜을 하여 봉쇄 상태에 들어가는 경우엔 CPU 스케쥴러가 다른 프로세스에게 CPU를 할당하겠죠?? 이러한 경우엔 Context Switching이라 부를 수 있습니다.

반면 그 밖의 인터럽트나 시스템 콜 발생 시에는 Context Switching이 있다기 보단, 단지 모드 변경(사용자모드->커널모드)만이 있는 것입니다.

 

이러한 문맥 교환에 소요되는 시간은 시스템 입장에서 일종의 오버헤드라고 할 수 있습니다. 따라서 타이머에 CPU 할당시간을 아주 작게 세팅해 프로그램 간 문맥 교환이 빈번하게 발생하도록 하면 이에 드는 오버헤드가 상당히 커집니다. 반면 CPU 할당 시간을 너무 크게 설정하면 문맥 교환 횟수는 줄지만 시분할 시스템의 의미가 퇴색되겠죠??

 

따라서 이러한 TradeOff를 잘 해결하여 적절한 CPU 할당시간을 정하는 것이 중요합니다!


5. 프로세스를 스케쥴링하기 위한 큐

운영체제는 준비 상태에 있는 프로세스들을 줄 세기 위한(처음 메모리에 적재되는 경우도 포함) 준비 큐(Ready Queue)와, 특정 자원을 기다리는 프로세스(block 상태)들을 줄 세우기 위해 자원별 장치 큐(Device Queue)를 둡니다.

 

예를 들어 디스크에 입출력 서비스를 요청한 프로세스들은 디스크 입출력 큐(Disk I/O queue, Device Queue 중 하나)에 줄 서게 됩니다. 그러면 디스크 컨트롤러는 디스크 입출력 큐에 서 있는 순서대로 프로세스들의 입출력 작업을 수행합니다. 한 작업이 완료되면 인터럽트를 발생시켜 현재 block 상태에 있는 프로세스를 준비 큐로 줄 세우겠죠???

 

이와 같이 프로세스의 상태 관리는 커널의 주소 영역 중 데이터 영역에 다양한 큐를 두어 수행하게 됩니다.즉 각 프로세스의 상태를 커널이 총체적으로 관리한다는 뜻입니다!

 

예를 들어 타이머 인터럽트가 발생하면 커널은 자신의 데이터 영역에 있는 준비 큐의 정보를 참조해 다음에 어느 프로세스에게 CPU를 할당할지 결정하고, 현재 실행 중인 프로세스는 준비 큐의 젤 뒤로 보내게 됩니다.

 

운영체제는 앞서 설명한 준비 큐와 장치 큐뿐 아니라 작업 큐를 추가로 유지합니다.

작업 큐는 시스템 내의 모든 프로세스를 관리하는 큐로, 프로세스의 상태와 무관하게 현재 시스템 내에 있는 모든 프로세스가 속하게 됩니다.

즉 이러한 작업 큐가 가장 넓은 개념이고 준비큐와 장치큐에 있는 프로세스들은 모두 작업 큐에 속하게 됩니다.

 

위의 그림을 참고할 시, 큐는 각 프로세스의 PCB를 연결 리스트 형태로 관리하며 포인터를 사용해 순서를 정하게 된다는 것을 확인할 수 있습니다!


6. 스케쥴러

스케쥴러(Scheduler)어떤 프로세스에게 자원을 할당할지 결정하는 운영체제 커널의 코드를 지칭합니다.

 

이러한 스케쥴러는 장기 스케쥴러(Long term Scheduler, == 작업(job) 스케쥴러), 중기 스케쥴러(Middle term Scheduler), 단기 스케쥴러(Short term Scheduler, == CPU 스케쥴러)로 나뉩니다.

 

장기 스케쥴러어떤 프로세스를 준비큐로 진입시킬지 결정하는 역할을 합니다. 

처음 프로세스가 생성되면 시작 상태를 거쳐 준비 상태에 이르게 되는데, 장기 스케줄러는 이때 시작 상태의 프로세스 들 중 어떠한 프로세스를 준비 큐에 삽입할 것인지 결정하는 역할을 하는 것입니다.

 

반면 단기 스케줄러준비 상태(준비 큐에 있는)의 프로세스 중에서 어떤 프로세스를 다음번에 실행 상태로 만들 것인지 결정합니다.

시분할 시스템에서 타이머 인터럽트가 발생하면 단기 스케쥴러가 호출된다고 생각하시면 됩니다!

 

장기 스케쥴러는 시작 상태의 프로세스에게 메모리 할당을 승인하는 역할을 하기에, 메모리에 동시에 올라가 있는 프로세스의 수를 조절하게 됩니다. 그러나 현대의 시분할 시스템에서 사용되는 OS들은 일반적으로 장기 스케쥴러를 두지 않습니다.

즉 프로세스가 시작 상태가 되면 장기 스케쥴러 없이 곧바로 그 프로세스에 메모리를 할당해 준비큐에 넣어주는 것이죠

이 대신 현대의 시분할 시스템용 운영체제에서는 중기 스케쥴러를 두는 경우가 많습니다.

 

중기 스케쥴러메모리에 적재된 프로세스의 수를 동적으로 조절하기 위해 추가된 스케쥴러입니다.

 

만약 너무 많은 프로세스가 메모리에 적재되어, 프로세스당 보유하고 있는 메모리 양이 극도로 적어지면 CPU 수행에 당장 필요한 프로세스의 주소 공간조차도 메모리에 올려놓기 어려울 수 있습니다. 그렇게 되면 디스크 IO가 수시로 발생하여 시스템 성능이 심각하게 저하될 수 있겠죠???

중기 스케쥴러는 이러한 경우 메모리에 올라와 있는 프로세스 중 일부를 선정해 이들로부터 메모리를 통째로 빼앗아 그 내용을 디스크의 스왑 영역에 저장하며, 이와 같은 행위를 스왑 아웃이라고 부릅니다.

 

중기 스케쥴러가 일부 프로세스를 메모리에서 디스크로 스왑 아웃시켜야 하는 경우, 0순위인 프로세스는 봉쇄 상태에 있는 프로세스입니다. 이는 당장 CPU를 획득할 가능성이 없기에 메모리 보유하고 있는 것 또한 큰 의미가 없기 때문이죠.

 

이렇게 봉쇄 상태에 있는 프로세스를 다 Swap Out 시킨 후에도 메모리 공간이 부족하다면, 중기 스케쥴러는 보통 타이머 인터럽트가 발생해 준비큐로 이동하는 프로세스들을 추가적으로 스왑아웃 시킵니다(1순위).준비 큐에 너무 많은 프로세스가 존재한다면 메모리양이 지나치게 적어질 뿐만 아니라, CPU를 한번 할당받은 후 다시 할당받기까지 오랜 시간이 걸리기 때문입니다!

 

 

이러한 중기 스케쥴러의 등장으로 프로세스의 상태에는 실행, 준비, 봉쇄뿐 아니라 중지(suspend)라는 상태가 추가됩니다.중지 상태에 있는 프로세스(ex: 메모리가 부족하여 봉쇄 상태에서 중지 상태로 바뀐 경우, 준비 상태에서 중지 상태로 바뀐 경우)는 외부에서 재개시키지 않는 이상 다시 활성화될 수 없기에 메모리 자원이 당장 필요하지 않습니다. 따라서 중지 상태의 프로세스는 메모리를 통째로 빼앗기고 디스크로 스왑아웃됩니다.

 

이러한 중지 상태는 중지준비 상태(suspended ready)중지봉쇄 상태(suspended block)로 세분화될 수 있습니다.준비 상태에 있던 프로세스가 중기 스케쥴러에 의해 중지 상태가 된다면 중지 준비 상태가 되고, 봉쇄(block) 상태에 있던 프로세스가 중기 스케쥴러에 의해 스왑 아웃되면 이 프로세스는 중지 봉쇄 상태가 됩니다.

 

이때 중지 봉쇄에 있던 프로세스의 입출력 작업이 완료된다면 중지 준비 상태가 됩니다!

 

이러한 2가지 중지 상태에 있는 프로세스들은 메모리를 조금도 보유하지 않고, 디스크에 통째로 스왑 아웃된 상태로 존재하게 됩니다.


7.  프로세스의 생성

프로세스는 어디서 생성될까요???

저는 운영체제가 프로세스 전부를 생성한다고 생각했었는데, 실제로는 그렇지 않습니다!! 이에 대해 차근차근 알아보겠습니다 :)

 

시스템이 부팅된 후 최초의 프로세스는 운영체제가 직접 생성하지만, 그다음부터는 이미 존재하는 프로세스가 다른 프로세스를 복제 생성하게 됩니다.

이때 프로세스를 생성한 프로세스를 부모 프로세스, 새롭게 생성된 프로세스를 자식 프로세스라고 합니다.

 

프로세스 세계에서는 자식 프로세스가 먼저 죽고, 이에 대한 처리는 생성했던 부모 프로세스가 담당하는 방식으로 진행됩니다.

만약 프로세스를 많이 생성한 프로세스가 종료 상태를 가지고자 한다면, 해당 프로세스가 생성했던 모든 후손 프로세스들을 연쇄적으로 종료시킨 후에야 본인이 종료될 수 있는 것이죠.

 

프로세스가 수행되는 모델에는 부모와 자식이 공존하면서 수행되는 모델이 있는 반면, 자식이 종료(terminate)될 때까지 부모가 기다리는 모델이 있습니다.

부모와 자식이 공존하는 모델의 경우에는 자식과 부모가 같이 CPU를 획득하기 위해 경쟁하는 관계가 될 수 있고, 부모가 자식의 종료를 기다리는 모델에서는 자식 프로세스가 종료될 때까지 부모 프로세스는 아무 일도 하지 않고 봉쇄 상태에 머물러 있다가, 자식 프로세스가 종료되면 그때 부모 프로세스가 준비상태가 되어 다시 CPU를 얻을 권한이 생기게 됩니다.

 

부모 프로세스가 자식 프로세스를 생성하면 자식 프로세스는 부모 프로세스와는 별도의 물리적 메모리 주소를 가지지만, 처음 주소 공간을 생성할 때 부모 프로세스의 주소 공간 내용을 그대로 복사해 생성합니다. 이후 자식 프로세스가 다른 프로그램을 실행하기 위해서는 생성된 주소 공간 위에 새로운 프로그램의 주소 공간을 덮어 씌워 실행해야 합니다.

 

위의 로직을 이해하기 위해 UNIX를 통한 예를 간단히 들어보겠습니다!

 

운영체제는 자식 프로그램 생성을 위해 fork()라는 시스템콜을 제공합니다. 프로세스가 fork() 시스템 콜을 하게 되면 CPU 제어권이 커널로 넘어가게 되고, 커널은 fork()를 호출한 프로세스를 복제해 자식 프로세스를 생성하게 됩니다.

이때 부모 프로세스 주소 공간을 비롯해 프로그램 카운터 등 레지스터 상태, PCB 및 커널스택 등 모든 문맥을 그대로 복제해 자식 프로세스의 문맥을 형성하는 것입니다.

즉 이 말을 다르게 말해보자면, 자식 프로세스는 부모 프로세스의 처음부터 수행하는 것이 아니라 부모 프로세스가 현재 수행한 시점부터 수행하게 되는 것입니다.

다만 운영체제가 프로세스를 식별하기 위해 사용하는 프로세스 식별자가 다르다는 것이 유일한 차이점입니다!

 

부모 프로세스가 fork()를 통해 자식 프로세스를 생성했다고 가정해 봅시다

이때 두 프로세스에게 각각 다른 작업을 수행하고자 할 때 우선 이것이 부모(원본)인지 자식(복제본)인지 어떻게 구분할까요??

이를 위해 fork() 함수는 원본에게 양수 값을 return 하는 반면 복제본에게는 0을 return 하게 됩니다!

 

그렇다면 둘을 구분은 하였지만 동일한 코드 내용을 가지는 두 프로세스에게 독자적으로 작업을 어떻게 시킬까요??

이때는 자식 프로세스에게 부모와는 다른 독자적인 프로그램을 수행시킬 수 있는 메커니즘이 필요합니다.

 

UNIX에서는 이를 위해 프로세스 공간에 새로운 프로그램을 덮어씌우는 exec() 시스템콜을 지원합니다. 즉 exec() 시스템 콜은 지금까지 수행 내용을 잊어버리게 한 후, 새로운 프로그램의 첫 부분부터 다시 실행하도록 하는 것이죠!

 

요약하자면 새로운 프로그램을 수행시키기 위해서는 fork()를 통해 기존 프로세스와 동일한 프로세스를 복제한 후 exec()를 통해 새롭게 수행시키려는 프로세스를 자식 프로세스의 주소 공간에 덮어 씌우면 되는 것입니다.

fork(), exec()와 같은 시스템 콜은 사용자 프로세스가 직접 수행할 수 없는 특권명령에 해당하겠죠???

이 외에 프로세스 관련 메서드에는 exit() 프로세스와 wait() 프로세스가 있습니다.

 

프로세스는 자신이 실행을 완료하였을 때, 또는 어떤 이유로 더 이상 실행을 계속할 수 없을 때 exit() 시스템콜을 통해 운영체제에게 이를 알립니다. 이는 프로세스가 자발적으로 종료하게 되는 경우 사용되며, 해당 프로세스의 모든 시스템 자원을 회수하고, 프로세스의 상태를 종료 상태로 변경합니다. 비 자발적 종료를 지원하는 abort() 시스템콜도 존재합니다!

 

wait() 시스템콜은 부모 프로세스가 자식 프로세스의 종료를 기다리기 위해 사용하는 시스템 콜입니다. 부모 프로세스가 wait() 시스템 콜을 호출하면, 운영체제는 부모를 봉쇄 상태(blocked, 특정 이벤트가 발생될 때까지 실행 중단)로 만들고, 자식 프로세스의 종료를 기다립니다. 이후 자식 프로세스가 종료되면, 운영체제는 부모 프로세스를 준비 상태로 만듭니다.

이러한 wait()를 통해 부모 프로세스와 자식 프로세스 간의 동기화(synchronization)가 가능하겠죠??


8. 프로세스 간의 협력

일반적으로 프로세스는 각자 자신만의 독립적인 주소 공간을 가지고 수행되므로, 원칙적으로 프로세스가 다른 프로세스의 주소 공간을 참조하는 것은 허용되지 않습니다.

 

하지만 경우에 따라서는 독립적인 프로세스들이 협력할 때 업무의 효율성이 증진될 수 있습니다. 이는 부분적인 처리 결과나 정보를 공유할 수 있고, 처리 속도가 향상될 수도 있는 등 여러 가지 측면에서 효과적일 수 있습니다.

따라서 운영체제는 프로세스 간의 협력 메커니즘을 제공해 하나의 프로세스가 다른 프로세스의 수행에 영향을 미칠 수 있게 합니다.

 

프로세스 간의 협력 메커니즘을 위해 운영체제가 제공하는 대표적인 메커니즘이 IPC(Inter-Process Communication)이 있습니다.

IPC란 하나의 컴퓨터 안에서 실행 중인 서로 다른 프로세스 간에 발생하는 통신으로, 이러한 통신에서는 의사소통 기능과 함께 동기화를 보장해 주어야 됩니다.

 

이러한 IPC에는 다양한 기법이 있습니다!

  • 파일 사용 : 단순히 파일을 공유하여 데이터를 주고받는 방식으로 동기화 문제가 발생할 수 있습니다.
  • 메시지 큐 : 커널 공간에 데이터를 저장하고 전달하는 큐를 사용하여 프로세스 간에 데이터를 전달합니다. 동기화를 위한 별도의 메커니즘은 필요하지 않으며, 각 프로세스는 큐에 메시지를 넣거나 가져오기만 하면 됩니다.
  • 공유 메모리 : 서로 다른 프로세스들이 동일한 메모리 공간(공유 메모리 공간 : 커널 공간에 생성됨)을 공유하도록 해주는 방식입니다. 이 방식은 통신 속도가 빠르지만, 동기화를 위한 추가적인 메커니즘이 필요합니다.
  • 파이프 : 한 프로세스의 출력을 다른 프로세스의 입력으로 연결하는 방식으로 통신합니다. 주로 부모-자식 프로세스 간 사용되며, 단방향 통신이 가능한 기본 파이프와 양방향 통신이 가능한 명명된(named) 파이프가 있습니다.
  • 시그널 : 프로세스에 이벤트나 통지를 전달하는 방식으로 통신합니다. 이는 간단한 정보를 전달할 때는 유용하지만, 복잡한 데이터를 주고받기에는 적합하지 않습니다.
  • 세마포어 : 여러 프로세스 간 동기화와 상호 배제를 위해 사용하는 도구입니다. 세마포어는 공유 메모리나 메시지 큐와 같은 IPC 기법을 보완하기 위해 사용됩니다.
  • 소켓 : 네트워크를 통해 원격 프로세스 간 통신을 가능하게 하는 방식입니다. 소켓은 프로토콜, IP 주소, 포트 번호 등을 사용해 프로세스 간 데이터 전송을 지원합니다.

이러한 IPC 기법들을 사용하여 프로세스 간 데이터를 주고받거나 상태를 확인할 수 있습니다. 이러한 기법들은 사용자 목적과 상황에 따라 선택되어야 하며, 그에 따른 장단점 또한 고려해야합니다!


개발자 준비생이 대학 강의를 듣고 정리한 내용입니다

혹시 틀린 내용이 있다면 댓글 부탁드리겠습니다!! 곧바로 수정하겠습니다 :)