💡 데이터의 접근
데이터는 저장되는 곳(Memory)과 실행되는 곳(CPU)이 별도로 존재
1. 데이터는 스토리지에 저장
2. 실행되는 곳으로 이동
3. 연산 실행
4. 연산 결과를 다시 스토리지에 저장
✅ Execution Box : CPU, 컴퓨터 내부, 프로세스
✅ Storage Box : Memory, 디스크, 프로세스의 주소공간
즉!
➡️ 데이터의 불일치 문제를 발생시킬 수 있음
무슨소리냐구~?~?~??
하나씩 살펴보자
kernel 사이의 context switch 상황임.
즉, 둘다 커널 코드 == 같은 address space를 공유하기 때문에 일어난 문제
커널 모드의 수행이 끝나기 전에는 인터럽트를 받지 않도록 하는 방법(disable/enable)으로 해결!
커널 모드에서 수행중일 때는 CPU를 preempt 하지 않는다.
커널 모드에서 사용자 모드!로 돌아갈 때 preempt
lock/unlock
❓ Critical Section
코드 상에서 Race condition이 발생할 수 있는 특정 부분
== 공유 데이터를 접근하는 코드 부분
➡️ 하나의 프로세스가 critical section에 있을 때,
다른 모든 프로세스는 critical section에 들어갈 수 없어야 함.
모든 프로세스의 수행 속도는 0보다 크다.
프로세스들 간의 상대적인 수행 속도는 가정하지 않는다.
고 가정함
Mutual Exclusion
(상호배제)Progress
(진행)Bounded Waiting
(한정대기)do {
entry section
critical section //shared variable 접근 부분
exit section
remainder section
} while(1);
➡️ 프로세스들은 수행의 동기화 == 상태공유 를 위해
몇몇 변수를 "공유"할 수 있다
이렇게 공유할 수 있는 변수를 synchronzation variable이라고 함
현재 Critical Section에 들어갈 프로세스가 어떤 프로세스인지를 하나의 변수로 나타내어 일치하는 프로세스만 진입하도록 하자!
//P0의 turn의 값은 0
P1의 turn 값은 1 이라고 하자.
do {
while(turn != 0); // P0 : My turn??
critical section
turn = 1; // P0 : Now It's your turn!
remainder section
} while(1);
이 알고리즘은
프로세스가 반드시 한번씩 교대로 들어가야 함
현재 프로세스(P0)가 변수 turn의 값을 다음 프로세스 값(P1)으로 바꿔줘야만! 다음 프로세스 들어갈 수 있음.
Ex.
P0의 remainder section 수행 시간이 매우 길어서 여전히 remainder section에서 수행 중이고,
P1이 수행을 한번 마치고 다시 Critical Section으로 진입하려고 한다면
turn == 0이기 때문에 진입할 수 없다.
(P0이 turn값을 1로 바꿔줘야하거든~)
➡️ Critical Section에 아무런 프로세스가 존재하지 않는 상황!
즉!!
⭕️ Mutual exclusion 은 만족하지만
❌ Progress는 만족하지 못함
특정 프로세스가 Critical Section에 진입할 준비가 되었다는 것을 나타내는 변수를 두어,
다른 프로세스가 Critical Section에 진입하려고 한다면 현재 프로세스는 기다리는 방법
do {
flag[0] = true; // Pretend I'm in
while(flag[1]); // Is he also in? then wait. .
critical section
flag[0] = false; //I'm out
remainder section
} while(1);
두 프로세스가 flag = true까지만 수행하고 나면 두 프로세스 모두 무한히 Critical Section에 진입하지 못하고 기다리는 상황이 발생
즉, 얘도
⭕️ Mutual exclusion 은 만족하지만
❌ Progress는 만족하지 못함
알고리즘 1과 2를 합쳐놓은 개념
turn 변수와 flag 변수를 동시 사용
do {
flag[0] = true; // P0 들어가고자 함
turn = 1; // P1 turn
while (flag[1] && turn == 1); // P1이 들어가고자 하고, P1 차례라면
// P0는 wait
critical section
flag[0] = false;
remainder section
} while(1);
P0
은 flag[0] = true로 바꾸면서 Critical Section에 진입하고자 함 P1
이 들어가고 싶고 (flag[1] == true),P1
이 Critical Section에 들어가 있으면 (turn == 1)P0
이 들어간다. ⭕️ Mutual Exclusion,
⭕️ Progress,
⭕️ Bounded waiting
모두 만족함
하지만 문제는 있음!
‼️ Busy Waiting
계속 CPU와 메모리를 사용하면서 기다림
그러면!
이 복잡한 코드 구현들이 하드웨어/소프트웨어 측면에서 어떤 지원들이 제공될까?
하드웨어적으로 현재 상태를 확인하고 변경하는 test & modify를
atomic(하나의 operation인것처럼)하게 수행할 수 있도록 지원하는 경우,
앞의 문제는 간단히 해결!
(많은 시스템들은 이러한 하드웨어적인 지원을 제공하고 있음)
이전까지의 알고리즘들은 데이터를 읽고 쓰는 것을 하나의 명령어로 처리할 수 없기 때문에 복잡해진 것
➡️ 메모리에서 데이터를 읽으면서 쓰는 것까지 하나의 명령어로 동시에 수행이 가능하다면 코드가 훨씬 간결해짐
이는 Test_and_Set을 이용하여 아래와 같이 구현됨
// Synchronization Variable
boolean lock = false;
do {
while (Test_and_Set(lock)); // busy waiting
critical section // lock -> TRUE. false 전까지 계속 수행
lock = false; // SET
remainder section
}
하지만
‼️ 이러한 하드웨어적인 명령어는 bounded waiting 조건을 만족하지 못하는 단점 있음
추상 자료형
왜 사용함?
lock/unlock 방식을 간단하게 프로그래머에게 제공할수도 있음
공유자원을 획득하고 반납하는 것을 세마포어로 처리 가능
✅ 비효율적
Semaphores S
while (s<=0) do no-op; // wait
S--;
// if S is positive, decrement & enter.
// otherwise, wait until positive (busy-wait)
S++;
Block
프로세스가 "suspend" 되고
이 프로세스의 PCB를 세마포어에 대한 wait queue 에 넣음
Wake up
block된 프로세스를 wakeup
이 프로세스의 PCB를 ready queue오 옮김
Semaphores S
S.value--;
if (S.value < 0) {
add this process to S.L; // wait queue에 linked list 연결
block(); // suspend
}
S.value++;
if (S.value <= 0) {
remove a process P from S.L;
wakeup(P);
}
일반적으로 Block-Wakeup 방식이 더 좋긴 한데,,,
그렇다고 무조건은 아님!
왜?
Block-Wakeup 방식은 오버헤드가 발생함.
즉. Critical section의 길이가 매우 짧은 경우!
Block-Wakeup의 오버헤드가 busy-wait 오버헤드보다 커질 수도 있음
Counting semaphore
Binary semaphore
세마포어의 문제 상황
둘 이상의 프로세스가 서로 상대방에 의해 충족될 수 있는 event를 무한히 기다리는 현상 (상호교차적)
➡️ 반드시 starvation 유발함
P0는 S를 잡고 한없이 Q를 기다리고
P1은 Q를 잡고 한없이 S를 기다림
(사실 이 경우, 자원의 순서만 맞춰주면 됨
P1의 자원 순서를 Q→S에서 S→Q로)
자원을 얻지 못해서 무한히 기다림
여기서는
특정 프로세스들만 자원을 공유하면서 다른 프로세스들은 자원을 쓰지 못하는 상황을 의미!
세가지가 있음
자세히 살펴보장!
생산자 스레드들과 소비자 스레드들이 있고, 사이에 공유 버퍼가 있다고 가정하자!
만약
➡️ 따라서, 동시에 버퍼에 접근할 수 없도록 락을 걸어줘야 한다.
또,
✅ 여기서 세마포어를 사용하자.
binary semaphore
integer semaphore
한 프로세스가 데이터를
수정(Write)
하는 작업을 수행할 때 다른 프로세스가 접근하면 안 되고,
읽는(Read)
작업은 여러 프로세스가 동시에 수행 가능하도록 하는 문제
이를 위해
Reader
들의 접근을 허용하고, Writer
는 대기 중인 Reader
가 하나도 없는 경우Writer
가 접근 중이면 Reader
들은 접근할 수 없도록!→ 만약 n개의 reader가 기다리고 있다면, 제일 처음 reader만 wrt 세마포어에 넣고 나머지 n-1개의 reader는 mutex 세마포어 큐에 넣어둠으로써 효율성을 높여줌
위 해결 방법은 Starvation의 가능성이 있음.
계속해서 reader가 들어오거나 writer가 들어오는 경우 한쪽이 계속 수행되지 않을 수 있기 때문
➡️ 이는 큐에 우선순위를 부여한다거나, 일정 시간이 지나면 자동으로 write or read가 되도록 하여 해결 가능~~~
참 재밌는 이름이지요~
// initially all values are 1
// 동시에 둘이 젓가락을 잡을 수 X. 무조건 한 명만.
semaphore chopsticks[5];
do {
P(chopstick[i]);
P(chopstick[(i+1) % 5]; // 요로케 양 옆 젓가락 확보
...
eat(); // 밥 먹기
...
V(chopstick[i]);
V(chopstick[(i+1) % 5]; // 두 젓가락 놓음
...
think();
...
} while(1);
➡️ 이웃한 두 철학자가 동시에 먹는 경우가 없다는 것이 보장
// 상태. 공유 변수
enum { thinking, hungry, eating } state[5];
// 다섯 명의 철학자가 젓가락을 두 개 다 잡을 수 있어서 잡을 권한을 줄것인가 여부.
semaphore self[5] = 0; // 0 => 없음 / 1 => 있음
// lock
semaphore mutex = 1;
Philosopher i:
do {
pickup(i);
eat();
putdown(i);
think();
} while(1);
void putdown(int i) {
P(mutex);
state[i] = thinking;
test((i+4) % 5);
test((i+1) % 5);
V(mutex);
}
void pickup(int i) {
P(mutex); // lock
state[i] = hungry;
test(i);
V(mutex);
P(self[i]);
}
// i가 젓가락 두 개를 다 잡을 수 있는 상태인지 test
void test(int i) {
// (i+4) % 5 : 왼쪽 철학자 eating X
// (i+1) % 5 : 오른쪽 철학자 eating X
// hungry : 내가 지금 배고픈 상태
if (state[(i+4) % 5] != eating && state[i] == hungry && state[(i+1) % 5] != eating) {
state[i] = eating;
V(self[i]); // 젓가락 잡는 권한 부여
}
}
KOCW 공개강의 (2014-1. 이화여자대학교 - 반효경)
[운영체제(OS)] 6. 프로세스 동기화(Process Synchronization)
[운영체제] 6. Process Synchronization