본 글은 다음 강의를 들으며 정리한 내용입니다.
강의 정보 : 운영체제 / 이화여대 반효경
강의 링크
Example :
E-box | S-box | |
---|---|---|
1 | CPU | Memory |
2 | 컴퓨터 내부 | 디스크 |
3 | 프로세스 | 그 프로세스의 주소 공간 |
OS에서 race condition은 언제 발생하는가?
interrupt handler vs kernel
Preempt a process running in kernel?
두 프로세스의 address space 간에는 data sharing이 없음
그러나 system call을 하는 동안에는 kernel address space의 data를 access하게 됨 (share)
이 작업 중간에 CPU를 preempt 해가면 race condition 발생
If you preempt CPU while in kernel mode ...
해결책
커널 모드에서 수행 중일 때는 CPU를 preempt하지 않음
커널 모드에서 사용자 모드로 돌아갈 때 preempt
multiprocessor
어떤 CPU가 마지막으로 count를 store했는가? ➔ race condition
multiprocessor의 경우 interrupt enable/disable로 해결되지 않음
방법 1
방법 2
공유 데이터(shared data)의 동시 접근(concurrent access)은 데이터의 불일치 문제(inconsistency)를 발생시킬 수 있다.
일관성(consistency) 유지를 위해서는 협력 프로세스(cooperating process) 간의 실행 순서(orderly execution)를 정해주는 메커니즘 필요
Race condition
race condition을 막기 위해서는 concurrent process는 동기화(synchronize)되어야 한다.
Example of a Race Condition
사용자 프로세스 P1 수행중 timer interrupt가 발생해서 context switch가 일어나서 P2가 CPU를 잡으면? ➔ 보통은 문제가 안생김
n 개의 프로세스가 공유 데이터를 동시에 사용하기를 원하는 경우
각 프로세스의 code segment에는 공유 데이터를 접근하는 코드인 critical section이 존재
Problem
두 개의 프로세스가 있다고 가정 : P0, P1
프로세스들의 일반적인 구조
do {
entry section
critical section
exit section
remainer section
} while (1);
synchronization variable
Mutal Exclusion (상호 배제)
Progress (진행)
Bounded Waiting (유한 대기)
가정
Synchronization variable :
int turn;
initially turn = 0; // Pi can enter its critical section if (turn == i)
Process P0 :
do {
while (turn != 0); // My turn?
critical section
turn = 1;
remainder section // Now it's your turn
} while (1);
Satisfies mutual exclusion(상호 배제), but not progress(진행)
즉, 과잉양보 :
Synchronization variable :
boolean flag[2];
initially flag[모두] = false; // no one is in CS
Pi ready to enter its critical section if (flag[i] == true)
Process Pi :
do {
flag[i] = true; // Pretend I am in
while (flag[j]); // Is he also in? then wait
critical section
flag[i] = false; // I am out now
remainder section
} while (1);
Satisfies mutual exclusion(상호 배제), but not progress requirement(진행)
둘 다 2행까지 수행 후 끊임 없이 양보하는 상황 발생 가능
Combined synchronization variables of algorithm 1 and 2
Process Pi :
do {
flag[i] = true; // My intention is to enter...
turn = j; // Set to his turn
while (flag[j] && turn ==j); // wait only if...
critical section
flag[i] = false;
remainder section
} while(1);
Meets all three requirements; solves the critical section problem for two processes
Busy Waiting(= spin lock)! (계속 CPU와 memory를 쓰면서 wait)
하드웨어적으로 Test & modify를 atomic하게 수행할 수 있도록 지원하는 경우 앞의 문제는 간단히 해결
Synchronization variable :
boolean lock = false;
Process Pi :
do {
while (Test_and_Set(lock));
critical section
lock = false;
remainder section
}
앞의 방식들을 추상화시킴
integer variable
아래의 두 가지 atomic 연산에 의해서만 접근 가능
P 연산 : 공유 데이터를 획득하는 과정
V 연산 : 사용 후 반납하는 과정
S : 자원의 갯수
Synchronization variable :
semaphore mutex; // initially 1 : 1개가 CS에 들어갈 수 있다
Process Pi :
do {
P(mutex); // If positive, dec-&-enter, otherwise wait
critical section
V(mutex); // Increment semaphore
remainder section
} while (1);
busy-wait는 효율적이지 못함 (= spin lock) (위 방식)
Block & Wakeup 방식의 구현 (= sleep lock) (아래 방식)
Semaphore를 다음과 같이 정의 :
typedef struct
{
int value; // semaphore
struct process *L; // process wait queue
} semaphore;
block과 wakeup을 다음과 같이 가정 :
block
wakeup(P)
Semaphore 연산이 이제 다음과 같이 정의됨 :
P(S) :
S.value--; // prepare to enter
if (S.value < 0) // Oops, negative, I cannot enter
{
add this process to S.L;
block();
}
V(S) :
S.value++;
if (S.value <= 0)
{
remove a process P form S.L;
wakeup(P);
}
Block & Wakeup overhead vs Critical section 길이
Critical section의 길이가 긴 경우 Block & Wakeup이 적당
Critical section의 길이가 매우 짧은 경우 Block & Wakeup 오버헤드가 busy-wait 오버헤드보다 더 커질 수 있음
일반적으로는 Block & Wakeup 방식이 더 좋음
Counting semaphore
Binary semaphore (=mutex)
Shared data
Synchronization variables
한 process가 DB에 write 중일 때 다른 process가 접근하면 안됨
read는 동시에 여럿이 해도 됨
solution
Shared data
Synchronization variables
위 그림 solution의 문제점
Deadlock 가능성이 있다.
모든 철학자가 동시에 배가 고파져 왼쪽 젓가락을 집어버린 경우
해결 방안
4명의 철학자만이 테이블에 동시에 앉을 수 있도록 한다.
젓가락을 두 개 모두 집을 수 있을 때에만 젓가락을 집을 수 있게 한다. (아래 그림)
비대칭 : 짝수(홀수) 철학자는 왼쪽(오른쪽) 젓가락부터 집도록 한다.
Semaphore의 문제점
코딩하기 힘들다.
정확성(correctness)의 입증이 어렵다.
자발적 협력(voluntary cooperation)이 필요하다.
한번의 실수가 모든 시스템에 치명적 영항을 미친다.
Example :
Monitor : 동시 수행중인 프로세스 사이에서 abstract data type의 안전한 공유를 보장하기 위한 high-level synchronization construct (Semaphore와 다르게 lock을 할 필요 X)
모니터 내에서는 한번에 하나의 프로세스만이 활동 가능
프로그래머가 동기화 제약 조건을 명시적으로 코딩할 필요없음
프로세스가 모니터 안에서 기다릴 수 있도록 하기 위해 condition variable
사용
condition x, y;
condition variable
은 wait
와 signal
연산에 의해서만 접근 가능x.wait();
x.signal();
x.wait()
을 invoke한 프로세스는 다른 프로세스가 x.signal()
을 invoke하기 전까지 suspend된다.
x.signal()
은 정확하게 하나의 suspend된 프로세스를 resume한다.
suspend된 프로세스가 없으면 아무 일도 일어나지 않는다.