CS/operating system

Process Management: 프로세스 관리 (3/3)

superbono 2021. 2. 10. 21:40

7. 프로세스의 생성(Process Creation)

프로세스 생성은 어려운 거라서 시스템이 부팅되고 난 최초의 프로세스는 운영체제가 생성하지만 그 이후 존재하는 프로세스가 다른 프로세스를 복재 생성하게 된다. 

부모 프로세스(parent process)가 자식 프로세스(child process)를 생성한다.

이러한 방식을 통해 프로세스는 족보와 같은 계층을 생성하게 된다. 현실 세계에서는 부모가 먼저 죽고 자식이 죽지만 프로세스 세계에서는 자식이 먼저 죽고 부모가 죽는다. 왜냐하면 부모 프로세스는 자식 프로세스의 죽음에 대한 처리를 담당해야 하기 때문이다. 불효가 아닐 수 없다.

프로세스는 자원을 필요로 하는데 어떻게 자원을 공급받을까? 그 방법은 1. 운영체제로부터 공급받는다. 2. 부모와 공유하여 사용한다. 

자원을 공유하는 프로세스는 1. 부모와 자식이 모든 자원을 공유하는 모델, 2. 일부를 공유하는 모델, 3. 전혀 공유하지 않는 모델(일반적)이 있다. 

프로세스의 수행(execution)은 2가지가 있는데 1. 부모와 자식은 공존하며 수행되는 모델 -> cpu를 획득하기 위해 경쟁하는 모델.

2. 자식이 종료(terminated)될 때까지 부모가 기다리는(wait, blocked)모델 이 있다. 이런 프로세스 모델의 부모는 자식이 종료될 때가지 기다리거나 봉쇄 상태에 있다가 자식 프로세스가 종료되면 그 때 부모프로세스가 준비 상태가 되어 다시 cpu를 얻을 권한이 생기게 된다. 

 

프로세스 생성

프로세스가 생성되면 각자의 주소 공간도 생성되는데 자식은 부모 공간을 그대로 복사한다. (binary and os data) 유닉스를 예를 들어 설명하자면 fork() 시스템 콜을 통해 새로운 프로세스를 생성할 수 있는데 이 때 새로이 생성된 프로세스는 부모 프로세스의 주소 공간을 모두 똑같이 베껴온다(process id 제외) 따라서 부모 프로세스와 자식 프로세스는 주소 공간은 따로 갖게 되지만 주소 공간 내에는 동일한 내용을 갖게 된다. 

fork가 새로운 프로세스를 복제 생성하는 시스템 콜이라면 exec는 그 위에 다른 프로그램으로 덮어씌우는 시스템 콜이다. 

*COW(Copy On Write) 

write가 발생했을 때(= 내용이 바뀔 때) 그 때 copy를 하겠다. 그 전까지는 부모꺼 같이 공유하다가 바뀌면(내용이 수정되면) 그때 그 부분만 copy해서 쓰겠다. 그러나 원칙은 독립적 프로세스이다. 공유를 할 수 있으면 공유를 하는게 효율적이다라는 얘기

 

프로세스 종료(process termination)

프로세스의 종료는 2가지가 있다. 하나는 자발적 종료이고 하나는 강제적 종료이다.

* 자발적 종료(exit)

프로세스가 마지막 명령을 수행한 후 운영체제에게 이를 알려준다. 프로세스는 instruction을 모두 수행한 수, 프로그램이 끝나는 코드 지점에 exit라는 시스템 콜이 들어가게 되어 있다. 이 시스템 콜을 통해 프로세스는 운영체제에게 나 죽는다를 알리게 되는 것이다. 프로세스의 각종 자원은 운영체제게에 반납되며 시스템 내에서 이 프로세스를 정리하게 된다. 자식이 부모에게 output data를 보낸다.(via wait)

* 비자발적 종료(abort)

부모 프로세스가 자식의 수행을 종료 시키는 것으로 강제적 종료이다. 이러한 경우는 자식이 할당 자원의 한계치를 넘어섰거나, 자식에게 시킬 일이 없거나(자식에게 할당된 task가 더이상 필요하지 않음), 부모가 종료(exit)하는 경우이다. 운영체제는 부모 프로세스가 종료되는 경우 자식 프로세스가 더 이상 수행되지 못하게 하기 때문에 단계적인 종료가 이루어진다. 

 

fork() 시스템 콜 

프로세스는 fork 시스템 콜에 의해서 생성된다. 운영체제는 자식 프로세스의 생성을 위해 fork 시스템 콜을 제공한다. 프로세스가 fork 시스템 콜을 하게 되면 cpu 제어권은 커널에게 넘너가고 커널은 fork를 호출한 프로세스를 복제해 자식 프로세스를 생성하게 된다. 즉 fork 시스템 콜을 하게 되어 생성된 프로세스는 부모 프로세스와 동일한 문맥을 가진다(주소 공간 내용이 복제본이나 새 주소공간을 가짐), pcb, pc 이런 것도 다 복제해온다. 따라서 자식 프로세스는 부모 프로세스의 처음부터 수행을 시작하는 것이 아니라 부모 프로세스가 현재 수행한 시점(pc 지점)부터 수행하게 된다는 것이다. 그러나 운영체제가 프로세스를 관리하기 위해서 pid는 유일하게 다르다.

부모 프로세스와 자식 프로세스의 fork

부모 프로세스 0에서부터 자식 프로세스가 생성된다. 자식 프로세스는 코드의 처음부터(A)가 아닌 B 부터 실행되는데 그 이유는 부모 프로세스의 context(pc)를 그대로 복제해서 이 부분부토 실행해야 한다는 것을 알기 때문이다. 부모 프로스세는 fork의 값으로 0보다 큰 양수의 값을, 자식 프로세스는 0을 받는다. 그래서 어느 놈이 원본이고 어느 놈이 복사본인지 fork의 return value로 구분할 수 있다. 이 pid값을 이용하여 양수라면 부모함수를 실행하게끔 할 수 있고 음수라면 자식 함수를 실행하게끔 하는 등 다른 일을 시킬 수 있다.

 

exec() 시스템 콜

execlp는 exec수행, 새로운 프로그램으로 만듬-> 그 프로그램의 시작부터 (main함수 시작부터)새로 덮어씌운다. 완전히 기억을 잃어버린 아기가 된다고 생각하면 된다. 따라서 새로운 프로그램을 수행시키기 위해서는 fork를 통해 기존 프로세스와 동일한 프로세스를 복제하고 exec를 통해 새롭게 수행시키려는 프로세스를 자식 프로세스의 주소 공간에 덮어씌우면 된다. 

 

wait() 시스템 콜

부모 프로세스가 fork하고 wait해서 자식 프로세스 종료될 때까지 기다린다음에 깨어나면 ready큐에 재진입하여 다시 cpu를 얻을 권한을 획득한다. 자원을 기다리며 줄 서 있는 봉쇄상태가 아니다!!! 자식 프로세스의 종료를 기다리며 부모 프로세스가 봉쇄상태에 머무르도록 할 때 사용되는 것이다. 이러한 방식으로 부모 프로세스와 자식 프로세스간에 동기화(synchronization)이 가능해진다. 

 

exit() 시스템 콜

* 자발적 종료

마지막 statement 수행 후 exit 시스템 콜을 통해

프로그램에 명시적으로 적어주지 않아도 main함수가 리턴되는 위치에 컴파일러가 넣어준다. - > 프로세스 종료 직전에 항상 호출된다.

 

* 비자발적 종료(외부에서 종료)

부모 프로세스가 자식 프로세스를 강제 종료시킴 abort (자식 프로세스가 한계치를 넘어서는 자원을 요청했을 경우, 자식에게 할당된 task가 더이상 필요하지 않은 경우)

키보드로 kill, break등을 친 경우

부모가 종료하는 경우 부모프로세스가 종료하기 전에 자식들이 먼저 종료된다.

 

<프로세스와 관련한 시스템 콜>

fork() : create a chile(copy) 부모 프로세스를 복제 생성

exec(): overlay new image 완전히 새로운 프로그램으로 덮어씌움

wait(): sleep until child is doen 자식이 종료될 때까지 잠듦

exit(): frees all the resources, notify parent 프로세스를 종료시키는, 모든 자원 반납한 뒤 부모 프로세스에게 나 죽는다고 연락함 

 

8. 프로세스간의 협력

프로세스는 각각 자신만의 독립적인 주소 공간을 가지고 수행된다. 프로세스가 다른 프로세스의 주소 공간을 참조하는 것은 허용되지 않는다. 따라서 원칙적으로 하나의 프로세스는 다른 프로세스의 수행에 영향을 미치지 못한다. 

경우에 따라서는 독립적인 프로세스들이 협력할 때 업무의 효율성이 증진될 수 있기에 운영체제는 프로세스 간의 메커니즘을 제공하여 하나의 프로세스가 다른 프로세스의 수행에 영향을 미칠 수 있게 해준다. 

프로세스 간에 협력 메커니즘은 대표적으로는 IPC(Inter Process Communication)이라고 한다. 하나의 컴퓨터 안에서 실행 중인 서로 다른 프로세스 간에 발생하는 통신을 말한다. 프로세스 간의 통신(communication)과 동기화를 이루기 위한 메커니즘을 의미하기도 한다. 

 

IPC의 대표적인 방법으로는 message passing 방식과 shared memory 방식이 있다. 이 두 방식의 차이는 프로세스 사이에 공유 데이터(shared data)를 사용하는가, 그렇지 않는가에 있다.

 

- message passing

message passing 중 direct communicaion과 indirect communication

커널을 통해(주소 공간이 다르니까) 메세지를 전달한다. 공유 데이터는 일체 사용하지 않는다. 메세지 통신을 하는 시스템은 커널에 의해 send(message) 연산receive(message) 연산을 제공받는다. 이 두 연산을 통해 프로세스는 전달할 메세지들을 운영체제에게 시스템 콜 방식으로 요청해 전달할 수 있다. 메세지를 프로세스끼리 직접 주고받으면 보안에 악영향이 끼칠 수 있으므로 운영체제는 메세지를 주고받는 명령을 특권 명령으로 정하여 커널을 통하여만 가능하도록 하였다. 통신하기를 원하는 두 프로세스는 커뮤니케이션 링크 (comunication link)를 생성한 후 send()와 receive() 을 이용하여 메세지를 주고받게 된다 이 커뮤니케이션 링크의 구현 방법은 물리적인 방법과 논리적인 방법이 있다. 

message passing 중 전송 대상이 직접적으로 다른 프로세스인지 아니면 mailbox라는 일종의 저장공간인제이 따라 다시 direct communication과 indirect communication으로 나뉜다. 둘은 사실 인터페이스 측면에서 다를 뿐 실제 메세지 전송이 이루어지는 내부 구현은 커널의 중재에 의해 사실상 동일한 방식으로 이루어진다. 

direct communication은 통신하려는 프로세스의 이름을 명시적으로 표시한다. 그러나 위 그림에서 보면 p -> q로 가는 것 같지만 사실은 P->커널->Q로 간다. 이러한 방식을 위한 커뮤니케이션 링크는 자동적으로 생성되며 하나의 링크는 정확히 한 쌍의 프로세스에만 할당된다. 이 링크는 양방향성(bidirectional)이나 단방향성(undirectional)일 수도 있다.

indirect communication은 메세지를 메일박스 또는 포트로부터 메세지를 간접 전달받는다. 이 메일박스라는 것은 커널에 존재하며 고유의 id가 있어서 메일박스를 공유하는 프로세스끼리만 서로 통신을 할 수 있다. 하나의 커뮤니케이션 링크는 여러 프로세스들에게 할당될 수 있으며 각 프로세스의 쌍은 여러 링크를 공유할 수 있다. 또한 링크는 단방향성 또는 양방향성일수도 있다. 메일박스는 누가 꺼내볼진 명시하지 않는다. 아무나 문어발식으로 전달한다는 것이다. 

만약 indirect communication에서 p1, p2, p3이 메일박스를 공유하는 경우 p1이 메시지를 보냈다면 p2와 p3 중 누가 그 메시지를 받게 되는가? p2와 p3에개 각각 따로 링크를 생성하는 방법과, 링크에 대한 receive연산을 매 시점 하나의 프로세스만 수행할 수 있도록 하는 방법이 있다. 그렇지 않으면 시스템의 메시지 수신자를 임의로 결정하여 누가 메시지를 받았는지 송신자에게 통신해주는 방법이 사용될 수도 있다. 

 

또다른 IPC 방식으로는 shared memory(공유메모리) 방식이 있다. 공유메모리 방식에서는 프로세스들이 주소 공간의 일부를 공유한다. 원칙적으로는 프로세스마다 독립적인 주소공간을 가져야하나 운영체제는 공유메모리를 사용하는 시스템 콜을 지원하여 서로 다른 프로세스들이 그들의 주소 공간 중 일부를 공유할 수 있게 해준다. 이러한 shared memory를 하는 프로세스는 자신의 주소 공간이 아님에도 공통적으로 포함되는 영역이므로 여러 프로세스가 읽고 쓰는 것이 가능하다. 실제 구현은 프로세스 a과  b가 독자적인 주소 공간을 갖고 있지만 이 주소 공간이 물리적 메모리에 매핑될 때 공유 메모리 영역에 대해서는 동일한 물리적 메모리로 매핑되는 것이다. shared memory는 프로세스끼리 상당히 신뢰하는 관계여야 하며, 한번 커널이 shared memory를 하게 시스템콜을 해주면 그 이후부터는 커널의 도움 없이 자기들끼리 알아서 소통할 수 있다.