본문 바로가기
컴퓨터 공학/운영체제

Chapter 6. 동기화 도구들 - 3부

by 조엘 2020. 11. 13.

안녕하세요 파피몬입니다! 운영체제 6단원에 대해 공부해 보았습니다. 중간고사 보고 이거 저거 준비하고 과제하고 하느라 이제서야 포스팅하네요ㅜㅜㅜ 드디어 3부입니다! 아마 4부작이 되지 않을까 싶네요! 

Abraham Silberschatz의 Operating System Concepts 10th edition과 학부 수업을 듣고 정리한 내용입니다. 오개념이 있다면 알려주세요~~

 

1,2부에서 살펴본 임계구역 문제에 대한 솔루션들은 하드웨어 기반이다. 이는 복잡하고, 응용 프로그래머는 사용할 수 없는 솔루션이다. 이번 3부에서는 Mutex Lock, Semaphore에 대해서 공부해보자!

 

<Mutex Locks (Spin Lock) >

하드웨어 기반의 솔루션(TAS, CAS 등등)을 뒤로 하고 이제 상위 수준 소프트웨어 도구를 살펴볼 차례이다!

경쟁 조건을 방지하고자 Mutex lock이라는 것을 컴공 선배님들이 만들어 두셨는데 어찌 작동하는지 알아보자. 

 

Mutex lock에서는 한 프로세스가 임계구역에 들어가기 전에 무조건 락을 획득 acquire() 해야하고, 나올 때는 락을 반환 release() 해야한다. 이는 중간에 인터럽트 없이 원자적으로 수행되어야 함을 기억하자!

Mutex lock은 available이라는 변수로 임계구역에 들어갈 수 있는지 여부를 알려준다! 코드로 이해해 보자. 

 

acquire() {
	while(!available)
    	    ; //busy waiting
	available = false;
}

release() {
	available = true;
}

-----Mutex Lock 이용한 해결-----

while(true) {
	acquire();
    	    CRITICAL SECTION
	release();
    	    REMAINDER SECTION
}

 

한 프로세스가 임계 구역에 있는 동안, 임계 구역에 들어가고 싶어하는 다른 프로세스는 while(!available) 에서 무한히 기다리고 있어야 한다. 

여기서 눈에 띄는 점은, acquire()한 프로세스가 무조건 release()를 해줘야 한다는 점이다. 이는 세마포와의 차이점이 되니 기억해 두도록 하자. 

 

또한, Busy waiting 방식으로 acquire()이 구현되어 있다. 그래서 이를 Spin lock이라고 한다. 

장점으로는 release()되는 대로, 다른 프로세스가 문맥 교환 없이 빠르게 작업을 수행할 수 있다. 

단점으로는 Busy waiting 자체의 문제인, 아무 일도 안하면서 CPU 자원을 낭비하는 점이다. 

 

<Semaphore>

이번에는 조금 더 정교하게 동기화 할 수 있는 방법인 Semaphore를 알아보자. 

Semaphore 에는 S라는 쓸 수 있는 자원의 수를 나타내는 정수 변수가 있다. 

Semaphore를 제어하는 방법은 위의 mutex lock의 acquire/release 같이 wait/signal이 있다. 코드로 이해해 보자. 

wait(S)
{
    while(S<=0)
        ; //busy waiting
    S--;
 }
 
 signal(S)
 {
 	S++;
 }

어딘가 굉장히 mutex lock과 비슷해 보이는 동작 방식이다. 초기화 해준 자원의 갯수 S가 0이 된다면, 다른 프로세스가 다 사용하고 반납할 때 까지 busy waiting으로 기다리게 된다. 

 

여기서 mutex lock과의 차이점은 S의 자원의 갯수가 여러 개라면, 꼭 wait()을 호출한 주체가 signal()을 호출해야만 다른 프로세스가 자원을 획득할 수 있는게 아니란 것이다. 프로세스끼리 자원을 나눠쓰고 반납하는 한층 평화로운 동기화가 가능하다. 

물론, S가 1개라면 위의 Mutex lock과 굉장히 유사해진다. 

 

근데 여기까지 읽으면 이게 더 정교해진건가...? 의심이 들것이다. 그래서 한층 더 발전한 Semaphore를 소개한다. 

 

<바쁜 대기를 탈피한 Semaphore>

Busy waiting 자체의 단점을 포스팅 위에서 간략하게 설명했다. 하는 일도 없으면서 CPU를 차지하는 것! 이를 탈피하고자, Semaphore는 일시 중지 기능을 구현해냈다. 프로세스의 일시 중지가 어찌 이뤄지는지 살펴보자. 

typedef struct{
    int value;
    struct process * list; //value가 0일때 요청 들어온 프로세스를 대기 상태로 저장
}

wait(semaphore *S){
    S->value--;
    if(S->value < 0){
    	이 프로세스를 S->list에 넣기
        sleep();
    }
}

signal(semaphore *S){
    S->value++;
    if(S->value < 0){
    	S->list에서 부터 프로세스 P를 꺼내오기
        wakeup(P);
    }
}

 

프로세스를 세마포의 대기큐에 넣어 놓음으로써 프로세스의 일시 중지가 이뤄진다. 

만일 세마포의 value가 0인 상태에서 자원을 요청한 프로세스가 있다면, sleep() 함수를 통해 처리된다. 

  1. 프로세스를 세마포의 대기 큐에 넣기

  2. 프로세스 상태를 대기로 전환

  3. 그 후 제어를 CPU 스케줄러로 이동

  4. 스케줄러는 다른 프로세스 실행시킴

 

이렇게 잠들게 된 프로세스는 signal()연산에서 wakeup() 함수를 통해 일어나게 된다. 

  1. 프로세스의 상태를 대기에서 준비 완료로 변경

  2. 프로세스를 세마포의 대기 큐에서 뺀다

  3. 준비 완료 큐에 넣어준다

 

이를 통해 이제, 세마포는 음수 값을 가질 수 있게 된다. 생각해보면, 이 값의 절댓값은 세마포의 자원을 기다리고 있는 프로세스의 갯수가 된다. 

 

마지막으로 우리가 생각해 봐야 할 점은 wait(), signal() 연산이 원자적으로 수행될 수 있는가? 이다. 

만약 단일 코어 처리기에서 위와 같은 구현 방법을 사용한다면, wait()/signal() 연산 수행시 인터럽트를 끄면 해결 될 것이다. 

하지만 다중 코어 처리기에서는 조금 생각을 해보아야한다. 모든 처리기의 인터럽트를 끄게 되면 성능이 너무 저하되기 때문이다. 그래서 이 경우에는 wait()/signal() 연산 수행시 2부에서 다뤘던 CAS, 혹은 위에서 다룬 Spin Lock 같은 방식으로 해결한다고 한다. 

 

타이틀을 바쁜 대기를 탈피한 Semaphore라고 했으나, 다중 코어 처리기에서는 완벽히 처리하지 못했다.

하지만 성능이 나쁜 것은 아니다. Spin Lock은 위에서 언급했듯, 문맥 교환 없이 빠른 처리가 가능 하기 때문에 장점도 있다. 

 

책에 따르면, Busy waiting을 wait(), signal() 연산의 임계 구역에만 국한하였고, 이는 매우 짧다고 한다. 따라서 임계구역은 거의 항상 비어있으며, Busy waiting은 드물게 발생하며, 발생하더라도 매우 빨리 끝난다고 한다. 

 

 

반응형

댓글