-
기존 방법은 busy waiting으로 인한 문제점이 있었다.
-
극복 방법
- wait()
- 똑같이 semaphore value가 양수가 아니면 기다리긴 하는데, 프로세스 자신을 suspend 한다.
- suspend 하는 방법 : semaphore를 위한 waiting queue에다가 프로세스를 집어넣고, 그 프로세스를 waiting 상태로 만든다.
- 그 후 control은 CPU scheduler에게 넘어가서 다음 프로세스가 실행되게 된다.
- signal()
- 어떤 프로세스가 signal() operation을 실행하게 된다면, waiting 상태이던 프로세스는 wakeup() operation에 의해 다시 ready state로 바뀐다. 즉, CPU의 ready queue에 들어가게 된다.
-
바뀐 semaphore definition
typedef struct {
int value;
struct process *list;
} semaphore;
- process가 semaphore 에서 기다려야 한다면, semaphore의 list에서 기다리게 된다. -> signal() operation이 list에서 한 프로세스를 깨워서 실행되게 한다.
-
바뀐 wait(), signal() definition
wait(semaphore *S) {
S->value--;
if (S->value < 0) {
add this process to S->list;
sleep();
}
}
signal(semaphore *S) {
S->value++;
if (S->value <= 0) {
remove a process P from S->list;
wakeup(P);
}
}
- sleep() operation은 호출한 프로세스를 중지시킨다.
- wakeup(P) operation은 그 중지되었던 프로세스 P를 다시 시작시킨다. -> basic system call에 의해 제공된다.
-
waiting process들의 list는 PCB로 구성할 수 있다.
-
bounded waiting을 위해서 list를 FIFO queue로 구성할 수 있다. -> 이 외에도 다른 여러 알고리즘들을 사용 가능.
-
당연히, semaphore operation은 모두 atomically 실행되어야 한다. -> wait(), signal() 모두 동시에 실행될 수 없어야 한다.
- 동시에 실행되면 안 되는 이유 : 만약 semaphore count가 1이고, 두 프로세스가 동시에 wait()을 실행했다고 해 보자. 그러면 count값이 -1이 되고, 두 프로세스 모두 wait queue에 담기는 일이 생기게 되어버린다. -> 절대로 wakeup()이 실행되지 않음.
- single-processor의 경우, 이지하게 위의 operation이 실행하는 동안 interrupt를 모두 차단하면 된다. -> 그러면 그동안 CPU scheduler에게 control이 돌아가지 않게 되어 wait()이나 signal()은 오직 하나만 실행되게 된다.
- multicore의 경우, 모든 processor에서 interrupt를 무시해야 한다. 이유 : 각각의 processor에서 CPU scheduling이 일어날 수 있기 때문. -> 성능 저하로 이어지게 된다. -> SMP system에서는 compare_and_swap()이나 spinlock을 지원해주어야 한다.
-
마지막 정리
- It is important to admit that we have not completely eliminated busy waiting with this definition of the wait() and signal() operations. Rather, we have moved busy waiting from the entry section to the critical sections of application programs. Furthermore, we have limited busy waiting to the critical sections of the wait() and signal() operations, and these sections are short (if properly coded, they should be no more than about ten instructions). Thus, the critical section is almost never occupied, and busy waiting occurs rarely, and then for only a short time. An entirely different situation exists with application programs whose critical sections may be long (minutes or even hours) or may almost always be occupied. In such cases, busy waiting is extremely inefficient. - 본문 발췌
- 여기서 말하는 'we have moved busy waiting from the entry section to the critical sections of application programs'란, naive definition의 경우 프로세스가 완전히 종료하기 전까지 계속 busy waiting을 했었는데, 바뀐 definition에 의해 busy waiting을 하지 않고 semaphore count에 접근하는 데에만 busy waiting이 쓰이게 된다는 것을 의미한다. (예를 들어, S->value의 값을 바꿀 때, mutex를 사용해서 바꾸게 되면, 기존 mutex의 정의에 의해 busy waiting을 통해서 mutex가 동작하게 된다. 하지만 이 busy waiting은 semaphore의 동작 중의 S->value값을 바꾸는 아주 잠깐에 해당할 뿐 이 후에는 바로 process를 block시키기 때문에 이 외에 상황에서는 busy waiting이 발생하지 않게 된다.)
- 결론적으로, 기존에는 프로세스의 critical section이 종료될 때 까지 busy waiting을 했었는데, 바꾼 정의에 의해서 wait()이나 signal()의 critical section(mutex 등으로 구현되는, semaphore count를 변경시키는 영역만을 의미)에서만 busy waiting이 이루어져서 굉장한 효율 증진이 이루어짐을 뜻하는 것 같다.