공유 자원에 대해 여러 프로세스가 동시에 접근하면서, 결과값에 영향을 줄 수 있는 상태를 Race Condition(경쟁 상태)이라 한다.
공유 자원에 결함이 발생할 수 있음
Race Condition이 발생하는 경우와 해결 방법
- 커널 작업을 수행하는 중에 인터럽트 발생
- 문제점 : 커널모드에서 데이터를 로드하여 작업을 수행하다가 인터럽트가 발생하여 같은 데이터를 조작하는 경우
- 해결법 : 커널모드에서 작업을 수행하는 동안, 인터럽트를 disable 시켜 CPU 제어권을 가져가지 못하도록 한다.
- 프로세스가 'System Call'을 하여 커널 모드로 진입하여 작업을 수행하는 도중 문맥 교환이 발생할 때
- 문제점 : 프로세스1이 커널모드에서 데이터를 조작하는 도중, 시간이 초과되어 CPU 제어권이 프로세스2로 넘어가 같은 데이터를 조작하는 경우
- 해결법 : 프로세스가 커널모드에서 작업을 하는 경우 시간이 초과되어도 CPU 제어권이 다른 프로세스에게 넘어가지 않도록 함
- 멀티 프로세서 환경에서 공유 메모리 내의 커널 데이터에 접근할 때
- 문제점 : 멀티 프로세서 환경에서 2개의 CPU가 동시에 커널 내부의 공유 데이터에 접근하여 조작하는 경우
- 해결법 : 커널 내부에 있는 각 공유 데이터에 접근할 때마다, 그 데이터에 대한 lock/unlock을 하는 방법
그밖에도 멀티 프로세스 / 멀티 스레드 환경에서 여러 프로세스나 스레드가 공통 전역변수에 접근할 경우에도 Race Condition이 발생할 수 있다.
그러한 문제를 해결하기 위해 아래 2가지 방법 중 한 가지를 선택하게 된다.
- 세마포어(Semaphore)
- 뮤텍스(Mutex)
세마포어는 동기화할 자원이 1개 이상일 때 사용하며, 뮤텍스는 동기화할 자원이 1개일 때 사용한다.
세마포어와 뮤텍스를 알아보기 전에 임계 구역을 알고가자.
임계 구역(Critical Section)
여러 프로세스가 데이터를 공유하며 수행될 때, 각 프로세스에서 공유 데이터를 접근하는 부분을 의미한다.
임계 구역 문제를 해결하기 위해서 아래의 3가지 조건을 충족해야 한다.
- Mutual Exclusion (상호 배제)
- 한 프로세스가 자신의 임계 구역이면, 다른 프로세스들은 임계 구역에 진입할 수 없다.
- Progress (진행)
- 아무도 임계 구역에 있지 않다면, 진입하고자 하는 프로세스가 임계 구역에 진입하게 해줘야 한다. 즉, 아무도 임계 구역에 진입하지 못하면 안된다.
- Bounded Waiting (유한 대기)
- 프로세스가 임계 구역에 진입하기 위해 무한정 기다리는 현상이 발생해서는 안된다.
이제 세마포어를 알아보자.
세마포어(Semaphore)
세마포어는 멀티 프로그래밍 환경에서 공유 자원에 대한 접근을 제한하는 방법이다.
세마포어의 연산은 P, V로 나눈다.
- P: 임계 구역에 들어가기 전에 수행
- 프로세스 진입 여부를 자원의 개수(S)를 통해 결정한다.
- V: 임계 구역에서 나올 때 수행
- 자원 반납을 알려서 대기 중인 프로세스를 깨운다.
구현 방법
P(S);
// --- 임계 구역 ---
V(S);
procedure P(S)
while S=0 do wait --> S(사용 가능한 자원 수)가 0이면 대기한다.
S := S-1 --> S에서 사용할 자원의 개수를 1 감소한다.
end P
--- 임계 구역 ---
procedure V(S) --> 현재상태는 S가 0임
S := S+1 --> S를 다시 증가시켜서 자원이 존재하다는 것을 알린다.
end V
P와 V를 사용해서 임계 구역에 대한 상호배제를 구현한다. 이를 통해 한 프로세스가 P 혹은 V를 수행하고 있는 동안 프로세스가 인터럽트 당하지 않게 된다.
[예시]
- A 프로세스에서 임계 구역에 진입하기 전에 S를 0으로 만든다.
- B 프로세스에서는 S가 1이 될때 까지 기다린다.
- A 프로세스가 임계 구역에서 나갈 때 S를 1로 변경한다.
- B 프로세스가 임계 구역에 진입한다.
뮤텍스(Mutex)
뮤텍스는 임계 구역을 가진 스레드들의 실행시간이 서로 겹치지 않고 각각 단독으로 실행되게 하는 기술이다.
뮤텍스(Mutex): 상호 배제(Mutual Exclusion)의 약자
해당 접근을 조율하기 위해 lock과 unlock을 사용한다.
- lock: 현재 임계 구역에 들어갈 권한을 얻어온다.
- 만약 다른 프로세스 / 스레드가 임계 구역을 수행 중이면 종료할 때까지 대기한다.
- unlock: 현재 임계 구역을 모두 사용했음을 알린다.
- 대기 중인 다른 프로세스 / 스레드가 임계 구역에 진입할 수 있다.
뮤텍스는 상태가 0, 1로 이진 세마포어로 부르기도 한다.
1. 데커(Dekker) 알고리즘
프로세스 / 스레드가 두 개일때 상호 배제를 보장하는 최초의 알고리즘이다.
flag와 turn 변수를 통해 임계 구역에 들어갈 프로세스 / 스레드를 결정하는 방식이다.
- flag: 프로세스 중 누가 임계 구역에 진입하려고 시도하는 지를 나타낸다.
- turn: 누가 임계 구역에 들어갈 차례인지 나타낸다.
구현
while(true) {
flag[i] = true; // 프로세스 i가 임계 구역 진입 시도
while(flag[j]) { // 프로세스 j가 현재 임계 구역에 있는지 확인
if(turn == j) { // j가 임계 구역 사용 중이면
flag[i] = false; // 프로세스 i 진입 취소
while(turn == j); // turn이 j에서 변경될 때까지 대기
flag[i] = true; // j turn이 끝나면 다시 진입 시도
}
}
}
// ------- 임계 구역 ---------
turn = i; // 임계 구역 사용 끝나면 turn을 넘김
flag[j] = false; // flag 값을 false로 바꿔 임계 구역 사용 완료를 알림
2. 피터슨(Peterson) 알고리즘
프로세스 / 스레드가 두 개일때 상호 배제를 보장하는 알고리즘이다.
데커 알고리즘과 유사하지만, 상대방 프로세스/스레드에게 진입 기회를 양보하는 것에 차이가 있고 더 간단하게 구현되었다.
while(true) {
flag[i] = true; // 프로세스 i가 임계 구역 진입 시도
turn = j; // 다른 프로세스에게 진입 기회 양보
while(flag[j] && turn == j) { // 다른 프로세스가 진입 시도하면 대기
}
}
// ------- 임계 구역 ---------
flag[i] = false; // flag 값을 false로 바꿔 임계 구역 사용 완료를 알림
제과점(Bakery) 알고리즘
프로세스 / 스레드가 N개일 때 상호 배제를 보장하는 알고리즘이다.
가장 작은 수의 번호표를 가지고 있는 프로세스가 임계 구역에 진입한다.
while(true) {
isReady[i] = true; // 번호표 받을 준비
number[i] = max(number[0~n-1]) + 1; // 현재 실행 중인 프로세스 중에 가장 큰 번호 배정
isReady[i] = false; // 번호표 수령 완료
for(j = 0; j < n; j++) { // 모든 프로세스 번호표 비교
while(isReady[j]); // 비교 프로세스가 번호표 받을 때까지 대기
while(number[j] && number[j] < number[i] && j < i);
// 프로세스 j가 번호표 가지고 있어야 함
// 프로세스 j의 번호표 < 프로세스 i의 번호표
}
}
// ------- 임계 구역 ---------
number[i] = 0; // 임계 구역 사용 종료
참고
'Operation > OS' 카테고리의 다른 글
OS - Process의 생성과 제어를 위한 System Calls! (0) | 2022.10.03 |
---|---|
블로킹(Blocking)과 논 블로킹(Non-Blocking)! (0) | 2022.07.06 |
OS - 프로세스와 스레드 차이 (0) | 2022.07.05 |
OS - 교착 상태(Deadlock) 정리! (0) | 2022.04.19 |