동기화란
동기화의 의미
동시다발적으로 실행되는 많은 프로세스는 서로 데이터를 주고받으며 협력하며 실행될 수 있다. 이렇게 협력적으로 실행되는 프로세스들은 아무렇게나 실행되면 안 되는데 이를 위해 동기화가 필수다.
프로세스 동기화란 프로세스들 사이의 수행 시기를 맞추는 것을 의미한다. 이는 크게 두 가지 방법이 있다.
1. 실행 순서 제어 : 프로세스를 올바른 순서대로 실행
2. 상호 배제 : 동시에 접근해서는 안 되는 자원에 하나의 프로세스만 접근하게 하기
+ 프로세스와 마찬가지로 실행의 흐름을 갖는 스레드도 동기화 대상이다.
- 실행 순서 제어를 위한 동기화
읽기 프로세서와 쓰기 프로세서를 동시에 실행한다 해보자. 여기서 쓰기 프로세서는 텍스트 파일에 값을 저장하는 프로세서고 읽기 프로세서는 값을 불러오는 프로세서다. 이때 쓰기 프로세서가 값을 저장하기 전에 텍스트를 읽어오면 올바른 실행 순서가 아니다. 즉, 읽기는 쓰기가 완료되어 텍스트 파일에 값이 있어야 실행을 이어갈 수 있다. 이처럼 동시에 실행되는 프로세스를 올바른 순서대로 실행하는 것이 첫 번째 프로세스 동기화다.
- 상호 배제를 위한 동기화
상호 배제는 공유가 불가능한 자원의 동시 사용을 피하기 위해 사용하는 알고리즘이다. 계좌에 10만 원이 저축되어 있다 해보자. A 프로세스는 2만 원을 B 프로세스는 5만 원을 저축하려 한다. 아마 17만 원의 저축을 기대할 것이다. 하지만 동기화가 제대로 이루어지지 않는다면 이상한 값이 나올 수 있다.
A 프로세스는 잔액을 읽어온 후 잔액에 값을 더하고 나온 값을 저장하는 순서를 따를 것이다. 그런데 잔액에 값을 더한 후 값을 저장하기 전에 문맥 교환이 일어나면 다시 초깃값에 값을 더해 저장하는 B 프로세서가 실행될 것이다. 그렇게 되면 B 값을 저장하고 다시 문맥 교환이 일어나 A 과정에서 값만 저장하는 단계가 실해되어 초깃값에 A 프로세서 값인 2만 원만 더해져 12만 원의 잔액을 갖는 결과가 나올 수 있는 것이다.
이렇게 동시에 접근해서는 안 되는 자원에 동시에 접근하지 못하게 하는 것이 상호 배제를 위한 동기화다.
공유 자원과 임계 구역
접근해서 안 되는 자원이란 무엇일까? 계좌 잔액 문제에서 전역 변수 잔액이라는 공동 자원을 두고 작업을 했다 이런 자원을 공유 자원이라 한다. 공유 자원은 전역 변수가 될 수도 있고, 파일이 될 수도 있고, 입출력장치, 보조기억장치가 될 수도 있다.
동시에 실행하면 문제가 발생하는 자원에 접근하는 코드 영역을 임계 구역이라 한다. 두 개 이상의 프로세스가 임계 구역에 진입하고자 한다면 둘 중 하나는 대기해야 한다. 임계 구역에 먼저 진입한 프로세스의 작업이 마무리되면 그제야 기다리던 프로세스가 임계 구역에 들어가야 한다.
잘못된 실행으로 인해 여러 프로세스가 동시다발적으로 임계 구역의 코드를 실행하여 문제가 발생하는 경우가 있다. 이를 레이스 컨디션이라 한다. 레이스 컨디션이 발생하면 데이터의 일관성이 깨지는 문제가 발생한다.
운영체제는 이러한 임계 구역 문제를 아래 세 가지 원칙하에 해결한다.
- 상호 배제 : 한 프로세스가 임계 구역에 진입했다면 다른 프로세스는 임계 구역에 들어올 수 없다.
- 진행 : 임계 구역에 어떤 프로세스도 진입하지 않았다면 진입하고자 하는 프로세스는 들어갈 수 있어야 한다.
- 유한 대기 : 한 프로세스가 임계 구역에 진입하고 싶다면 그 프로세스는 언젠가는 임계 구역에 들어올 수 있어야 한다.
동기화 기법
뮤테스 락
뮤테스 락은 동시에 접근해서는 안 되는 자원에 동시에 접근하지 않도록 만드는 도구, 다시 말해 상호 배제를 위한 동기화 도구다.
임계 구역에 진입하는 프로세스는 자신이 임계 구역에 있음을 알리기 위해 뮤테스 락을 이용해 임계 구역에 자물쇠를 걸어둘 수 있고, 다른 프로세스는 임계 구역이 잠겨 있다면 기다리고, 잠겨있지 않다면 임계 구역에 진입할 수 있다.
뮤테스 락은 단순한 형태로 하나의 전역 변수와 두 개의 함수로 구현할 수 있다.
- 자물쇠 역할 : 프로세스들이 공유하는 전역 변수 lock
- 임계 구역을 잠그는 역할 : acquire 함수
- 임계 구역의 잠금을 해제하는 역할 : release 함수
acquire 함수는 프로세스가 임계 구역에 진입하기 전에 호출하는 함수다. 만일 임계 구역이 잠겼다면 열릴 때까지(lock -> false 될 때까지) 임계 구역을 반복적으로 확인하고 열려 있다면 잠그는(lock -> true로 바꾸는) 함수다.
release 함수는 임계 구역에서 작업이 끝나고 호출하는 함수다. (lock을 false로 바꿔 잠긴 임계 구역을 연다)
acquire() {
while(lock == true);
lock = true;
}
release() {
lock = false;
}
acquire와 release 함수를 같이 임계 구역 전후로 호출함으로써 하나의 프로세스만 임계 구역에 진입할 수 있다.
acquire 함수를 보면 임계 구역이 잠겨 있을 경우 반복적으로 lock을 확인하는 것을 알 수 있는데 이러한 대기 방식을 바쁜 대기라 한다.
세마포
뮤테스 락은 하나의 공유 자원에 접근하는 프로세스를 상정한 방식인데 공유 자원이 여러 개 있는 경우에는 여러 개의 프로세스가 각각 공유 자원에 접근이 가능해야 한다. 이를 공유 자원이 여러 개 있는 상황에서도 적용 가능한 동기화 도구인 세마포라 한다.
+ 세마포는 이진 세마포와 카운팅 세마포가 있지만 이진 세마포는 뮤테스 락과 비슷한 개념이기에 여러 공유 자원을 다룰 수 있는 카운팅 세마포를 다루겠다.
세마포는 멈춤 신호와 가도 좋다는 신호로서 임계 구역을 관리한다. 즉, 프로세스는 임계 구역 앞에서 멈춤 신호를 받으면 잠시 기다리고, 가도 좋다는 신호를 받으면 임계 구역에 들어가게 된다. 이는 단순히 하나의 변수와 두 개의 함수로 구현할 수 있다.
- 임계 구역에 진입할 수 있는 프로세스의 개수(공유 자원의 개수)를 나타내는 전역 변수 S
- 임계 구역에 들어가도 되는지, 기다려야 하는지 알려주는 wait 함수
- 임계 구역 앞에서 기다리는 프로세스에게 가도 좋다는 신호를 주는 signal 함수
뮤테스 락과 마찬가지로 세마포도 임계 구역 진입 전후로 wait()와 signal()을 호출한다.
wait() {
while(S <= 0); // 임계 구역에 진입할 수 있는 프로세스 개수가 0 이하면 반복적으로 확인
S--; // 임계 구역에 진입할 수 있는 프로세스 개수가 하나 이상이면 S를 1감소 시키고 진입
}
signal() {
S++; // 임계 구역에서 작업을 마친 뒤 1 증가
}
뮤테스 락에도 해당되는 사용할 수 있는 공유 자원이 없는 경우 프로세스는 무작정 반복하며 S를 기다리는 바쁜 대기를 반복하며 CPU 주기를 낭비하는 손해가 발생한다. 그렇기에 더 나은 방법으로 wait 함수는 사용 가능한 자원이 없을 경우 해당 프로세스를 대기 상태로 만들고, 그 프로세스의 PCB를 세마포를 위한 대기 큐에 집어넣는다. 그리고 다른 프로세스가 임계 구역에서 작업이 끝나고 signal 함수를 호출하면 signal 함수는 대기 중인 프로세스를 대기 큐에서 제거하고, 프로세스 상태를 준비 상태로 변경한 뒤 준비 큐로 옮겨 준다.
wait() {
S--;
if (S < 0){
add this process to Queue; // 해당 프로세스 PCB를 대기 큐에 삽입
sleep(); // 대기 상태로 접어든다
}
}
wait() {
S++;
if (S <= 0){
remove a process p from Queue; // 대기 큐에 있는 프로세스 p 제거
wakeup(p); // 프로세스 p를 대기 상태에서 준비 상태로 만든다
}
}
위의 내용들은 세마포를 이용한 상호 배제를 위한 동기화 기법이다. 세마포를 이용한 프로세스의 순서를 제어하는 실행 순서 동기화 방법을 알아보자. 방법은 간단하다. 세마포의 변수 S를 0으로 두고 먼저 실행할 프로세스 뒤에 signal 함수, 다음에 실행할 프로세스 앞에 wait 함수를 붙이면 된다. 그러면 먼저 실행하고자 하는 프로세스가 아닌 프로세스가 올 경우 wait을 통해 대기하게 되고 다른 프로세스가 먼저 처리된 후 signal을 호출하며 대기 중인 프로세스가 임계 구역에 진입하여 실행된다.
모니터
세마포는 매번 임계 구역 앞뒤로 wait와 signal 함수를 명시해야 하는 번거로움이 있다. 모니터는 세마포에 비해 관리가 편리한 도구다. 공유 자원과 공유 자원에 접근하기 위한 인터페이스(통로)를 묶어 관리한다. 그리고 프로세스는 반드시 인터페이스를 통해서만 공유 자원에 접근하는 것이다.
이를 위해 모니터를 통해 공유 자원에 접근하고자 하는 프로세스를 큐에 삽입하고, 큐에 삽입된 순서대로 하나씩 이용하도록 한다. 모니터는 세마포와 마찬가지로 실행 순서 제어를 위한 동기화를 제공한다. 이를 위해 조건 변수를 사용하는데 조건 변수는 프로세스나 스레드의 실행 순서를 제어하기 위해 사용하는 특별한 변수다.
조건 변수로는 wait와 signal 연산을 수행할 수 있다. 우선 wait은 호출한 프로세스의 상태를 대기 상태로 전환하고 일시적으로 조건 변수에 대한 대기 큐에 삽입하는 연산이다. 여기서 상호 배제를 위한 큐와 조건 변수에 대한 큐는 다르다는 것을 명심해야 한다. 전자는 한 번에 하나의 프로세스만 진입하도록 하기 위함이고, 후자는 모니터에 이미 진입한 프로세스의 실행 조건이 만족될 때까지 잠시 실행이 중단되어 기다리기 위해 만들어진 큐다.
wait 연산으로 일시 중지된 프로세스는 다른 프로세스의 signal 연산을 통해 실행이 재개될 수 있다. 즉, signal은 wait을 호출하여 큐에 삽입된 프로세스의 실행을 재개하는 연산이다.
정리하면 특정 프로세스가 아직 실행될 조건이 되지 않았을 때에는 wait를 통해 중단하고 특정 프로세스가 실행될 조건이 충족되어 있을 때에 signal을 통해 재개한다.
'CS 공부 > 컴퓨터 구조, 운영체제' 카테고리의 다른 글
| 운영체제(6) - 가상 메모리 (1) | 2026.02.17 |
|---|---|
| 운영체제(5) - 교착 상태 (0) | 2026.02.16 |
| 운영체제(3) - CPU 스케줄링 (0) | 2026.02.14 |
| 운영체제(2) - 프로세스와 스레드 (0) | 2026.02.13 |
| 운영체제(1) - 운영체제 시작하기 (0) | 2026.02.12 |