크게 두가지로 일컫는데 특정 자원에 접근할 때 한 개의 프로세스만 접근하게 하거나, 프로세스를 올바른 순서대로 실행하게 하는 것을 의미한다.
1. 실행 순서 제어를 위한 동기화
Book.txt 파일에 값을 저장하는 Writer 프로세스와 Book.txt 파일에 저장된 값을 읽어 들이는 Reader 프로세스가 동시에 실행 중이라고 가정해 보자. Writer 프로세스가 Book.txt 파일에 값을 저장하기도 전에 Reader 프로세스가 Book.txt를 읽는 것은 올바른 순서가 아니다. 이렇게 동시에 실행되는 프로세스를 올바른 순서대로 실행하는 것이 첫 번째 프로세스 동기화다.
2. 상호 배제를 위한 동기화
프로세스 A와 B가 동시에 실행되었다고 가정 했을 때, 당연히 실행 결과가 17만 원이 계좌에 남을 것을 기대할 것이다. 하지만 동기화가 제대로 이루어지지 않은 경우 엉뚱한 결과가 나올 수 있다.
A와 B는 '잔액'이라는 데이터를 동시에 사용하는데, A가 끝나기도 전에 B가 잔액을 읽어 버렸기 때문에 엉뚱한 결과가 나온 것이다. A와 B가 올바르게 실행되기 위해서는 한 프로세스가 잔액에 접근했을 때 다른 프로세스는 기다려야 한다. 이렇게 동시에 접근해서는 안 되는 자원에 동시에 접근하지 못하게 하는 것이 상호 배제를 위한 동기화다.
위 계좌 잔액 문제에서 동시에 실행되는 프로세스들은 전역 변수 '잔액', '총합'이라는 공동의 자원을 두고 작업을 했다. 이러한 자원을 공유 자원(shared resource)이라고 한다. 공유 자원은 전역 변수가 될 수도 있고, 파일일 될 수도 있고, 입출려장치, 보조기억장치가 될 수도 있다.
그리고 이 공유 자원 중에는 두 개 이상의 프로세스를 동시에 실행하면 문제가 발생하는 자원이 있다. 이러한 자원에 접근하는 코드 영역을 임계 구역(critical section)이라고 한다.
두 개 이상의 프로세스가 임계 구역에 진입하고자 하면 둘 중 하나는 대기해야 한다. 임계 구역에 먼저 진입한 프로세스의 작업이 마무리되면 그제서야 비로소 기다렸던 프로세스가 임계 구역에 진입한다.
임계 구역은 두 개 이상의 프로세스가 동시에 실행하면 안 되는 영역이지만, 잘못된 실행으로 여러 프로세스가 동시 다발적으로 임계 구역의 코드를 실행하여 문제가 발생하는 경우가 있다. 이를 레이스 컨디션(race condition)이라고 한다.
운영체제는 이러한 임계 구역 문제를(상호 배제를 위한 동기화를 위해) 세 가지 원칙 하에 해결한다.
뮤텍스 락(Mutex lock: MUTual EXclusion lock)은 동시에 접근해서는 안 되는 자원에 동시에 접근하지 않도록 하는 상호 배제를 위한 동기화 도구다.
임계 구역에 진입하는 프로세스는 '지금 임계 구역에 있음'음을 알리기 위해 뮤텍스 락을 이용해 임계 구역에 락을 걸고, 다른 프로세스는 임계 구역이 잠겨 있다면 기다리고, 잠겨 있지 않다면 임계 구역에 진입할 수 있다.
메커니즘
acquire 함수는 프로세스가 임계 구역에 진입하기 전에 호출하는 함수다. 임계 구역이 잠겨 있다면 임계 구역이 열릴 때까지(lock === false) 임계 구역을 반복적으로 확인하고, 임계 구역이 열려 있다면 임계 구역을 잠그는(lock = true) 함수다.
release 함수는 임계 구역에서의 작업이 끝나고 호출하는 함수다. 현재 잠긴 임계 구역을 열어주는 역할을 한다(lock = false).
acquire() {
while(lock == true) lock = true;
}
release() {
lock = false;
}
do {
acquire();
// 임계 구역
release();
} while(lock)
세마포(semaphore)는 뮤텍스 락과 비슷하지만, 조금 더 일반화된 방식이다. 뮤텍스 락이 하나의 공유 자원에 접근하는 프로세스를 상정한 방식이라면 세마포는 공유 자원이 여러 개 있을 경우 여러 개의 프로세스가 각각 공유 자원에 접근이 가능하다.
세마포의 종류에는 이진 세마포(binary semaphore), 카운팅 세마포(counting semaphore)가 있다. 이진 세마포는 뮤텍스 락과 비슷하다.
메커니즘
wait() {
while(S <= 0) // 임계 구역에 진입할 수 있는 프로세스가 0개 이하면
; // 사용할 수 있는 자원이 있는지 반복적으로 확인하고,
S--; // 임계 구역에 진입할 수 있는 프로세스 개수가 하나 이상이면 S를 1감소시키고 임계 구역에 진입
if(S < 0) {
add this process to Queue; // 해당 프로세스 PCB를 대기 큐에 삽입한다.
sleep(); // 대기 상태로 접어든다.
}
}
sigmal() {
S++; // 임계 구역에서의 작업을 완료 후 S를 1증가
if(S <= 0) {
remove a process p from Queue // 대기 큐에 있는 프로세스 p를 제거한다.
wakeup(p) // 프로세스 p를 대기 상태에서 준비 상태로 만든다.
}
}