프로세스 동기화(Process synchronization)란 말 그대로 프로세스 사이의 동기화를 말한다. 현재는 대부분 스레드 기준으로 문맥 교환(Context switching)이 일어나므로 스레드 동기화라고도 한다.
프로세스 동기화는 여러 프로세스가 공유하는 자원의 일관성을 유지하는 것이며, 이에 대해 이해하기 위해서는 '경쟁 상태'와 '임계 구역'의 개념을 먼저 이해해야 한다.
경쟁 상태는 여러 프로세스들이 동시에 데이터에 접근하는 상황에서, 데이터에 접근하는 순서에 따라 결괏값이 달라질 수 있는 상황을 말한다. 공유 데이터의 동시 접근(Concurrent access)은 데이터의 불일치 문제를 발생시킬 수 있으며, 이를 방지하기 위해 협력 프로세스 간의 실행 순서를 정해주는 매커니즘이 바로 동기화인 것이다.
경쟁 상태가 발생하는 경우는 크게 다음과 같은 세 가지 경우로 나눌 수 있다.
커널 모드로 수행 중에 인터럽트가 발생하는 경우
의도된 동작은 count--와 count++이 모두 반영되어 초깃값을 유지하는 것이지만, load 후에 인터럽트가 발생하는 경우 인터럽트의 결과는 반영되지 않고 count++만 반영된다.
이는 커널 모드의 수행이 끝나기 전에는 인터럽트가 발생하지 않도록 하는 방법으로 해결할 수 있다.
프로세스가 시스템 콜을 호출해 커널 모드로 수행 중인데 문맥 교환이 발생하는 경우
두 프로세스의 주소 공간에서는 데이터를 공유하지 않지만, 시스템 콜을 수행하는 동안에는 두 프로세스 모두 커널 주소 공간의 데이터에 접근할 수 있다. 따라서 커널 주소 공간에서 작업을 수행하는 도중 CPU 자원을 빼앗기면 경쟁 상태가 발생하게 된다.
이는 커널 모드 수행 중에는 CPU가 preempt되지 않도록 하고, 커널 모드에서 유저 모드로 돌아갈 때 preempt되도록 하여 해결 가능하다.
preempt는' 선점하다'라는 의미로, 말 그대로 다른 프로세스가 사용하고 있는 CPU를 빼앗는 것을 의미한다.
멀티 프로세서에서 공유 메모리 내의 커널 데이터에 접근하는 경우
이 경우에는 어떤 CPU의 작업이 저장되었는지에 따라 결괏값이 달라진다. 싱글 프로세서와 달리 멀티 프로세서에서는 인터럽트를 disable하는 방법으로는 이를 해결할 수 없으며, 한 번에 하나의 CPI만 커널에 들어갈 수 있도록 하는 방법도 매우 비효율적이다(두 프로세스가 서로 다른 데이터에 접근하여 경쟁 상태의 가능성이 없는 경우도 있으므로).
따라서 이는 커널 내부에 있는 각 공유 데이터에 접근할 때마다 그 데이터에 대해서만 lock을 거는 방식으로 해결할 수 있다.
임계 구역은 코드상에서 앞서 살펴본 경쟁 상태가 발생할 수 있는 특정 부분을 말한다. 즉, 공유 데이터에 접근하는 코드 부분을 지칭하는 말이다.
이러한 임계 구역으로 인해 발생하는 문제들을 해결하기 위한 조건들은 다음과 같이 정리할 수 있다.
Mutex = Mutual Exclusion
일반적인 구현 방식
이 방식의 경우 Busy waiting이 발생하여 비효율적이다. 다만 임계 구역이 매우 짧은 경우 더 효율적일 수도 있다.
Block & Wakeup 방식
임계 구역으로 진입하는 데 실패한 프로세스를 대기시키지 않고 Block한 뒤 임계 구역에 자리가 생기면 해당 프로세스를 다시 깨움으로써 Busy waiting의 CPU 낭비 문제를 해결한 방식이다.
이 방식에서 세마포어는 다음과 같이 정의한다.
이때 value는 세마포어 변수를, L은 block된 프로세스들이 기다리는 큐(queue)를 의미한다. 따라서 wait과 signal 함수도 다음과 같이 구현한다.
만약 Block을 수행하면 커널은 block을 호출한 프로세스를 정지시키고 해당 프로세스의 PCB를 wait queue에 넣는다.
Wakeup을 수행하면 block된 프로세스 P를 깨운 뒤 이 프로세스의 PCB를 Ready queue로 이동시킨다.
이때 wait나 signal 등의 함수는 같은 세마포어에 대해 두 프로세스가 동시에 실행하는 것이 불가능하다.