동시다발적으로 실행되는 프로세스들은 서로 협력하며 영향을 주고 받는다.
이 과정에서 자원의 일관성을 보장해야 한다.
-> 자원의 일관성을 보장하기 위해서 프로세스들의 동기화를 고려해야 한다.
1. 동기화의 의미
공동의 목적을 위해 동시에 수행되는 프로세스
ex) 워드 프로세서 프로그램
- 맞춤법 검사 프로세스
- 입력 내용을 화면에 출력하는 프로세스
이렇게 막 실행해도 괜찮은가?
-> No. 올바른 수행을 위해 프로세스들은 동기화되어야 한다.
(1) 프로세스 동기화란?
프로세스 동기화란 프로세스들의 수행 시기를 맞추는 것이다.
이는 크게 두 가지를 의미한다.
- 실행 순서 제어 : 프로세스를 올바른 순서대로 실행하기
- 상호 배제 : 동시에 접근해서는 안 되는 자원에 하나의 프로세스만 접근하게 하기
-> 실행의 문맥을 갖는 모든 대상은 동기화 대상이기에 스레드도 동기화 대상이다.
1) 실행 순서 제어를 위한 동기화 : reader write problem
Writer : Book.txt 파일에 값을 저장하는 프로세스
Reader : Book.txt 파일에 저장된 값을 읽어들이는 프로세스
Reader와 Writer 프로세스는 무작정 아무렇게나 실행되어선 안된다. -> 실행의 순서가 있기 때문
Reader 프로세스는 'Book.txt 안에 값이 존재한다'는 특정 조건이 만족되어야만 실행 가능
2) 상호 배제를 위한 동기화 : Bank account poblem
공유가 불가능한 자원의 동시 사용을 피하기 위한 동기화
-> 한 번에 하나의 프로세스만 접근해야 하는 자원에 동시 접근을 피하기 위한 동기화
ex) 현재 계좌에 잔액 : 10만원
1. 프로세스 A
(1) 계좌의 잔액을 읽어 들인다.
(2) 읽어 들인 잔액에 2만원을 더한다.
(3) 더한 값을 저장한다.
2. 프로세스 B
(1) 계좌의 잔액을 읽어 들인다.
(2) 읽어 들인 잔액에 5만원을 더한다.
(3) 더한 값을 저장한다.
프로세스 A, 프로세스 B를 동시에 실행하면 17만원이 계좌에 남을까?
동기화가 제대로 이루어지지 않을 경우 위 그림과 같이 된다.
-> 프로세스 A와 B는 '잔액'이라고 하는 데이터를 동시에 사용하는데 프로세스 A가 끝나기도 전에 프로세스 B가 '잔액'이라고 하는 자원에 접근했기 때문에 위 그림과 같은 결과가 발생한다.
-> 이것은 동시에 실행하면 문제가 발생하는 자원에 접근하는 코드 영역인 임계 구역에 동시에 접근해 읽기/쓰기 작업이 수행되었기 때문!
2. 공유 자원과 임계 구역
(1) 공유 자원(Shared Resource)
- 여러 프로세스 혹은 스레드가 공유하는 자원
- 시스템 안에서 각 프로세스, 스레드가 함께 접근할 수 있는 모니터, 프린터, 메모리, 파일, 데이터 등의 자원이나 전역 변수
- 공유 자원을 두 개 이상의 프로세스가 동시에 읽거나 쓰는 상황을 레이스 컨디션(Race Condition)이라고 한다.
(2) 임계 구역(Critical Section)
- 공유 자원 중 동시에 실행하면 문제가 발생하는 자원에 접근하는 코드 영역
- 임계 구역에 진입하고자 한다면 진입한 프로세스 이외에는 대기해야 한다.
임계 구역에 동시에 접근해 읽기/쓰기 작업이 수행된다면 자원의 일관성이 깨지게 된다.
1) 운영체제가 임계구역 문제를 해결하는 세 가지 원칙
- 상호 배제(mutual exclusion) : 한 프로세스가 임계 구역에 진입했다면 다른 프로세스는 들어올 수 없다.
- 진행(progress) : 임계 구역에 어떤 프로세스도 진입하지 않았다면 진입하고자 하는 프로세스는 들어갈 수 있어야 한다.
- 유한 대기(bounded waiting) : 한 프로세스가 임계 구역에 진입하고 싶다면 언젠가는 임계 구역에 들어올 수 있어야 한다.
2. 동기화 기법
(1) 뮤텍스 락
- 뮤텍스는 프로세스나 스레드가 공유 자원을 acquire() ( =lock() )을 통해 잠금 설정하고 사용한 후에는 release() ( =unlock() )을 통해 잠금을 해제하는 객체이다. 다른 프로세스나 스레드는 잠긴 코드 영역에 접근할 수 없고 해제는 그와 반대이다. 또한 뮤텍스는 잠금 또는 잠금 해제라는 상태만을 가진다.
- 상호 배제를 위한 동기화 도구(자물쇠 역할)
1) 형태
전역 변수 1개, 함수 2개로 구성되어 있다.
- 자물쇠 역할 : 프로세스들이 공유하는 전역 변수 lock
- 임계 구역을 잠그는 역할 : acquire 함수
- 프로세스가 임계 구역에 진입하기 전에 호출
- 임계 구역이 잠겨 있다면
- 임계 구역이 열릴 때까지(lock이 false가 될 떼까지) 임계 구역을 반복적으로 확인
- 임계 구역이 열려 있다면
- 임계 구역을 잠그기(lock을 true로 바꾸기)
- 임계 구역의 잠금을 해제하는 역할 : release 함수
- 임계 구역에서 작업이 끝나고 호출
- 현재 잠긴 임계 구역을 열기(lock을 false로 바꾸기)
acquire(){
while (lock == true) /* 만약 임계 구역이 잠겨 있다면 */
; /* 임계 구역이 잠겨 있는지를 반복적으로 확인 */
lock = true; /* 만약 임계 구역이 잠겨 있지 않다면 임계 구역 잠금 */
}
release() {
lock = false; /* 임계 구역 작업이 끝났으니 잠금 해제 */
}
2) 과정
acquire(); // 자물쇠 잠겨 있는지 확인, 잠겨 있지 않다면 잠그고 들어가기
// 임계 구역 // 임계 구역에서의 작업 진행
release(); // 자물쇠 반환
3) 바쁜 대기(busy waiting)
acquire() 함수의 일부분으로 계속해서 임계 구역이 잠겨있는지를 반복적으로 확인하는 작업
while (lock == true) /* 만약 임계 구역이 잠겨 있다면 */
; /* 임계 구역이 잠겨 있는지를 반복적으로 확인 */
(2) 세마포어
- 세마포어는 일반화된 뮤텍스
- 공유 자원이 여러 개 있는 경우에도 적용 가능
- 상호 배제를 위한 동기화 도구
- 실행 순서 제어를 위한 동기화 도구
1) 개념
- 임계 구역 앞에서 멈춤 신호를 받으면 잠시 기다리기
- 임계 구역 앞에서 가도 좋다는 신호를 받으면 임계 구역 진입
2) 형태
전역 변수 1개, 함수 2개로 구성되어 있다.
- 임계 구역에 진입할 수 있는 프로세스의 개수(사용 가능한 공유 자원의 개수)를 나타내는 전역 변수 S
- 임계 구역에 들어가도 좋은지, 기다려야 할지를 알려주는 wait 함수
- 임계구역 앞에서 기다리는 프로세스에 '이제 가도 좋다'고 신호를 주는 signal 함수
wait()
// 임계 구역
signal()
3) wait 함수
wait() {
while ( S <= 0 ) /* 만일 임계 구역에 진입할 수 있는 프로세스 개수가 0개 이하라면 */
; /* 사용할 수 있는 자원이 있는지 반복적으로 확인하고 */
S--; /* 임계 구역에 진입할 수 있는 프로세스 개수가 하나 이상이면 S를 1 감소시키고 임계 구역 진입 */
}
4) signal 함수
signal() {
S++ /* 임계 구역에서의 작업을 마친 뒤 S를 1 증가시킨다 */
}
5) 예제
세 개의 프로세스 P1, P2, P3가 두 개의 공유 자원(S=2)에 P1, P2, P3 순서로 접근한다고 가정
Step1. 프로세스 P1 wait 호출, S는 현재 2이므로 S를 1 감소시키고 임계 구역 진입
Step2. 프로세스 P2 wait 호출, S는 현재 21이므로 S를 1 감소시키고 임계 구역 진입
Step3. 프로세스 P3 wait 호출, S는 현재 0이므로 무한히 반복하며 S 확인
Step4. 프로세스 P1 임계 구역 작업 종료, signal() 호출. S를 1 증가
Step5. 프로세스 P3 S가 1이 됨을 확인. S는 현재 1이므로 S를 1 감소시키고 임계 구역 진입
6) Busy Waiting의 해결 방법
- 문제점 : CPU 사이클 낭비
- 마치 탈의실 문이 열렸는지/닫혔는지 반복적으로 확인하는 것과 같다
- 사용할 수 있는 자원이 없을 경우 대기 상태로 만든다
- 해당 프로세스의 PCB를 대기 큐에 삽입
- 사용할 수 있는 자원이 생겼을 경우 대기 큐의 프로세스를 준비 상태로 만든다.
- 해당 프로세스의 PCB를 대기 큐에서 꺼내 준비 큐에 삽입
wait() {
S--;
if ( S < 0 ) {
add this process to Queue; /* 해당 프로세스 PCB를 대기 큐에 삽입한다 */
sleep(); /* 대기 상태로 접어든다 */
}
}
signal() {
S++
if ( S <= 0 ) {
remove a process p from Queue /* 대기 큐에 있는 프로세스 p를 제거한다 */
wakeup(p) /* 프로세스 p를 대기 상태에서 준비 상태로 만든다 */
}
}
7) Busy Waiting을 해결했을 때의 예시
Step1. 프로세스 P1 wait 호출, S를 1 감소시키면 S는 1이므로 임계 구역 진입
Step2. 프로세스 P2 wait 호출, S를 1 감소시키면 S는 0이므로 임계 구역 진입
Step3. 프로세스 P3 wait 호출, S를 1 감소시키면 S는 -1이므로 본인의 PCB를 대기 큐에 넣고 대기 상태로 전환
Step4. 프로세스 P1 임계 구역 작업 종료, signal() 호출. S를 1 증가하면 0이므로 대기 상태였던 P3를 대기 큐에서 꺼내 준비 큐로 옮겨줌
Step5. 깨어난 프로세스 P3 임계 구역 진입
Step6. 프로세스 P2 임계 구역 작업 종료, signal() 호출. S가 1 증가하면 1
Step7. 프로세스 P3 임계 구역 작업 종료, signal() 호출. S가 1 증가하면 2
8) 실행 순서 제어를 위한 동기화
- 세마포어의 변수 S를 0으로 두고
- 먼저 실행할 프로세스 뒤에 signal 함수
- 다음에 실행할 프로세스 앞에 wait 함수를 붙이면 된다.
9) 종류
- 바이너리 세마포어
- 0과 1의 두 가지 값만 가질 수 있는 세마포어
- 뮤택스는 잠금을 기반으로 상호 배제가 일어나는 '잠금 매커니즘'
- 세마포어는 신호를 기반으로 상호 배제가 일어나는 '신호 매커니즘'
- 카운팅 세마포어
- 여러 개의 값을 가질 수 있는 세마포어이며, 여러 자원에 대한 접근을 제어하는 데 사용
(3) 모니터
매번 임계구역 앞뒤로 wait(), signal()을 호출해야 하나?
실수가 발생한다면?
-> 그래서 모니터가 등장
-> 모니터는 둘 이상의 스레드나 프로세스가 공유 자원에 안전하게 접근할 수 있도록 공유 자원을 숨기고 해당 접근에 대해 인터페이스만 제공
-> 사용자(개발자)가 다루기에 편리한 도구
1) 상호 배제를 위한 동기화
- 인터페이스를 위한 큐
- 공유자원에 접근하고자 하는 프로세스를 (인터페이스를 위한) 큐에 삽입
- 큐에 삽입된 순서대로 (한 번에 하나의 프로세스만) 공유 자원 이용
2) 실행 순서 제어를 위한 동기화
- 조건 변수(condition variable) 이용
- 프로세스나 스레드의 실행 순서를 제어하기 위해 사용하는 특별한 변수
- 조건변수.wait() : 대기 상태로 변경, 조건 변수에 대한 큐에 삽입
- 조건변수.signal() : wait() 으로 대기 상태에 접어든 조건변수를 실행 상태로 변경
모니터 안에는 하나의 프로세스만이 존재할 수 있다.
- wait()을 호출했던 프로세스는 signal()을 호출한 프로세스가 모니터를 떠난 뒤에 수행을 재개
- signal()을 호출한 프로세스의 실행을 일시 중단하고 자신이 실행된 뒤 다시 signal()을 호출한 프로세스의 수행을 재개
즉,
- 특정 프로세스가 아직 실행될 조건이 되지 않았을 때는 wait를 통해 실행을 중단한다
- 특정 프로세스가 실행될 조건이 충족되었을 때는 signal을 통해 실행을 재개한다
3) 세마포어와 모니터의 차이점
모니터에서 상호 배제는 자동인 반면에, 세마포어에서는 상호 배제를 명시적으로 구현해야 하는 차이점이 있다.
- 모니터가 세마포어보다 구현이 쉽다.
'CS > 운영체제' 카테고리의 다른 글
[CS/운영체제] JSCODE 회고 (0) | 2024.02.14 |
---|---|
[CS/운영체제] 교착 상태(Deadlock) (0) | 2024.02.01 |
[CS/운영체제] 병행성 vs 병렬성 (0) | 2024.01.29 |
[CS/운영체제] 스케줄러의 종류 (1) | 2024.01.25 |
[CS/운영체제] CPU 스케줄링 (0) | 2024.01.23 |