데이터가 저장된 위치에서 데이터를 읽어와서 연산을 한 후 그 결과를 저장된 위치에 다시 저장
누가 먼저 읽어왔느냐에 따라서 결과가 달라질 수 있음. 그렇게해서 생기는 문제 = Synchronization
여러 주체가 하나의 데이터를 동시에 접근하려고 할 때를 Race Condition(경쟁상태)라고 함
이를 조율해주는 방법이 필요할 것
프로세스는 일반적인 경우라면 자기 주소공간만 접근하기 때문에 Race Condition 문제가 발생하는 경우가 없지만 본인이 직접 실행할 수 없는 부분, 운영체제에게 대신 요청해야하는 경우엔 시스템콜을 해서 커널의 코드가 실행됨. CPU를 뺏겨서 또 시스템콜을 하면 커널의 코드를 건드리기 때문에 Race Condition의 문제가 발생할 수 있음.
커널모드 running 중 interrupt가 발생하여 인터럽트 처리루틴이 수행
=> 양쪽 다 커널 코드이므로 kernel address space 공유
n개의 프로세스가 공유 데이터를 동시에 사용하기를 원하는 경우
각 프로세스의 code segment에는 공유 데이터를 접근하는 코드인 critical section (임계 구역)이 존재
Problem
Mutual Exclusion (상호 배제)
Progress (진행)
Bounded Waiting (유한 대기)
가정
두 개의 프로세스가 있다고 가정 P0, P1
프로세스들의 일반적인 구조
do {
entry section /* 락을 걸어서 여러 프로세스가 critical section에 들어가는 것을 막음 */
critical section
exit section
remainder section
} while (1);
프로세스들은 수행의 동기화(synchronize)를 위해 몇몇 변수를 공유할 수 있다 -> synchronization variable
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; /* Now it's your turn */
remainder section
} while (1);
Satisfies mutual exclusion, but not progress
즉, 과잉양보: 반드시 한번씩 교대로 들어가야만 함 (swap-turn)
그가 turn을 내값으로 바꿔줘야만 내가 들어갈 수 있음
특정 프로세스가 더 빈번히 critical section을 들어가야 한다면?
Synchronization variables
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[i]); /* 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행까지 수행 후 끊임 없이 양보하는 상황 발생 가능 (둘다 True인 상태이고, 일단 들어가야 깃발을 내려놓을 수가 있어짐 아무도 못 들어감)
Combined synchronization variables of algorithms 1 and 2.
Process Pi
do {
flag[i] = true; /* My intention is to enter ...깃발 들어서 의사표시 */
turn = j; /* Set to his turn turn을 상대방에게 바꿔놓음 */
while (flag[i] && 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) - 이미 들어가 있는 상태에서 CPU를 잡으면 할당 시간 동안까지 계속 체크함. 상대방을 체크하지 않고 쓸데 없이 본인의 할당 시간을 while문에서 소비하고 반납하게 됨. (비효율적인 방법)
a가 원래 0이었다면 0이 읽히고 1로 바뀜
Test_and_set(a)는 읽고 바꾸는 것을 atomic하게 수행함
Mutual Exclusion with Test & Set
Synchronization variable:
boolean lock = false;
Process Pi
do {
while (Test_and_Set(lock));
critical section
lock = false;
remainder section
}
앞의 방식들을 추상화시킴
공유자원을 획득하고 반납하는 것을 처리해줌
Semaphore S
integer variable
아래의 두 가지 atomic 연산에 의해서만 접근 가능
P(S): while (S<=0) do no-op; /* -> i.e. wait */
S--;
if positive, decrement-&-enter.
Otherwise, wait until positive (busy-wait)
V(S):
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
}
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)
block된 프로세스 P를 wakeup 시킴
이 프로세스의 PCB를 ready queue로 옮김
Semaphore 연산이 이제 다음과 같이 정의됨
P(S): S.value--; /* prepare to enter */
if (S.value < 0){ /* Oops, negative, I cannot enter 자원의 여분이 없음 */
add this process P to S.L;
block(); /* block되어 있다가 자원이 생기면 깨어날 수 있음 */
}
V(S): S.value++;
if (S.value <= 0){
remove a process P from S.L;
wakeup(P); /* 잠들어 있는 연산이 있으면 깨워줌 */
}
Deadlock
S와 Q가 1로 초기화된 semaphore라 하자.
자원을 획득하는 순서를 똑같이 맞춰주면 데드락 해결 가능
Starvation
임시로 데이터를 저장하는 공간(Buffer)이 유한한 환경에서의 생산자-소비자 문제
Producer 프로세스, Consumer 프로세스가 여러개 존재
생산자 : 공유 버퍼에 데이터를 하나 만들어서 집어 넣는 역할(주황색: 데이터가 들어있는 버퍼, 나머지는 비어있는 버퍼)
소비자 : 데이터를 하나씩 꺼내 쓰는 역할
버퍼가 유한하기 때문에 생기는 문제: 생산자 입장에서는 사용할 자원이 없는 것이 됨. 소비자가 나타나서 내용을 꺼내가야 빈 버퍼가 생길 때가지 생산자는 기다려야 함. 소비자 입장에서는 꺼내갈 것이 없어서, 생산자가 내용을 만들어서 넣어줄 때까지 기다려야 함
둘이 동시에 공유 버퍼에 접근하는 것을 막기 위해 lock을 걸어 배타적 접근을 하도록 함. 가용 자원을 세는 변수가 필요함
Shared data
Synchronization variables
한 프로세스가 db에 write 중일 때 다른 process가 접근하면 안됨
read는 동시에 여럿이 해도 됨
Solution
Shared data
Synchronization variables
다섯명의 철학자가 생각하는 시간과 밥 먹는 시간이 다름. 젓가락 두 개를 다 잡아야 함.
공유 자원이기 때문에 옆에 있는 철학자가 잡고 있으면 못 잡고 기다려야 됨.
앞의 solution의 문제점
해결 방안
p연산과 v연산을 통해 프로세스 동기화를 하지만, 모니터는 동기화할 수 있는 방법을 프로그램에 알려주고 프로그램이 그것을 하는 것.
Semaphore의 문제점
예
동시 수행중인 프로세스 사이에서 abstract data type의 안전한 공유를 보장하기 위한 high-level synchronization construct
모니터 내에서는 한번에 하나의 프로세스만이 활동 가능
프로그래머가 동기화 제약 조건을 명시적으로 코딩할 필요가 없음
프로세스가 모니터 안에서 기다릴 수 있도록 하기 위해 condition variable 사용
condition x, y;
Condition variable 은 wait과 signal연산에 의해서만 접근 가능.
x.wait();
x.wait()을 invoke한 프로세스는 다른 프로세스가 x.signal()을 invoke 하기 전까지 suspend된다
x.signal(); (깨우기)
x.signal()은 정확하게 하나의 suspend된 프로세스를 resume한다.
Suspend된 프로세스가 없으면 아무 일도 일어나지 않는다
모니터가 알아서 제어해주기 때문에 굳이 들어가기 전에 lock을 걸고 나올 때 lock을 풀 이유가 없어짐.
공유 버퍼에 대해서 락을 걸거나 푸는 코드가 필요 없음
full은 내용이 들어있는 버퍼 empty는 빈 버퍼를 기다리는 컨디션
내용이 들어있는 버퍼를 기다리면서(소비자 버퍼) 잠들어있는 버퍼가 있으면 깨어줘라.
세마포어보다 모니터버전이 훨씬 더 자연스러움
세마포어에서는 lock을 거는 코드가 있고, 모니터 버전에서는 없음
빈 버퍼가 없으면 잠들게 해라 라는 코드가 있으나 세마포어에서는 P연산을 해줌
모니터에서는 시그널 연산, 세마포어에서는 V연산
세마포어에서는 V연산을 해주면 내용이 들어있는 버퍼를 기다리는 소비자가 있으면 깨어주는 역할을 해야하고, 값을 가지면 V연산은 값의 변화가 항상 있으나 모니터에서는 잠들어있는 프로세스가 있으면 그냥 깨워라는 것이기때문에, 잠들어있는 프로세스가 없으면 값을 바꾸거나 아무 일도 발생하지 않음.
🔗강의 바로가기 운영체제 - 이화여자대학교 반효경 교수님 강의를 듣고 정리한 내용입니다.