출처 : https://www.youtube.com/watch?v=AnYN-kbCbRI&list=PLBrGAFAIyf5rby7QylRc6JxU5lzQ9c4tN&index=18
이전까지 배운 솔루션들은 Flexible 다양하게 적용이 가능하다는 장점이 있었다. 하지만 좀 복잡하다는 문제점이 있고, 에러가 발생할 가능성이 크다는 단점이 있다.
그래서 이번에는 우리가 사용하는 프로그래밍 언어 레벨에서 제공하는 솔루션인 "Monitor"를 알아보자.
High-level Mechanism
- Monitor
- Language-level constructs : 프로그래밍 언어가 Mutual Exclusion을 서포트한다.
- Object-Oriented concept와 유사
- 사용이 쉬움
Monitor
- 공유 데이터와 Critical Section의 집합, 공유데이터(읽고 싶은 책)와 CS(책을 읽기 위한 연산을 수행하는 영역)를 모아놓은 하나의 방.
- 한 번에 한 명만 들어올 수 있음
- Conditional variable
- wait(), signal() operations
모니터의 구조
- Entry queue (진입 큐) : 한 명씩 CS 진입할 수 있도록 만들어주는 큐
- 모니터 내의 procedure(=function) 수만큼 존재
- Mutual exclusion : 프로그래밍 언어가 보장해
- 모니터 내에는 항상 하나의 프로세스만 진입 가능
- Information hiding (정보 은폐)
- 공유 데이터는 모니터 내의 프로세스만 접근 가능
- Condition queue (조건 큐)
- 모니터 내의 특정 이벤트를 기다리는 프로세스가 대기하는 대기
- Signaler queue (신호제공자 큐) : 대기실에서 기다리는 프로세스에게 신호를 보내기 위해 잠깐 들어가는 공간
- 모니터에 항상 하나의 신호제공자 큐가 존재
- signal() 명령을 실행한 프로세스가 임시 대기
- 자원 할당 문제
- R_Available(=책) 라는 리소스가 1개 있다. 그러므로 한 번에 한 명만 사용할 수 있다.
- entry queue for requestR() : 자원을 요청하는 function, 책을 대출하는 명령
- entry queue for releaseR() : 자원을 반납하는 function, 책을 반납하는 명령
- requestR() : 책을 대출하는 공간
- releaseR() : 책을 반납하는 공간
- condition queue R_Free : 리소스가 있을 때(=책을 빌릴 수 있을 때) 빌릴 수 있도록 조건을 확인하는 대기하는 대기실
- signaler queue : condition queue에 있는 프로세스를 깨우는 공간
코드로 옮기면 위 그림과 같다.
대출
Procedure requestR() : 책을 대출하기위한 요청 메시지를 보낸다.
if (~R_available) : 책이 있는지 없는지 확인한다.
R_free.wait() : 만약 책이 없으면 기다린다.
R_Available <- False : 만약 책이 있으면 R_Available을 False로 변경해서 다른 사람이 빌리지 못하도록 만든다.
반납
procedure releaseR() : 책을 반납하기 위한 메시지를 보낸다.
R_Available <- True : 책을 반납하면 다른 사람이 빌릴 수 있도록 R_Available을 True로 변경한다.
R_free.signal() : 책을 빌릴 수 있으니까 대기실에 있던 사람에게 책을 빌려도 된다 signal을 보낸다.
- 자원 할당 시나리오. Initial monitor state
- 자원 R 사용 가능
- 모니터 안에 프로세스 없음
최초의 자원 R은 모니터 밖에 존재한다. R_Available(자원이 존재하는가?)는 1(True)로 시작한다.
- 자원 할당 시나리오 Monitor state 1
- 프로세스 Pj가 모니터 안에서 자원 R을 요청
entry queues for requestR()에 프로세스 Pj가 책을 빌리러 왔다. 모니터 안에는 아무도 없으므로 Pj는 모니터 안의 requestR()에 들어온다. 프로세스 Pj가 책을 빌려가므로 R_Available은 0(=False)로 변경된다. 그리고 모니터 밖으로 나와서 책(=자원 R)을 본다.
이 상황에서 entry queues for requestR()에 Pk, Pm이 도착했다고 해보자. Pj는 모니터 밖에서 R을 사용하고 있으므로 모니터 안에는 프로세스가 존재하지 않으므로 Pk는 모니터 안의 requestR()에 진입한다. 하지만 R_Available이 0이므로 Pk는 condition queue R_Free 라는 대기실에서 대기하게 된다. Pm도 Pk와 동일한 방식을 거쳐서 condition queue에서 대기하게 된다.
- 자원 할당 시나리오 Monitor state 2
- 자원 R이 Pj에게 할당 됨
- 프로세스 Pk가 R 요청, Pm 또한 R 요청
프로세스 Pj가 작업을 마치면 자원 R을 반납하기 위해 entry queues for releaseR()로 들어오게 된다. 모니터 안에는 아무도 없으니 바로 releaseR()로 들어오고, R_Available을 1로 바꾼다. 그리고 signaler queue로 들어와서 condition queue에 있는 Pk를 깨운다.
condition queue에서 대기하던 프로세스 Pk는 requestR()로 들어와서 R_Available을 0으로 바꾸고 자원 R을 사용한다.
- 자원 할당 시나리오 Monitor state 3
- Pj가 R 반환
- R_Free.signal() 호출에 의해 Pk가 Wake up
- 자원 할당 시나리오 Monitor state 4
- 자원 R이 Pk에게 할당 됨
- Pj가 모니터 안으로 돌아와서, 남은 작업 수행
프로세스 Pk가 자원 R을 사용하기 위해 모니터에서 나가면 signaler queue 에 있던 Pj는 다시 releaseR()로 돌아와 남은 마무리 작업을 수행하고 모니터 밖으로 나간다.
- 사이즈가 N인 circular queue를 사용하는 Producer-Consumer problem
- entry queue for fillBuf() : 물건을 넣는 프로세스. Producer가 호출
- entry queue for emptyBuf() : 물건을 빼가는 프로세스. Consumer가 호
- bufHasData : Consumer가 물건이 없으면 기다리는 공간. 즉, Consumer의 condition queue
- bufHasSpace : Producer가 물건이 없으면 기다리는 공간. 즉, Producer의 condition queue
- validBufs : 물건의 갯수
Procedure fillBuf(data : message) : producer가 물건을 채우기 위해 공간이 있는지 확인한다.
if (validBufs = N) then bufHasSpace.wait() : 만약 물건의 수가 N이라면(공간이 없다면), bufHasSapce에서 대기한다.
buffer[in] <- data : 만약 공간이 있다면(혹은 없었는데 생겼다면) buffer[in]에 데이터를 쌓는다.
validBufs <- validBufs + 1 : 물건을 하나 놓았으니 물건 수를 1 증가시킨다.
in <- (in + 1) mod N : 물건을 넣을 공간 in 을 업데이트 한다.
bufHasData.signal() : 내가 물건을 놓았으니, 혹시 물건을 기다리는 Consumer가 있으면 wake up 시킨다.
Procedure emptyBuf : Consumer가 물건을 가져가기 위해 물건이 있는지 확인한다.
if (validBufs = 0) then bufHasData.wait() : 만약 물건이 0개라면, bufHasData에서 대기한다.
data <- buffer[out] : 물건이 있다면 혹은 생겼다면, data에 buffer[out]에 있는 것을 저장한다. 즉, 물건을 꺼낸다.
validBufs <- validBufs - 1 : 물건을 꺼냈으니, 물건의 갯수를 1 감소시킨다.
out <- (out + 1) mod N : 물건을 꺼낼 공간 out 을 업데이트 한다.
bufHasSpace.signal() : 내가 물건을 꺼냈으니, 혹시 공간을 기다리는 Producer가 있으면 wake up 시킨다.
- Reader-Writer Problem
- reader / writer 프로세스들간의 데이터 무결성 보장 기법
- writer 프로세스에 의한 데이터 접근 시에만 상호 배제 및 동기화 필요함
- 모니터 구성
- 변수 2개
- 현재 읽기 작업을 진행하고 있는 reader 프로세스의 수
- 현재 writer 프로세스가 쓰기 작업을 진행 중인지 표시
- condition queue 2개
- reader / writer 프로세스가 대기해야 할 경우에 사용
- Procedure 4개
- reader(writer) 프로세스가 읽기(쓰기) 작업을 원할 경우에 호출, 읽기 (쓰기) 작업을 마쳤을 때 호출
- 변수 2개
- Dining philosoper problem
- 5명의 철학자
- 철학자들은 생각하는 일, 스파게티 먹는 일만 반복함
- 공유 자원 : 스파게티, 포크
- 스파게티를 먹기 위해서는 좌우 포크 2개 모두 들어야 함. 하지만 포크는 총 5개가 존재
- 철학자 = Process, 포크 = shared data
pickup(i) : 스파게티를 먹기 위해 포크를 든다.
eating : 스파게티를 먹는다.
putdown(i) : 스파게티를 먹고 나서 포크를 내려놓는다.
thinking : 생각한다. 대기실에서 대기한다.
procedure pickup(me) : 포크를 2개 들어야 한다. 이 연산은 한 명만 할 수 있다. 왜냐하면 이 밑에 있는 것들은 모니터 안에서 동작하기 때문이다.
if (numForks[me] != 2) then ready[me].wait() : 포크를 2개 들 수 있는지 확인한다. 만약 들 수 없으면 자신의 ready(=condition queue)에서 기다린다.
numForks[right(me)] <-numForks[right(me)] - 1
numForks[left(me)] <- numForks[left(me)] - 1 : 만약 내가 포크를 2개 들 수 있다면, 내 왼쪽과 오른쪽에 있는 철학자가 사용할 수 있는 포크의 수가 1개씩 줄어든다.
Procedure putdown(me) : 스파게티를 먹고 포크를 내려놓는다.
numForks[right(me)] <- numForks[right(me)] + 1
numForks[left(me)] <- numForks[left(me)] + 1 : 내 오른쪽과 왼쪽에 있는 철학자가 사용할 수 있는 포크의 수가 1개씩 늘어난다.
if (numForks[right(me)] = 2) then ready(right(me)).signal()
if (numForks[left(me)] = 2) then ready(left(me)).signal() : 내 왼쪽과 오른쪽의 철학자가 ready에서 대기하고 있고, 사용할 수 있는 포크가 2개가 되었으면 wake up 시켜준다.
- numForks : 철학자들이 사용가능한 포크의 수
- condition queues : 각 철학자들이 자기만의 대기실을 가지고 있다. 내가 포크를 쓸 수 있을 때까지 condition queue에서 대기한다.
Monitor
- 장점
- 사용이 쉽다
- Deadlock 등 error 발생 가능성이 낮다.
- 단점
- 지원하는 언어에서만 사용 가능
- 컴파일러가 OS를 이해하고 있어야 한다.
- Critical Section 접근을 위한 코드 생성
'Study > 운영체제' 카테고리의 다른 글
7. 교착 상태 Deadlock (2) - Deadlock model (2) | 2023.11.22 |
---|---|
7. 교착 상태 (1) - Deadlock and Resource types (0) | 2023.11.21 |
6. 프로세스 동기화 & 상호 배제 (6) - Eventcount / Sequencer (0) | 2023.11.18 |
6. 프로세스 동기화 & 상호 배제 (5) - Semaphore (1) | 2023.11.16 |
6. 프로세스 동기화 & 상호 배제 (4) - Spinlock (0) | 2023.11.13 |