CS/OS

[운영체제] 6-3. Semaphore의 활용

F12:) 2023. 10. 12. 01:27

  오늘은 이전 글에서 다룬 semaphore를 어떻게 활용하는 지에 대해서 조금 더 자세히 알아보겠습니다.

 


Semaphore는 기본적인 critical section problem을 해결하는데 사용됩니다.

 

이전 글에서 언급했지만, 한 번 더 확인해봅시다.

우리에게 익숙한 Producer, Consumer 문제를 예로 들어보겠습니다.

semaphore counter = 0;
final int BUFFER_SIZE = 10;
semaphore empty = BUFFER_SIZE;
semaphore mut_ex = 1;

void producer(){
	while(true){
    	produce();
        semWait(empty);
        semWait(mutex);
        
        append();
        
        semSignal(mut_ex);
        semSignal(counter);
    }
}

void consumer(){
	while(true){
    	semWait(counter);
        semWait(mut_ex);
        
        take();
        
        semSignal(mut_ex);
        semSignal(empty);
        consume();
    }
}

void main(){
	parbegin(producer1, producer2, ... , consumer);
}

producer는 우선 produce를 실행한 후, empty와 mut_ex를 semWait로 실행하여 entry section을 수행합니다.

여기서 empty는 버퍼가 비었을 때만 사용할 수 있게 한 것이고, mut_ex는 critical section의 mutual exclusion을 보장하기 위해 설정한 것입니다.

 

특이한 것은 critical section을 수행한 후, semSignal(counter);를 수행하는 것인데, 이는 Buffer 내에 있는 produce가 생성한 것의 개수를 나타내는 것입니다.

 

semSignal(counter)를 producer에서만 선언한다면, 계속 counter의 값은 증가하게 되고 의미없는 코드가 됩니다.

 

 

하지만 여기서는 producer와 consumer가 상호작용합니다. 목적이 다른 프로세스이지만, 같은 데이터 영역을 접근하므로 critical section에서 충돌할 수 있습니다. 여기서 Consumer는 Produce한 것이 존재하지 않으면 작동할 수 없기 때문에 가장 먼저 counter가 0보다 큰지 알아보기 위해 semWait(counter)를 진행합니다. 이를 지나갔으면, critical section을 수행하는지 확인하기 위해 semWait(mut_ex)를 수행하고, take()한 후 주어진 코드처럼 흘러갑니다.

 

 

 

즉, 다시 정리해보면 counter는 produce 됐을 때만 consume할 수 있도록 해주는 semaphore이고, empty는 Buffer가 꽉 찼을 때는 producer의 역할을 멈추고, 비어있을 때는 consumer의 역할을 멈추는 semaphore이고, mut_ex는 critical section에서 mutual exclusion을 보장하기 위해 사용하는 semaphore입니다.

 

여기서 느끼셨을 수 있겠지만, 보통 critical section의 mutual exclusion을 보장하기 위해 사용하는 semaphore의 이름은 mut_ex로 칭하며 초기값은 1이 되는 것이 통상적입니다.

 

 

여기서 왜 counter는 semSignal을 먼저할까? 라는 의문점이 들 수 있는데, 이는 counter의 기능에 집중하면 알게 됩니다.

counter는 0 이하로 떨어짐을 방지하기 위한 것입니다. 따라서 semSignal을 먼저 사용합니다.

반면 mut_ex는 1이상으로 올라가는 것을 방지하기 위한 것이므로 semWait를 설정합니다. 물론, mut_ex가 음수가 되지 않게도 보장하지만요. 

 

딱, 한가지만 주의합시다. semWait와 semSignal의 총 개수는 동일해야합니다. 아래의 그림처럼 말이죠.

 

 

이처럼 각 semaphore의 기능을 우선적으로 이해하면, semaphore의 활용을 조금 더 쉽게 이해하고 적용할 수 있습니다.

 

 

General Synchronization Tool

일반적인 semaphore의 활용은 이미 다뤘고, 이제는 특수하게 사용하는 경우에 대해서 알아봅시다.

semaphore는 각 프로세스의 순서를 결정하는 데에도 쓰입니다.

 

예를 들어 reader와 writer의 프로세스가 있다고 해봅시다. reader는 메모리에 값을 read하는 프로세스이고, writer는 메모리에 값을 write합니다.

 

그렇다면, 아래와 같이 되어있을 때, 순서를 보장받고 writer 프로세스가 항상 먼저 실행되게 됩니다.

Wwriter가 semaphore를 넘어야 Reader가 작동을 함.

 

Readers / Writers Problem

semaphore로 아주 특별한 케이스를 다룰 수 있습니다. 두 개의 프로세스 또는 역할이 존재할 때, 하나의 역할은 critical section의 mutual exclusive를 보장하지 않아야하고, 나머지 하나는 critical section을 mutual exclusive하게 해야합니다.

 

이럴 땐 어떻게 진행해야할까요??

바로 코드를 통해서 확인해보겠습니다. 예시는 reader와 writer로 진행합니다.

int readCount;
semaphore rsem = 1;
semaphore wsem = 1;

void main(){
	readCount = 0;
    parbegin(reader1, reader2, ..., writer1, writer2, ...);
}

void writer(){
	while(true){
    	semWait(wsem);
        write();
        semSignal(wsem);
    }
}

void reader(){
	while(true){
    	//  #### 공유변수인 readCount를 위한 fence ####
    	semWait(rsem);
	        readCount++;
        	if(readCount == 1) semWait(wsem); // (1)
        semsignal(rsem);
        // #####################################        
        read();

        
        semWait(rsem);
	        readCount--;
    	    if(readCount == 0) semSignal(wsem);
        semSignal(rsem);
    }
}

(1)이 만약이 true라면, 자신이 첫 번째 reader임을 의미합니다. 이 때, 해주어야할 것은 writer를 중단해야한다는 것입니다.

 

reader가 critical section에서 정보를 읽고 있는데, writer가 거기에 접근하는 중이지만 reader가 critical section에 접근하면 문제가 되는 것이죠.

 

따라서 (1)로써 자신이 첫번째 reader라면 writer를 기다리게 하기 위해 rsem을 이용합니다. 

 

 

이 흐름은, 코드를 보면서 하나하나 천천히 이해하는 것이 가장 관건일 것 같습니다.

 

 

----- (추가내용) -----

Binary Semaphore

일반적인 semaphore보다는 좁은 개념입니다. Binary Semaphore는 이름에서도 알 수 있듯이 Semaphore의 값이 0 또는 1을 갖습니다. 음수 또는 2 이상의 수를 갖지 않죠. 따라서 우리는 critical section을 수행할 수 있는지 없는지만 0과 1을 통해서 알 수 있습니다.

 

기존 semaphore의 원리와 동일하게 만약 semaphore가 0이라면 block됩니다.

또한 semaphore가 1이라면 0으로 만들고 critical section을 수행하게 합니다.

 

만약 semaphore의 값이 1이고 어떤 프로세스가 critical section을 수행하기 위해 sempahore를 0으로 만들고, critical section을 모두 수행한 후 다시 semaphore의 값을 1로 바꾸게 될 때 우리는 원래 block이었던 다른 프로세스들을 하나씩 Queue에서 pop을 통해 ready queue에 넣었습니다.

 

하지만 Binary Semaphore는 Block된 모든 프로세스를 깨우게 됩니다. 이것이 Binary Semaphore의 특징입니다.

 

 

사실 Semaphore의 좁은 범위 내에서 Binary Semaphore가 작동할 수 있으므로 보통 이 두개의 기능을 운영체제에서는 동시에 제공한다고 합니다.

 


오늘은 semaphore를 활용하는 방법에 대해서 알아보았습니다. 감사합니다.

'CS > OS' 카테고리의 다른 글

[운영체제] Deadlock 처리  (1) 2023.10.21
[운영체제] Deadlock  (0) 2023.10.21
[운영체제] 6-2. Semaphore  (4) 2023.10.11
[운영체제] 6-1. Testset Instruciton  (2) 2023.10.10
[운영체제] 5-3. Critical Section Problem-SW Solution  (0) 2023.10.07