본문 바로가기

Information Technology/OS

[운영체제] Process Management(2)

이 글은 개인의 학습을 위해 정리한 글입니다. 이점 참고하고 읽어주세요;)


- 자원 공유

프로세스의 생성 -> 부모 프로세스의 복제로부터 시작

완전히 같은 내용의 프로세스라면 메모리에 똑같은 두 개의 프로세스를 올리는 게 메모리의 낭비

때문에 리눅스 등의 일부 운영체제에서는 무조건 프로세스를 복제하기보다는 일단 공유할 수 있는 자원은 최대한 공유.

부모 프로세스를 카피해서 코드 스택 데이터를 복사하는 게 원칙이지만(공유 x), 리눅스 등의 일부 운영체제에서는 일단 카피하지 않고 자식이 부모의 주소 공간을 공유. 프로그램 카운터만 따로 하나를 복사해서 같은 위치를 가리키고 있게 함.

그런 식으로 실행을 하다가 부모와 자식이 내용이 달라지는 순간에(ie. 데이터 영역의 변수 값이 달라지는 경우) 그제서야 부모와 공유하던 주소 공간의 일부를 카피해서 자식이 가지게 됨.

이런 기법을 Copy-On-Write(COW) 기법. Write가 발생했을 때, 그때 Copy를 하겠다는 의미. Write가 발생한다는 건, 원래 있던 내용을 변경한다는 것. 내용이 변경된다는 것. 그 이전까지는 그냥 부모의 주소 공간과 내용을 공유.

이것도 부모의 주소공간을 통째로 복사하지 않고, 메모리에 올라가서 실행될 잘게 쪼개진 부분만 복사해서 필요한 내용을 write.

(일반 프로세스의 경우도 프로그램 전부가 메모리에 올라가서 실행되지 않고 필요한 부분들만 메모리에 올라가서 실행).

 

fork()라는 시스템 콜을 통해 프로세스를 복사.

일단 fork()를 통해 기존 프로세스를 복사하고, exec()를 통해 새로운 프로세스를 덮어 씌움. 기존의 템플릿이 있으면 프로그램을 생성하고 만들기 더욱 쉬움.


fork() 함수를 호출하는 게 시스템 콜.

 

왼쪽이 부모 프로세스. 오른쪽이 자식 프로세스.

왼쪽 부모 프로세스에서 main 함수가 시작되고, int형 변수 pid를 생성하고, pid에 fork()를 실행.

fork()를 실행함으로써 자식 프로세스가 오른쪽에 생성.

CPU의 프로세스 카운터는 부모 프로세스의 fork를 가리키고 있음. 오른쪽의 자식 프로세스는 부모 프로세스를 그대로 가져왔음. 현재 PC는 pid=fork()를 가리키고 있기 때문에 자식 프로세스 main함수의 시작부터 프로그램이 시작되지 않고 fork()부터 코드를 시작.

때문에 왼쪽 부모 프로세스는 처음의 printf를 실행하고 다음 코드를 실행하지만,

오른쪽 자식 프로세스는 첫번째 printf를 실행하지 않고, 바로 pid=fork()부터 실행

이럴 때 발생할 수 있는 문제점

1) 복제된 자식 프로세스가 마치 자신이 원본인 부모 프로세스인 것처럼 작동

2) 하나의 프로세스로부터 복제된 프로세스들이기 때문에 동일한 작동 원리를 가진 프로세스들로 메모리가 가득 참

 

이러한 문제를 해결하기 위해 fork() 함수의 return value가 다름

부모 프로세스에서 fork() 함수의 return value(여기서는 pid의 값)는 양수(>0)

자식 프로세스에서 fork() 함수의 return value는 0.

이렇게 fork() 함수의 return value를 통해 원본 프로세스와 복제 프로세스를 구분하고 각각 다른 작업을 시킬 수 있음.

exec 시스템 콜은 fork()를 통해 복제된 자식 프로세스를 완전히 새로운 프로세스로 만들어줌.

execlp 함수를 통해 exec() 시스템 콜을 호출.

exec 시스템 콜을 만나게 되면, 부모 프로세스의 구조를 잊고 새로운 프로그램으로 덮어 씌우고 새로운 함수의 main 함수로부터 시작을 하게 됨.

한 번 exec 시스템 콜을 통해 새로운 프로세스가 되면 그때는 이전의 부모 프로세스로 다시 돌아올 수 없음.

꼭 자식 프로세스를 만들고 exec을 할 필요는 없음. 하지만 이렇게 하면 exec 이후의 printf는 실행이 불가능.

execlp를 사용할 때에는

1) 사용할 프로그램 이름을 두 번 입력하고("echo", "echo")

2) 전달할 인자(argument)를 따옴표 안에 입력해서 콤마로 구분하여 전달하고("hello", "3")

3) 마지막엔 (char*) 0을 전달

위의 프로그램은 먼저 1을 출력하고, 그다음으로 3을 출력하고, 2는 출력하지 않고 프로그램 종료.


프로세스를 blocked 상태로 보내는 것이 wait() 시스템 콜

보통 자식 프로세스를 만든 후에 wait 시스템 콜 호출. 즉, 자식 프로세스가 종료되길 기다리면서 block 상태 유지.

그러다가 자식 프로세스가 종료되면 부모 프로세스가 ready 상태가 되고 CPU를 할당받는 걸 기다림.

 

wait 시스템 콜의 대표적 예가 리눅스에서 명령 프롬프트에서 A 프로그램에서 B 프로그램을 호출하면

B 프로그램이 종료되기 전까지 A 프로그램은 wait 상태


자발적 종료: 마지막 문장을 실행 후(중괄호가 마무리될 때), 혹은 코드 중간에 exit이 선언될 때 exit() 시스템 콜 호출.

비자발적 종료: 자식 프로세스가 한계치를 넘는 CPU를 요구할 때, 자식 프로세스에게 할당된 작업이 모두 종료됐을 때, 등등의 상황에서 자식 프로세스를 강제로 종료. 또는 사용자가 강제 종료키를 사용할 때. 마지막으로 부모 프로세스가 먼저 종료될 때(프로세스는 무조건 부모 프로세스보다 자식 프로세스가 먼저 종료되어야 함).


fork: 부모 프로세스를 복제하여 자식 프로세스를 생성

exec: 새로운 프로그램으로 덮어 씌움

wait: 자식 프로세스가 종료될 때까지 잠들어서 기다림

exit: 모든 자원을 반납하고 프로세스를 종료시킴


프로세스는 매우 독립적.

경우에 따라서는 프로세스끼리 협력을 해야 더욱 효율적인 경우도 존재함.(정보를 주고받는 등의)

이러한 협력을 IPC(Interprocess Communication).

1) Shared memory: 원칙적으로 프로세스들은 독자적인 주소 공간을 가지고 있음(코드, 데이터, 스택). 때문에 자신의 주소 공간만 볼 수 있음. 그럼에도 불구하고 일부 주소 공간을 두 프로세스가 공유하는 방식. 프로세스의 주소 공간을 할당할 때 일부러 공유하는 프로세스의 주소 공간과 겹치도록 생성. 이것 역시 프로세스끼리

알아서 주소 공간을 공유하지 않고, 커널에게 주소 공간을 공유한다는 시스템 콜을 통해 미리 맵핑을 하고 그 이후부터 사용자 프로세스끼리 공유하는 영역에서 작업. 

 

2) Message passing: 프로세스 A가 프로세스 B에게 메시지를 전달하고, 이를 통해 프로세스 운영. 하지만 프로세스는 매우 독립적이기 때문에 자신의 데이터, 스택 내용만 접근하고 다룰 수 있지, 프로세스끼리 메시지를 전달할 방법은 없음. 때문에 중간의 커널을 통해 메시지를 주고받음. 

1) Direct Communication: 상대방의 이름을 명시. 메시지를 전달하는 프로세스가 메시지를 누구에게 전달할지 목적지 프로세스를 명시

2) Indirect Communication: 상대방 프로세스의 이름 명시 X. Mailbox에 메시지를 집어넣고 누가 받을지는 명시 x. 경우에 따라서는 아무에게나 보낼 수 있음.

두 방식 모두 중간에 커널이 존재.

* Thread: 스레드는 완전히 같은 프로세스 안에서 동작하기 때문에 협력이 당연히 가능함

 

 

'Information Technology > OS' 카테고리의 다른 글

[운영체제] CPU Scheduling(2)  (0) 2020.01.28
[운영체제] CPU Scheduling(1)  (0) 2020.01.26
[운영체제] Process Management(1)  (0) 2020.01.26
[운영체제] Process(2)  (0) 2020.01.24
[운영체제] Process(1)  (0) 2020.01.20