💡3장 목표
- 프로세스의 개념과 특성
- 프로세스 간 통신
- Shared-Memory, Message Passing
- Client-Server System
Program: Passive entity(수동적 존재)
Process: Active entity(능동적 존재)
프로그램을 수행하는 것이 프로세스이다. 프로그램이라는 사용설명서를 따라 프로세스가 실행되는 것이다. OS는 프로세스를 수행하여 다양한 프로그램을 실행한다.
🔸Process Concept
🔹Process Components
- text section: program code(Instruction)
- Program counter: 다음에 어떤 instruction을 수행할 지 알려주는 register값 (CPU내에 존재)
- Stack: 일시적인 데이터 보관 장소 (ex. function parameter, return address, local variable)
- Data section: global variable 보관(초기화 유무에 따라 구분되어 보관)
- Heap: 프로세스 실행 중에 동적으로 할당되는 메모리
메모리에 프로세스가 올라갈 때, Data section의 크기는 고정되어있고(Static memory), Stach과 Heap은 실행 중에 필요에 따라 동적으로 할당되고 해제된다(Dynamic memory)
- process in memory

- example of memory layout (c program)

🔹Process State⭐
프로세스는 능동적 존재이기 때문에 실행되면서 상태가 변화한다. 프로세스가 생성되고 종료되는 과정에서 Running, waiting, ready 상태 변화가 반복된다.
- State Transition Diagram

- New: 프로세스 생성 됨
- Running: Instruction 실행 중 (CPU를 할당받아 실질적으로 동작)
- Waiting: 프로세스가 event가 일어나기를 기다리는 중
- Ready: 프로세스가 processor(CPU)에 할당되기를 기다리는 중
- Terminated: 프로세스의 excution이 종료
New 상태에서 수행되면(admitted) Ready상태가 되고, CPU scheduler에 의해 본인이 CPU를 할당받은 경우 Running상태가 된다. Running 상태에서 exit로 종료할 수도 있고, IO request 등으로 waiting 상태로 전환할 수도 있다. event가 발생하면 다시 Ready상태로 변환된 것이다. 또는, Time sharing의 경우 아주 작은 시간 단위로 CPU를 할당받고 해제되는데, 이때 time interrupt가 발생하며 Running 상태에서 바로 Ready상태로 가기도 한다. time interrupt가 아닌 그냥 interrupt도 마찬가지다.
🔹Process Control Block(PCB)
프로세스를 관리하기 위한 테이블로, 특정 프로세스와 연관된 여러 정보를 가지고 있다.
- Process State: new, ready, running, waiting, terminated
- Program Counter
- CPU Registers
- PC와 register는 CPU안에 존재하긴 하지만, CPU에는 다양한 프로세스의 정보가 있어서 구분이 안됨. 그래서 PCB에 각 프로세스에 해당하는 값을 저장하는 것
- CPU scheduling information: 우선순위, 큐에 대한 포인터 등과 같은 scheduling 파라미터 포함
- Memory-management information
- Accounting information: CPU 사용 시간, open한 파일 정보
- IO status information

Threads
Thread는 프로그램이 수행하는 동작을 의미한다. 지금까지는 한 프로세스에서 한 동작만 하는 single thread를 살펴본 것이다. Multi thread는 한 프로세스의 자원을 공유하여 다수의 thread를 가지는 것으로, 한 프로세스가 한 번에 여러 개의 동작(task)을 수행할 수 있는 것이다.
- 여러 개의 프로세스를 사용하는 것보다 자원을 절약할 수 있어 비용이 낮고, 속도가 빠르다 (메모리를 공유하기 때문에 IPC를 하지 않아도 됨)
- 이러한 점은 여러 Thread가 병렬로 수행되는 multi-core system에서 이익을 얻을 수 있음
- Thread를 지원하는 System에서, PCB는 각 Thread에 대한 정보를 포함
PCB in Linux → Task structure
- PCB와 동일하게 프로세스의 다양한 정보 포함
- 프로세스를 복제하는 과정을 fork라고 하고, parent를 fork해서 children을 만든다

🔹Process Scheduling
어떠한 multi~ 방식이든 CPU에 비해 CPU가 처리할 프로세스의 수가 더 많다(여러개의 프로세스 ↔ 1개의 CPU). CPU사용을 최대화하기 위하여 프로세스들 사이에서 CPU를 자주 switching해줘야 한다. 그래서 CPU가 언제 어떠한 프로세스를 처리할 것인지 Scheduling이 필요하다.
→ 시분할의 목적은 각각의 프로세스들이 실행되는 동안 서로 상호작용할 수 있도록 프로세스들 사이에서 CPU를 계속해서 교체하는 것
Scheduling Queue
리소스를 관리하는 큐로, 주로 linked list로 구현되며, header는 리스트의 첫번째와 마지막 PCB를 가리키는 포인터를 가짐
- Ready queue: CPU 할당을 기다리는 프로세스를 나타냄
- 아래 그림은 ready queue가 7번과 2번 PCB를 관리하고 있으며, 2번과 7번 프로세스는 자신에게 CPU가 할당될때까지 기다림

- Wait queue: IO, event를 기다리는 프로세스를 나타냄 (각 event마다 큐 존재)
- 아래 그림은 wait queue가 3, 14, 6번 PCB를 관리하고 있으며, 이 프로세스들은 해당 디바이스 장치를 사용하기를 기다리고 있음(wailting state)

Queuing Diagram
각 원은 서비스이다. 각 프로세스는 자신이 원하는 서비스를 받기 위해 알맞는 곳에 줄서기 위해 큐에 들어간다.
- 프로세스는 ready state에서 시작해서 ready queue에서 CPU할당을 기다리고, 할당받으면 running state가 됨
- 실행되다가 IO request가 들어오면, waiting state가 되며 IO wait queue에서 대기하고, 서비스를 수행하고 interrupt가 발생하면 다시 ready state가 되며 ready queue에서 기다림
- 또는, 주어진 CPU slice를 다 사용하면 timer interrupt가 발생하고 다시 ready queue에서 기다림
- fork해서 child에게 일 시키는 것과 interrupt와 관련해서도 위와 같은 주기를 반복하게 되고, 종료되면 모든 큐에서 삭제되며 자신의 PCB와 resource를 반납함

- Running queue가 없는 이유는? CPU를 점유할 수 있는 프로세스는 1개뿐이라서 리스트를 유지할 필요가 없음! (single processe의 경우)
🔹CPU Switching
Scheduling에 따라 CPU에게 할당되는 프로세스를 바꾸는 과정
Context Switch
CPU를 다른 프로세스로 swtich하기 위해서, 이전의 프로세스 상태를 보관하고 새로운 프로세스의 보관된 상태를 복구하는 작업
- Context: 프로세스 상태, CPU register값, PC, 메모리 관리 정보 등 → 프로세스의 PCB에 표현 됨
- 커널은 과거 프로세스의 context를 PCB에 저장하고(context save), 실행할 프로세스의 PCB에서 saved context를 복구(context reload/resume)

🔹Multitasking in Moblie Systems
초기 모바일 시스템은 화면 공간과 사용자 인터페이스의 제한 때문에 한 번에 하나의 프로세스만 실행할 수 있었다. (백그라운드 실행X)
- IOS Multitasking
- single foreground process: 사용자 interface를 통해 제어되는 프로세스 (사용자 interaction 존재)
- Multiple background process: 메모리에서 실행 중이지만 화면에 표시되지 않고, 사용자 interface없이 뒤에서 동작하는 프로세스
- 한계: single/short task, 이벤트 알림 수신, 특정 장기 실행 작업(오디오 재생) 등
- Andriod Multitasking
- foreground, background 프로세스 실행 가능
- IOS에 비해 한계가 적음
- Service: background 프로세스 작업 수행을 위한 중간 역할로 background 프로세스가 중단되더라도 계속 실행 가능, 인터페이스X, 적은 메모리 사용량
🔸Operations on Processes
프로세스가 생성되고 종료되는 과정의 동작 살펴보기
🔹Process Creation
실행되는 동안 프로세스는 여러 개의 새로운 프로세스를 생성할 수 있다. 생성하는 프로세스를 Parent, 생성된 프로세스를 child라고 하며, 이 과정을 Fork라고 한다(Copy). child프로세스는 또 새롭게 다른 프로세스를 생성할 수 있으며, 그 결과 ‘프로세스의 트리’를 형성한다.
- 하나의 프로세스로는 수많은 request를 처리하기 힘들기 때문에, Fork로 Child 프로세스를 만들고 일을 시켜서 여러개의 프로세스를 동시에 수행 → Concurrently(동시성)
- ex) 웹 서버에서 수많은 client의 requset, client마다 child를 생성
Parent↔child의 resource 공유 옵션 3가지
- 모두 공유
- parent의 resource만 공유
- 공유 안함
Parent↔child의 실행 옵션 2가지 (w. address 측면에서)
- parent와 child 동시 실행 → Concurrently (child는 parent의 복제본)
- parent가 child가 종료될 때까지 대기 (child는 프로그램이 로드 된 상태)
in Unix, Linux
- Unix, Linux와 같은 대부분의 현대 운영체제들은 Process Identifier(pid)라는 식별자로 프로세스를 구분
- parent와 child process의 유일한 차이점 → pid
- Linux에서는, 초기화 될 때 create로 첫 프로세스 ‘Demon’이 생성되고(pid=1), 나머지 프로세스는 Demon의 fork로 생성됨, 각 프로세스는 excute로 실행

parent가 child가 종료될 때까지 대기하는 Unix, Linux example
- parent는 fork()로 child를 생성함
- fork()의 return값 → parent에게는 child의 pid값, child에게는 0 (이 값으로 parent와 child 구분)
- child는 exec()로 다른 프로그램을 실행시키거나 함수를 실행시켜 일을 시키고, 수행이 끝나면 exit()으로 종료
- parent는 child가 일을 끝날 때까지 기다리고, exit()가 실행되면 다시 parent의 일을 이어서 수행

- in C Program
- parent가 main을 수행하며 fork하고, child도 같은 main을 수행하기 됨
- 이때, pid를 통해 parent와 child를 구분해서 일을 시킴

in Window
리눅스와 비슷한 구조인데 system call 이름이 다름
- fork() → CreateProcess()
- wait() → WaitForSingleObject

🔹Process Termination
- 프로세스가 실행을 끝내고, exit() system call을 사용하여 프로세스를 종료
- parent가 child의 종료를 기다리는 경우, child는 exit()함수로 status를 return하고 wait()함수를 통해 parent에게 status를 전달
pid = wait(&status);
Abort()
- parent는 abort() system call로 child process들 중 하나의 실행을 종료할 수 있다
- child가 자신에게 할당된 자원을 초과하여 사용하는 경우
- child에게 할당된 task가 더 이상 필요 없는 경우
- parent가 exit()하는데, 운영체제는 parent가 종료된 뒤 child가 실행되는 것을 허용하지 않는 경우
Cascading termination
- 부모가 종료되면 자식도 종료 (Cascading: 폭포, 위에서 아래로 쏟아지는)
- 좀비(Zombie) 프로세스: parent가 wait()하지 않는 경우 → child는 정상종료 되지 않고 좀비가 됨 (wait()를 다시 호출하면 정상종료 될 수 있음)
- 고아(Orphan) 프로세스: parent가 먼저 종료되어 wait()를 호출하지 않은 경우 (정상 종료 불가능)
운영체제마다 위의 해결 방법이 다름 (cascading termination도 해결 방법 중 하나)
in Andriod
모바일 운영체제는 메모리를 회수하기 위해 프로세스를 종료해야한다. Andriod는 가장 중요하지 않은 프로세스부터 종료하기 시작한다.
위로 갈수록 중요한 프로세스:
- Foreground process
- Visible process: 프로세스 동작 상태를 foreground process를 통해 볼 수 있음
- Service process: background process지만 동작 상태를 간접적으로 볼 수 있음
- Background process
- Empty process
in Chrome Browser
크롬 브라우저는 multiprocess 구조를 사용해서 다양한 프로세스로 구현된다 (단일 프로세스 사용 시, 하나의 웹사이트에서 문제가 발생하면 전체 브라우저가 멈출 수 있음)
- Browser process: user interface, disk, network IO관리
- Renderer process: 웹 페이지 렌더링, javaScript 처리 (각 웹사이트 열 때마다 독립적인 renderer process 생성)
- Plug-in process: 각 유형의 플러그인에 대해 별도의 프로세스 생성
🔸Interprocess Communication (IPC)
OS내에서 동시에 실행되는 프로세스들은 independent하거나 cooperating하다. 다른 프로세스와 데이터를 공유하고 영향을 주고 받는 프로세스는 cooperating하다고 한다.
- independent → 문서, 음악 프로그램 동시 실행 (서로 영향X)
- dependent → 크롬 웹 브라우저의 renderer와 plug-in 프로세스 (서로 영향O)
프로세스 cooperating을 허용하는 이유는 아래와 같다. (장점)
- Information sharing: 여러 사용자가 동시에 동일 정보에 접근 가능
- Computation speedup: 특정 task를 나누어 병렬로 실행 가능
- Modularity: 시스템 기능을 프로세스들로 나누어 모듈식 형태로 구성 → 유지보수
- Convenience: 한 사용자가 동시에 작업할 다수의 task를 가질 수 있음
cooperating 프로세스들은 InterProcess Communication(IPC, 프로세스 간 통신) 기법이 필요하다. OS에서 IPC를 지원해주며, 대부분 두 가지 모델을 가진다.
- Shared memory: 프로세스들이 shared memory 공간을 통해 데이터 공유
- Message passing: 프로세스에서 프로세스로 데이터를 직접 전달하기 위해 kernel의 message queue 사용

Producer-Consumer Problem
많은 종류의 애플리케이션이 존재하는데, 이걸 몇 가지 모델로 나눌 수 있다. 그 중 하나가 생산자-소비자 모델이다.
- 대부분의 애플리케이션 프로그램은 멀티 프로세스로 동작하며, 이 때, 데이터를 생산하고 소비하는 관계를 생산자-소비자 모델이라고 함
- ex) 사용자는 프린트하기 위해 데이터를 올리고(생산자), 프린터 디바이스는 데이터에 따라 출력함(소비자)
- 생산자 프로세스는 버퍼에 데이터를 올리고, 소비자 프로세스는 올라온 순서에 따라 버퍼에서 꺼내 수행하기 위해 프로세스 간 공유(IPC) → Shared memory 또는 message passing 사용 가능
- unbounded-buffer: 버퍼 크기에 실질적 제한이 없음
- bounded-buffer: 버퍼 크기 고정, 가득 차면 대기
- 버퍼가 꽉 차더라도 다시 처음으로 돌아가도록 연결하여 생산자가 계속 정보를 생산할 수 있음(원형배열)
🔹IPC in Shared memory
- 여러 프로세스가 공유할 수 있는 shared memory 공간이 존재
- 커널의 도움 필요 없이 read/write를 통해 전달
- shared memory 공간을 만들 때만 system call이 일어나므로 빠름
- Synchronize, 메모리 공간에 동시 접근할 때 충돌이 발생하지 않도록 책임져야 함⭐(ch.6-7)
Producer-Consumer model은 데이터와 2개의 포인터를 담고있는 shared memory를 사용할 수 있다.
Bound-buffer w.Shared-memory solution
- 생산자와 소비자가 메모리(버퍼)를 공유
- in 포인터 → 버퍼 내 next free position (생산자가 넣을 위치) → inque
- out 포인터 → 버퍼 내 first full position (소비자가 꺼낼 위치) → deque
- 초기화 과정에서 둘 다 0으로 초기화

Producer Process w.Shared-memory solution
- ((in+1) % BUFFER_SIZE) == out → 버퍼가 꽉차있다 → 기다리기
- 원형 배열이라 끝까지 왔을 때 앞으로 되돌아가기 위해 위와 같은 식 사용
- 꽉차있지 않다면 생산아이템을 버퍼에 넣고, in 포인터 증가

Consumer Process w.Shared-memory solution
- in == out → 버퍼가 비어있다 → 기다리기
- 비어있지 않다면 버퍼에서 소비될 아이템을 꺼내고, out포인터 증가

🔹IPC in Message passing
- 프로세스에서 프로세스로 데이터를 직접 전달
- 프로세스 간 communication link를 형성하여 연결하고, kernel의 message queue를 통해 send/receive로 데이터를 전달
- OS마다 다른 send/recive IPC API가 존재
- 작은 data를 교환할 때 유용하고 구현이 쉽지만 느림
Communication link
프로세스를 link하는 방법은 다양하며, OS는 이 다양한 옵션을 지원한다. 애플리케이션 요구에 따라 옵션을 지정한다.
- Physical
- Shared memory, Hardware bus, Network
- Logical
- direct of indirect, Synchronous or asynchrounous, Automatic or explicit buffering
Direct Communication
- 보내는/받는 프로세스의 이름을 꼭 명시해야 함
- send(P, message): process P(목적지)에게 메세지 전달
- receive(Q, message): process Q(발송지)로부터 메세지 받음
- P와 Q가 직접적으로 연결
- 전화를 걸거나, 목적지로 우편물을 보내는 것으로 비유
Indirect Communication
- 제 3의 공간인 Mailbox를 공유해서 데이터를 주고받음
- Mailbox는 프로세스들이 메세지를 저장하고 제거하는 객체로, 고유한 id를 가진다
- mailbox 생성
- mailbox를 통해 메세지 send, receive
- send(A, message): mailbox A로 메세지 전달
- receive(A, message): mailbox B로부터 메세지 받음
- mailbox 제거
둘 이상의 프로세스가 mailbox를 공유하는 경우
P1, P2, P3가 mailbox A를 공유하고, P1이 메세지를 전달했을 때, P2와 P3 중 누가 메세지를 받을까?
- 다양한 옵션의 해결책 존재
- link가 최대 두 프로세스만 될 수 있도록 제한
- 최대 한 번에 한 프로세스만 receive()할 수 있도록 제한
- 어떤 프로세스가 메세지를 receive할지 선택
Synchronization
프로세스 간 통신 과정에서 Synchronous(blocking)과 Asynchrounous(non-blocking)을 구분하여 관리
- Blocking (Synchronous) → 완성될 때까지 기다림
- send()하고, 그 메세지가 receive될 때까지 sender 대기
- 메세지가 avail할 때까지 receiver 대기
- Non-bloking (Asynchrounous) → 완성되지 않아도 보내고 받음
- 대기 없이 메세지 send()
- receiver는 메세지를 받거나, null을 받거나 (avail메세지가 없으면 receive()는 null을 return)
- null이 return된 경우, avail 메세지가 생기면 signal handler가 신호를 보내줌 (interrupt처럼)
- send와 receive 종류에 따라 4가지 조합이 가능함
- 전부 blocking인 경우에는 Rendezvous가 발생 (아래 zero capacity 참고)
Producer-Consumer Process w.message passing solution
- shared memory 모델과 다르게 send, receive로 진행


Buffering
프로세스 간 통신을 하는데, 각 프로세스의 차이(시간, 속도, 단위 등)를 완화하기 위해 Buffer를 사용한다. 예를 들어, CPU와 I/O는 처리 속도 차이가 있어 버퍼를 사용해 통신한다.
- Zero capacity: 버퍼 사용X → 직접 만나서 전달하는 rendezvous(랑데뷰)
- Bounded capacity: n개의 메세지만 보관할 수 있는 제한된 버퍼
- Unbounded capacity: 무한개 보관 가능 버퍼, 다쓰면 앞으로 돌아가서 무한대인 것처럼..
🔹Example of IPC systems
POSIX
- UNIX, LINUX 기반 shared memory를 위한 API
- shared memory 생성 → shm-open()
- memory 크기 설정 → ftruncate()
- shared memory객체를 메모리 주소 공간으로 매핑 → mmap()
- shared memory에 쓰고 읽을 때 mmap()으로 return된 포인터 값을 사용


Mach
- Mac OS의 Messsage Passsing System, micro kernel의 IPC API 지원
- mach_msg(): 메세지 전송 및 수신
- mach_port_allocate(): communication port 설정



- mailbox가 가득찬 경우, 4가지 옵션
- 무한 대기, 최대 n ms대기, 즉시 반환, 메세지를 임시로 캐시
LPC(Local Procedure Call)
- windows는 advanced local procedure call(LPC) 기능을 통해 Message passing system으로 IPC를 구현
- LPC는 한 컴퓨터 내에서 다른 프로세스의 function call을 할 수 있음 (그냥 function call이라는 것은 한 프로세스 안에서의 call을 의미)
- 추가적으로, RPC(remote procedure call)은 다른 컴퓨터의 프로세스의 function call 가능 (밑에 나옴)
- Port(mailbox와 유사)를 사용해서 communication channel 설정 및 유지
- client는 connection port를 open
- client는 connection request를 보냄
- server는 두 개의 private communication ports 생성, 그 중 하나의 handle은 clinet에게 반환
- clinet와 server는 해당 port handle을 사용해서 통신

Pipes
파이프는 두 프로세스 간 통신을 가능하게 하는 통로 역할을 한다. 파이프에서 물이 흘러가듯 데이터를 전달한다. 이때, parent-child 관계 존재 여부에 따라 두가지 종류로 분류된다.
- Ordinary pipes
- parent-child 관계가 있는 경우 사용됨
- parent가 pipe를 생성하면, fork된 child도 같이 pipe를 물려받게되며, 이 파이프로 parent와 child 프로세스 간 통신 (외부에서 접근X)
- 데이터가 한 방향으로만 흐르는 Unidirectional(단방향)으로 작동하며, 양방향 통신을 위해 2개의 pipe 필요 → 길이가 2인 fd(file discriptor) 배열 생성
- fd[0]을 read, f[1]에 write 하는 producer-consumer style

- Named pipes
- pipe에 이름이 있기 때문에 parent-child 관계가 없이 이름으로 접근 가능, 연관성이 없는 두 프로세스 간 통신
- Bidirectional(양방향)으로 데이터 전송
- First in First out
🔹Client-Server Systems
네트워크에 연결되어 있는 두 시스템(컴퓨터와 컴퓨터) 사이의 통신하는 방법을 client-server system이라고 한다. clinet-server 시스템의 통신는 Pipe, Socket, RPCs와 같은 기법이 사용될 수 있다.
Sockets
- TCP/IP프로토콜을 쉽게 이용할 수 있는 네트워크 API
- IP주소와 Port number를 결합한 형태
- Port: service number, 이 서비스를 제공하는 프로세스를 가리킴
- ex) 161.25.19.8: 1625
- 두 개의 소켓을 통해 두 프로세스가 네트워크 상에서 소통

- socket은 연결한 뒤 데이터를 주고받는 TCP(connection-oriented) 옵션과 연결 설정을 하지 않고 주소와 데이터를 동시에 보내는 UDP(connectionless) 옵션이 존재
- special IP address: 127.0.0.1 (loopback) → 한 대의 컴퓨터에서 테스트 시 사용
Socket example in Java
시간과 날짜를 알려주는 서비스 예제
- server는 자신의 socket을 생성하고, clinet의 connection 요청을 accept()로 받음

- clinet도 자신의 socket을 생성하고, 동시에 연결 설정(요청?)을 함, 그리고 server에서 보낸 데이터를 read

RPC(Remote Procedure Call)
- 두 시스템(컴퓨터) 사이 통신을 연결하기 위한 프로시저 호출을 추상화(abstracts)하여, 원격지의 함수를 로컬 함수처럼 호출할 수 있음
- Socket처럼 서비스 식별을 위한 port 사용
- Stubs: client, server 측에 각각 존재하며, 복잡한 네트워크 통신을 도와줌
- client-side stub: 실제 서버 프로시저에 대한 proxy역할, 서버를 찾고 매개변수를 패킹(marshalls)함
- server-side stub: client로부터 메세지를 수신하고, 패킹된 매개변수를 언패킹(unpacking)한 후 서버에서 프로시저 실행
- client와 server는 애플리케이션이고, 둘의 통신을 위해 데이터를 주고받는 과정이라 Stub는 일종의 middle ware라고 볼 수 있음
- Window에서는 stub code를 MIDL(Microsoft Interface Definition Language)로 작성
- Marshalls: client의 파라미터를 메세지 형태로 변환하는 과정
- XDR(External Data Representation): 서로 다른 아키텍처 간의 데이터 표현을 처리하기 위해 XDR형식 사용
- 컴퓨터마다 big-endian과 little-endian이 다름 → 큰/작은 자리수 표현이 컴퓨터마다 달라서 변환 필요
- procedure, RPC에 대한 정보를 제공하는 Match maker
- 실패하기 쉽기 때문에 메세지는 최대 한 번(at most once)이 아니라 정확히 한 번(exactly once) 전달될 수 있도록 보장
- clinet가 procedure X를 실행하기 위해 RPC 보냄
- Matchmaker가 procedure X를 제공하는 port number P를 알려줌
- client는 port P로 원격지 호출
- server는 요청을 받고 해당 호출을 실행하여 output을 client에게 보냄
