Race condition: 여러 프로세스가 동시에 같은 데이터에 접근할 때
=> 동기화 필요
The Critical-Section Problem
- n개의 프로세스들이 공유된 데이터에 경쟁적으로 접근할 때, 공유된 데이터에 접근하는 코드를 critical section이라 함
- 어떤 프로세스가 critical section을 수행 중이면 다른 어떤 코드도 critical section을 수행하지 못하게 해야 함
- 수행하지 못하게 막는 방법 -> solution
Solution of the critical-section problem
- Mutual Exclusion: 어떤 프로세스가 critical section을 수행 중이면 다른 어떤 코드도 critical section을 수행하지 못하게 해야 함
- 과도하게 막으면 throughput이 떨어짐
- Progress: 현재 critical section을 수행하는 프로세스가 없으면 어떤 프로세스가 들어갈 수 있어야 함
- Bounded Waiting: 어떤 프로세스가 들어가고자 할 때 무한정 기다리면 안됨, 기다리는 시간의 bound가 지정돼야 함
- entry section: critical section에 진입해도 되는지 확인하는 부분
- exit section: critical section 다 썼으니까 다른 애들한테 알려주는 부분
Algorithm 1
int turn = 0; // shared variable
do {
whlie (turn != 0);
critical section
turn = 1;
remainder section
} while (1);
- 프로세스 P0, P1은 각각 turn의 값에 따라 critical section에 진입 가능, 동시 진입 불가능하므로 mutual exclusion 보장해줌
- swap-turn: 2개의 프로세스가 번갈아가며 동작하는 코드이므로 progress를 보장하지 못함
Algorithm 2
boolean flag[2]; // 들어가고자 하는 의지를 보여줌
flag[i] = flag[j] = false;
do{
flag[i] = true; // 나 들어갈 거야
while (flag[j]); // 상대방 flag 체크, true면 기다려
critical section //
flag[i] = false; // 나 나옴
remainder section
} while(1);
- mutual exclusion 보장하지만 여전히 progress 안됨
- 두 놈이 다 flag가 true면 둘 다 while에서 막혀버림
Algorithm 3 (Peterson's Algorithm)
do {
flag[i] = true; // 나 들어갈래
turn = j; // 일단 니가 먼저 들어가
while (flag[j] and turn == j); // 상대가 들어갈 마음이 있고, 순서도 상대 순서면 기다려
critical section
flag[i] = false; // 상대가 빠져나오면서 flag가 false가 되면서 while 빠져나와서 내가 들어갈 수 있음
remainder section
} while(1);
- turn이 교통 정리를 해줌
- while문을 계속 돌음 => busy waiting(CPU를 계속 쓰면서 대기함)
- entry section이 너무 길어
더 나은 해결책
do {
acquire lock
critical section
release lock
remainder section
} while(TRUE);
- 기계적으로 코딩이 가능하게 primitive를 제공해주는 게 더 좋음 like systemcall
- 이 함수만 호출하면 돼 라는 믿음만 있다면 기계적으로 그 함수만 호출하면 됨
- locking system
- 열쇠를 가진 자만이 critical section에 진입 가능
- 빠져나오면서 열쇠 반납
- 열쇠는 한 순간 한 놈에게만 줘야 한다는 것이 보장되어야 함
- locking 시스템이 잘 코딩돼있으면 개발자 입장에서 정말 편함
- CPU가 하나일 경우 CPU 스케줄링에 의해 프로세스 간 왔다갔다 하는 상황 생김
- 스케줄링이 발생하지 않게 한다면 다른 프로세스로 안 넘어가
- 스케줄링만 안 일어나면 동시에 critical section에 접근하려는 일은 발생하지 않음
- critical section에 진입하면 CPU를 놓지 않고, 빠져나와서야 CPU를 놓으면 됨 => Interrupt
- CPU 스케줄링이 발생하는 경우 == 내가 CPU를 잡고 있는 상황에서 CPU를 놔야 되는 경우: 나한테 주어진 시간이 끝이 났을 경우,
I/O를 발생시켰을 경우(critical section에서는 I/O를 발생시키지 않음) - I/O 디바이스가 timer 인터럽트를 발생시켰을 때
- RR에서 time quantum을 주고, 그놈한테 time quantum이 다 됐는지 확인하는 주체 - timer
- critical section에 들어가기 전에 타이머 인터럽트를 disable 시킴 -> time quantum을 소진했더라도 인터럽트를 발생시키지 않아서 계속 CPU 사용 가능 -> critical section 빠져 나오면서 타이머 인터럽트 enable
- 하지만 멀티코어, 멀티프로세스 환경에선 general한 솔루션이 될 수 없음
General한 솔루션
: single cycle의 기능을 끝마친다는 것을, 중간에 끊기지 않는다는 것을 보장 해주는 특별한 하드웨어 회로 Atomic 제공
- Test-and-Set
boolean TestAndSet(boolean &target) {
boolean rv = target;
target = true; // target에는 true 주고
return rv; // 원래 target 값을 return
}
ㄴ 먼저 들어간 놈이 lock을 false로 바꿔주는 순간 대기하던 프로세스가 false를 리턴하면서 while문을 빠져나옴
- Swap
void Swap(boolean &a, boolean &b) {
boolean temp = a;
a = b;
b = temp;
}
boolean lock = false;
do {
key = true;
while(key == true) Swap(lock, key);
critical section
lock = false;
remainder section
}
ㄴ 먼저 들어간 놈은 lock이 true, key가 false가 되면서 while 문을 빠져나옴
ㄴ 두번째 들어온 놈은 lock, key가 모두 true -> 먼저 들어온 놈이 lock을 false로 바꿔주면 key가 false가 되어 while문 나옴
Semaphore S
동기화를 위해 하드웨어를 사용하지 않고 소프트웨어만으로는 primitive를 제공할 수 없을까?
- 정수 변수 S에 접근하는 operation은 단 2개
- P(): wait, 조건 충족 시까지 대기
- V(): signal, 다음 프로세스에게 알려주는 역할
P(S): //S가 양수가 될 때까지 while 문에서 돌다가 0보다 커지는 순간 S를 감소시키고 빠져나옴
while (S<=0) do no-op ;
S--;
V(S): //V 하나 감소시킴
S++;
- 동기화 방법: 모든 프로세스는 critical section 들어가기 전에 P 호출, 나오면서 V 호출
semaphore mutex; //initially 1
do {
P(mutex);
critical section
V(mutex);
remainder section
} while(1);
- P나 V는 atomic 함수(중간에 끊기지 않고 한번 실행되면 끝까지)이기 때문에 소프트웨어로만 가능해짐
- 만약 atomic 하게 동작하지 않는다면? 여러 프로세스가 동시에 critical section 진입
- Semaphore 구현 방법
- single process
- P 함수 시작 전에 interrupt disable
- P 함수 끝나면 interrupt enable
- multiple processes
- 알고리즘 사용
- semaphore 변수 S 값에 대한 접근 자체가 critical section이 됨
- P, V의 S 값을 peterson's algorithm으로 보호 -> 동시 접근 불가
- P, V 만 알고리즘으로 보호하면 함수 사용 시에는 peterson's algorithm을 사용하지 않아도 됨
- peterson's algorithm을 만들면 busy waiting을 해야 하는 문제 생김 -> critical section이 커지면 overhead 커짐
- Block/Wakeup Semaphore
- while에서 busy wait를 하는 대신 P 호출 시 조건에 안 맞으면 sleep 시킴(CPU 사용 X)->조건 충족 시 하나씩 깨워
- semaphore변수를 구조체로 만듦
- single process
typedef struct {
int value;
struct process *L; // Linked list를 가리키도록 함
} semaphore;
P(S):
S.value--; //초기값 1이니까 0으로 바꿈
if (S.value < 0) {
//처음 놈은 0이므로 이 조건에 충족되지 않아 critical section으로 들어감
//다음 놈부터는 linked list에 추가되고 block됨
add this process to S.L;
block;
}
V(S):
S.value++;
if (S.value <= 0) {
//처음 놈은 그냥 빠져나가고
//다음 놈부터는 기다리는 놈 하나 깨워줌
remove a process P from S.L;
wakeup(P);
}
- 성능 상 하드웨어로 하는 게 좋긴 함
OS 내부에서의 동기화 문제가 발생하는 경우
- Interrupt 발생 시, Interrupt handler code 실행 시
- 프로세스 간 스케줄링 시 systemcall
- 시스템 안에 프로세스가 여러개인 경우
한양대학교 강수용 교수님 운영체제 강의 내용 정리
'운영체제' 카테고리의 다른 글
[운영체제] Deadlocks (0) | 2024.03.12 |
---|---|
[운영체제] Process synchronization (2) (0) | 2024.03.06 |
[운영체제] CPU Scheduling (3) | 2024.02.20 |
[운영체제] Process and Threads (2) | 2024.02.09 |
[운영체제] Operating System Overview (0) | 2024.01.23 |