목차
1. 프로세스 간 통신(InterProcessCoummunication)이란?
1) 프로세스 간 통신의 세 가지 이슈
1.
한 프로세스가 다른 프로세스에게 정보 어떻게 넘겨줄래
•
프로세스들이 완전히 다른 주소공간에서 동작한다면 공유메모리를 어떻게 공유할것인가
1) 세마포어 같은 공유자료구조를 커널에 저장하고 시스템호출로만 접근
2) 프로세스의 주소 공간 일부분을 다른 프로세스와 공유
3) 위 두 가지 다 안되는 경우 공유 파일을 사용
2.
두 개 이상의 프로세스가 임계구역에 참여하고 있을 때, 상대방 방해하지 않도록 어떻게 할래
•
상호배제 적용
3.
프로세스 간에 종속성 존재할 때 순서 어떻게 매길래
ex) 서연이가 데이터 생성하고 기우는 생성된 데이터 출력한다면 기우는 서연이 기다려야함
2. 경쟁조건(Race Conditions)
1) 경쟁조건(Race Conditions)
•
정의) 둘 이상의 프로세스가 동시에 공유데이터를 읽거나 기록할 때 최종 결과가 누가 언제 수행되느냐에 따라 달라지는 상황
•
예시) 프린트 스풀러
◦
스풀러 디렉터리: 프로세스가 프린트하고 싶은 파일 이름 기입한 곳
◦
out: 프린트할 다음 파일 가리키는 공유 변수
◦
in: 다음 빈 슬롯 가리키는 공유 변수. in, out은 보통 한 파일에 존재
◦
프린터 데몬 프로세스: 주기적으로 스풀러 디렉터리 살펴서 프린트할 파일 있나 검사하고, 있으면 파일 프린트하고 이름을 디렉터리에서 지우는 프로세스. out을 하나 증가시킴.
경쟁조건 발생 과정)
3. 임계구역(Critical Regions)
1) 상호배제(Mutual Exclusion)
•
정의) 한 프로세스가 공유데이터를 읽거나 쓰고 있으면 다른 프로세스들은 똑같은 일을 수행하지 못하도록 하는 것(배제)
◦
공유데이터: 공유 변수나 파일
◦
둘 이상의 프로세스가 동시에 공유 데이터에 읽기나 쓰기 수행 못하도록 금지하는 것
2) 임계구역(Critical Region)
•
정의) 공유메모리에 접근하는 프로그램의 코드 부분
◦
공유메모리 그 자체가 아님.
3) 경쟁조건(Race Conditions) 방지하는 조건
1.
상호배제 제공 → 어느 두 개의 프로세스도 동시에 임계구역에 있지 못하게 함
임계구역 사용한 상호배제 그림
2.
CPU의 개수나 속도에 대한 가정 x
3.
임계구역 밖에서 수행 중인 프로세스는 다른 프로세스를 block 시키면 안됨
•
임계구역에 들어가려는 다른 프로세스를 막으면 안됨
4.
임계구역 들어가려고 무한히 기다리는 프로세스 없어야 함
4. 바쁜 대기를 이용한 상호배제(Mutual Exclusion with Busy Waiting)
•
바쁜 대기 이용해 상호배제 제공(구현)하는 방법들 4가지
1) 인터럽트 끄기(Disabling Interrupts) → 비추(권한 남용)
•
정의) 임계구역에 진입한 직후 모든 인터럽트를 비활성화하고, 떠나기 직전 다시 활성화함
•
장점) 한 프로세스가 임계구역에 있는 동안 CPU가 clock interrupt에 반응하지 않아 process switch가 일어나지 않게 함으로써 상호배제를 제공함
•
단점) 사용자 프로세스에게 인터럽트 껐다 켤 수 있는 권한 주는 거라 현명 x
◦
프로그래머가 인터럽트 활성화 명령 까먹으면 해당 시스템은 인터럽트에 영원히 반응하지 못해 process switch가 안 일어나서 더 이상 동작하지 않음.
•
process switch(=context switch):한 프로세스 수행하다 다른 프로세스 수행하는 것
◦
프로세스에 할당된 시간 끝나서 clock interrupt 걸리면 running 상태 프로세스를 ready로 보내고 다른 프로세스를 running하는 process switch일어남
•
interrupt disable 명령어
◦
interrupt CPU가 interrupt pin으로 받은 interrupt signal에 대해 반응하는 것인데,
◦
CPU가 interrupt disable 명령어 수행하면 해당 CPU는 인터럽트에 반응하지 않게 됨.
•
인터럽트 비활성화 사용하는 다른 예
◦
커널이 변수나 리스트 변경하는 단 몇 개의 명령만을 수행하는 동안 인터럽트 비활성화시킴
2) 락 변수 사용(Lock Variables) → 실패(상호배제 x)
•
Lock 변수: 임계구역에 프로세스가 있고 없고를 나타내는 공유변수. 초기값은 0.
◦
0 → 임계구역에 어떤 프로세스도 없음
◦
1 → 임계구역에 어떤 프로세스가 있음
•
정의) 임계구역 들어가기 전에 락 변수를 살펴봄.
◦
0 → 1로 변경하고 임계 영역으로 들어감
◦
1 → 0이 될 때까지 대기함
•
단점) 스풀러 디렉터리와 동일한 문제점 가짐: 상호배제 제공 못해 경젱조건 못피함.
◦
서연이가 락 검사 끝내고 락을 1로 변경하는 사이에 기우가 락을 1로 변경하면 기우랑 서연이랑 동시에 임계구역에 들어가면서(상호배제 깨짐) 경쟁조건 발생함.
•
특징) 소프트웨어 솔루션임
3) 엄격한 교대(Turn) → 비추(조건3 x)
•
정의) 두 프로세스가 엄격하게 교대로 임계구역에 진입하게 함
◦
turn: 임계구역에 진입할 순번 나타내는 공유변수. 초기값은 0
▪
0 → 서연 차례
▪
1 → 기우 차례
◦
임계영역 들어가기 전에 turn 변수를 살펴봄
▪
서연
•
0 → 1로 변경하고 임계 영역에 들어감.
•
1 → 0이 될 때까지 기다림
▪
기우
•
1 → 0으로 변경하고 임계 영역에 들어감.
•
0 → 1이 될 때까지 기다림
서연 코드 → turn이 0이면 임계구역에 들어감.
기우 코드 → turn이 1이면 임계구역에 들어감.
•
장점) 상호배제 제공해(조건1) 경쟁조건 피할 순 있음
◦
서연이가 임계구역에 들어가있는동안 기우는 turn이 0이라서 임계구역에 들어갈 수 없음. → 상호배제 제공
◦
서연이가 임계구역에서 나오고 turn을 1로 설정하는 순간 기우는 while문 빠져나오면서 임계구역에 들어감
•
단점)
◦
상호배제 제공(조건1)해도 조건3을 위반해서 완벽한 알고리즘 아님.
▪
임계영역에 있지 않은 프로세스가 다른 프로세스를 차단함
▪
기우 임계구역 실행 후 turn=0으로 변경 → 서연 임계구역 실행 후 turn=1로 변경, 기우는 비임계구역 실행 중 → 기우가 임계구역 실행할 차례인데 기우는 아직 비임계구역 실행 중이고 서연이가 임계구역 실행 원함. 허나 turn=1이라서 기우가 임계구역 실행하고 turn=0으로 바꿔줄 때까지 서연이는 블락됨.
▪
비임계구역의 기우가 임계구역 들어가고 싶은 서연이를 막는 상황.
▪
ex) 스풀러 디렉터리
•
서연이가 파일 프린트하고, 기우가 파일 프린트해야 서연이는 다음 자기 파일을 프린트 가능한 답답한 상황 발생. 번갈아가면서 할 때만 제대로 작동함.
◦
기우가 서연이보다 훨씬 느릴 때 교대로 진행하는 것은 좋지 않음
•
바쁜 대기(Busy Waiting)
◦
정의) 변수가 특정값이 될 때까지 계속해서 검사하는 것
◦
단점) CPU 시간을 단순한 루프 도는데에 낭비해서 피하는 게 좋음
▪
루프 도는 대기 시간 짧을 게 예상되는 경우에만 사용
◦
스핀 락(Spin Lock) : 바쁜 대기를 사용하는 락 → turn
4) Peterson의 해법 → 성공(Turn 변형)
•
코드
#define FALSE 0
#define TRUE 1
#define N 2 // 프로세스 개수
int turn; // 누구 차례인가. 0이나 1
int interested[N]; // 모든 값은 0(FALSE)으로 초기화. 누가 관심있는가
void enter_region(int process) { // 프로세스 0 혹은 1
int other; // 상대방 프로세스의 번호
other = 1-process; // 내가 1이면 다른앤 0, 내가 0이면 다른앤 1이겠지
interested[process] = TRUE; // 나 관심있어
turn = process; // 현재 차례는 나야!
while (turn == process && interested[other] == TRUE)
// 못들어가는 경우) 현재 자기 차례고 상대가 관심있으면 루프를 돔 (기다림)
// -> 내가 다른 애가 차례 쓰겠다 한거 덮어써버린 경우를 방지해줌!
// 차례가 내 차례라도 다른애가 관심있으면 난 임계구역 못가
// 들어가는 경우1) 현재 자기 차례가 아니면 루프 안돔
// -> 자기 차례 아닌데 여기 왓단 건 (심지어 상대가 관심 있단 건)
// 들어가려하는데 다른 애가 내 차례 덮어써버린거거덩
// 그래서 요 경우엔 임계구역 들어가게 해줌.
// 들어가는 경우2) 상대가 관심 없으면 루프 안돔
// -> 임계구역에 아무도 없는거니 당연히 임계구역 드가야지
}
void leave_region(int process) { // 프로세스 0 혹은 1
interested[process] = FALSE; // 나 이제 관심 없어
}
C
복사
•
임계구역 들어가기 전에 서연이랑 기우는 자기번호를 인자로 해서 enter_region을 호출함
•
임계구역에 들어가서 공유변수 사용을 마친 서연이는 leave_region을 호출해서 서연이가 임계구역 수행 종료하고 관심 없다고 밝혀서 임계구역 진입 원하는 기우가 진입할 수 있음을 알려줌 (상호배제 제공)
ex) 서연, 기우가 동시에 call
•
장점) 엄격한 교대 요구하지 않으면서 상호 배제 제공함
5) TSL 명령 → 성공(Lock 변형)
•
정의) Lock 변수 값을 register에 복사해두고 lock 변수값을 무조건 1로 세팅한 후, register에 복사해둔 이전 Lock값을 살펴봄
◦
0 → 1로 변경하고 임계 영역으로 들어감
◦
1 → 0이 될 때까지 대기함
◦
Lock: 임계구역에 프로세스가 있고 없고를 나타냄. 초기값 0(프로세스 없음)
•
어셈블리 코드
enter region:
TSL REGISTER, LOCK // lock 값 읽어서 레지스터에 저장하고, lock값을 1로 설정함
CMP REGISTER, #0 // 아까 저장한 lock값이 0이니?
JNE enter_region // (not equal) lock값 1이라면, lock은 세팅되어있는 애란 소리니 loop 돔
// 현재 임계구역에 있는 프로세스가 임계구역 작업 마치면 0이 되겠지.
RET // enter region 호출한 애로 return됨.
// 즉, 임계구역 들어감.
leave_region:
MOVE LOCK, #0 // lock에 0을 저장함.
RET // 호출자로 return됨.
// 락 반환은 프로세스가 0 저장하기만 하면 됨.
C
복사
◦
서연이가 임계구역 들어가고 싶어서 lock을 1로 설정함
◦
서연이가 봤을 때 설정 전 락은 0이었기에 서연이는 임계구역에 들어감
◦
기우가 임계구역 들어가고 싶어서 락을 1로 설정함
◦
기우가 봤을 때 설정 전 락은 1이었기에 기우는 임계구역에 못들어가고 락이 0이 될 때까지 락을 1로 계속해서 설정하며 루프 돔.
◦
서연이가 임계구역 나와서 락을 0으로 바꿈
◦
기우가 락을 1로 설정하지만 이번엔 설정 전 락이 0이었기에 임계구역 들어감
•
TSL 명령
◦
test and set lock
◦
lock 값 읽어서 레지스터에 저장하고 lock에 0이 아닌 값을 저장함
→ 하나의 연산. 분할 불가능. 연산 중간에 process switch 일어날 수없음
◦
TSL 명령 수행 끝날 때까지 메모리 버스 잠겨서 다른 어떤 CPU도 메모리 접근 불가함
◦
vs. 인터럽트 비활성화 → 인터럽트 끄는 것은 다른 CPU 못막음
•
특징)
◦
하드웨어 도움 받음
◦
분할 불가능함(indivisible)
•
단점)
◦
프로세스가 enter_region, leave_region을 제때 호출안하면 상호배제 실패함
5. Sleep and Wakeup
1) 바쁜 대기의 단점
•
임계구역 진입이 허용되지 않을 때 루프를 돌며 CPU 시간 낭비
•
우선 순위 역전 문제 [참고로만 알기]
2) Sleep과 Wakeup
•
임계구역 진입이 허용되지 않을 때 바쁜 대기 안하고, block함
•
Sleep
◦
호출자를 block 상태로 만드는 시스템 호출
◦
다른 프로세스가 Wakeup 시스템 호출로 깨워줄 때까지 block 상태에 머물게 됨
•
Wakeup
◦
깨울 프로세스를 인자로 가져 깨워서 ready 상태로 보내주는 시스템 호출
3) Sleep과 Wakeup 사용 예시: 생산자-소비자 문제(Producer-consumer problem) 해결 → 실패 (경쟁조건 발생)
•
생산자-소비자 문제
◦
정의) 생산자는 공유버퍼에 아이템 계속 생산하고, 소비자는 아이템 계속 소비하는 개념
▪
두 프로세스가 고정된 크기의 버퍼를 공유함
▪
생산자(서연이)가 정보를 버퍼에 넣고, 소비자(기우)는 정보를 버퍼에서 꺼내옴
•
Sleep, Wakeup 사용한 생산자-소비자 문제 해결
◦
서연이가 새로운 아이템을 버퍼에 넣고 싶은데 버퍼가 이미 꽉 찬 경우 기우가 버퍼에서 아이템 꺼내갈 때까지 sleep 상태가 됨
◦
기우가 아이템 버퍼에서 꺼내오고 싶은데 버퍼에 아무것도 없는 경우 서연이가 버퍼에 아이템 넣어줄 때까지 sleep 상태가 됨
◦
따라서, 서연이는 아이템 넣었을 때 기우를 wakeup 해줘야하고, 기우는 아이템 꺼내 제거했을 때 서연이를 wakeup 해줘야함.
◦
코드
#define N 100 // 버퍼 크기
int count=0; // 버퍼에 있는 아이템 개수
void producer(void) // 생성자
{
int item;
while(TRUE) { // 무한 반복
item = produce_item(); // 다음 아이템 생성
if(count==N) sleep(); // 버퍼 꽉 차있으면 sleep
insert_item(item); // 버퍼에 아이템을 넣음
count+=1; // 카운트를 1 증가시킴
if(count==1) wakeup(consumer); // 버퍼에 아이템 없던 상테어서 한 개 된 상태라면
// 소비자 깨움
}
}
void consumer(void) // 소비자
{
int item;
while(TURE) { // 무한 반복
if(count==0) sleep(); // 버퍼 비어있으면 sleep
item = remove_item(); // 버퍼에서 아이템 꺼내옴(pop)
count-=1; // 카운트 1 감소시킴
if(count==N-1) wakeup(producer); // 버퍼에 아이템이 꽉 찬 상태에서 한 개 빠진 상태라면
// 생성자 깨움
consume_item(item); // 아이템을 출력함
}
}
C
복사
◦
단점) 스풀러 디렉터리에서 봤던 경쟁조건 일어남
▪
정의) 아직 sleep 상태가 아닌 프로세스에게 wakeup 전송하는 경우 wakeup이 소실되어 둘다 영원히 잠들게 되는 결과 초래됨
▪
원인) sleep() 콜하기 직전에 process switch 일어나면(교묘하게 스케줄링 되면) sleep() 안된 상태로 wakeup() 받는 상황 발생함
경쟁조건 발생 과정
6. 세마포어(Semaphores)
1) 세마포어
•
정의) 미래를 위해 미리 호출한 wakeup 회수를 저장해주는 변수 타입
◦
다익스트라가 제안한 새로운 변수 타입
◦
정수 변수
▪
0 → wakeup이 저장 안됨
▪
양수값 → 하나 이상의 wakeup이 대기 중
•
장점) 경쟁조건 방지, 동기화 문제 해결
◦
세마포어 연산의 원자성이 보장되어 연산 시작하면 연산 완료되거나 프로세스가 잠들 때까지 다른 프로세스가 세마포어에 접근 불가
•
연산
◦
down(P)
if(P > 0) { // 세마포어 값이 0보다 큰지 검사
P--; // 0보다 크면 세마포어 감소시켜 세마포어에 저장된 wakeup하나를 소비하고 수행 계속함
} else if(P = 0) {
sleep(); // 0이면 프로세스는 down연산 완료 안 한 채 바로 잠듦
}
C
복사
◦
up(V)
V++; // 세마포어의 값을 증가시킴
if(sleeping process exist in V) { // 하나 이상의 프로세스가 down 완료 못한채 세마포어에서 잠들어있으면
wakeup(); // 그 중 한 프로세스가 시스템에 의해 선택되어 깨워져 down 완료함.
}
C
복사
◦
mutual exclusion 제공하는 법: down 연산 → critical region → up 연산
◦
up, down연산은 원자성 보장되어 실행 중 중단되지 않음.
▪
일반적으로 인터럽트 비활성화하는 시스템호출로 구현됨
▪
따라서 down에서 세마포어 값 검사한 후 감소시키는 중간에 process switch가 일어나지 않음.
2) 세마포어를 이용한 생산자-소비자 문제 해결 → 성공
•
세마포어는 wakeup이 소실되는 문제를 해결함
•
상호배제용 세마포어: mutex
◦
서연이랑 기우가 동시에 버퍼에 접근하지 못하게 함
◦
이진 세마포어임
▪
이진 세마포어: 1로 초기화되고 둘 이상의 프로세스에서 사용될 때 오직 하나의 프로세스만 critical region에 들어갈 수 있게 함
◦
1이면 임계지역 들어갈 수 있음. 임계지역 아무도 사용 x.
◦
0이면 임계지역 못들어감. 임계지역 사용 중.
•
동기화용 세마포어: full, empty
◦
버퍼가 가득 차거나 비었을 때 서연이나 기우가 중지되는걸 보장함
•
down(&sema)
if(sema>0) { sema—; } // 저장된 wakeup 하나 소비
if(sema=0) { sleep(); } // down은 완료x
C
복사
◦
서연이가 down(empty) 호출해서 empty(빈 슬롯 개수, 초기:N)가 0이면(버퍼가 꽉 차있다면) 잠들어. empty가 0보다 크면 empty 1 감소시켜(아이템을 하나 넣을 예정)
◦
혹은 기우가 down(full) 호출해서 full(찬 슬롯 개수, 초기:0)이 0이면(버퍼가 텅 비어있다면) 잠들어. full이 0보다 크면 full 1 감소시켜(아이템을 하나 뺄 예정)
◦
임계지역 들어가려는 누군가가 down(mutex) 호출해서 mutex가 0이면 잠들어. mutex가 1이면 mutex 1 감소시켜서 0으로 만들어(임계지역 들어가서 버퍼 건들 예정)
•
up(&sema)
if(sleep process exists) { wakeup(); down 수행 완료; }
else { sema++; } // 잠든 프로세스 없으면 세마포어 값 증가
C
복사
◦
기우가 up(empty) 호출해서 서연이가 잠들어있으면 서연이를 깨워줘. 서연이가 잠들어있지 않다면 empty하나 늘려서 1로 만들어.
◦
혹은 서연이가 up(full) 호출해서 기우가 잠들어있으면 기우를 깨워줘. 기우가 잠들어있지 않다면 full 하나 늘려서 1로 만들어.
◦
임계지역에서 나오는 누군가가 up(mutex) 호출해서 임계지역 못들어온채 잠들어있는 애 있다면 걜 깨워줘. 잠든 애 없다면 mutex 1 증가시켜서 1로 만듬(사용가능상태로 만듬).
•
코드
#define N 100 // 버퍼 크기
typedef int semaphore; // semaphore라는 타입 정의. 특별한 int임
semaphore mutex=1; // 생산자와 소비자가 동시에 버퍼 접근 못하게 함
semaphore empty=N; // 빈 슬롯의 개수 -> 첨에 텅 비어서 N -> 서연이가 궁금해해
semaphore full=0; // 아이템으로 채워진 슬롯의 개수 -> 첨에 텅 비어서 0 -> 기우가 궁금해해
void producer(void) { // 생산자 서연이
int item;
while(TRUE) {
item = produce_item(); // 버퍼에 넣을 아이템 생성
down(&empty); // 빈 슬롯의 개수(empty) 확인함.
//1이상이면 자리 있는거니 empty 감소시킴. (하나 넣을 예정)
//0이면 자리 없는거니 sleep 됨
//언젠가 기우가 뽑아가고서 깨워주면 down 수행 완료됨
down(&mutex); // 임계지역에 들어감.
//mutex가 1이면 0으로 바꿈 (임계지역 드감)
//mutex가 0이면 누가 사용중인거니 sleep됨 -> 기우 실행
//언젠가 기우가 임계지역에서 나와서 깨워주면 down 수행 완료되어 임계지역 드감
insert_item(item); // 임계지역 - 새 아이템을 버퍼에 넣음 (하나 넣어)
up(&mutex); // 임계 지역에서 나옴
//기우가 자고 있다면 깨워서 기우 down(mutex) 수행 완료시켜줌.
// -> mutex는 여전히 0이지만 기우는 깨어나서 임계구역 드갈 수 있음
// -> mutex 1로 증가시키는건 그럼 기우가 임계지역 나오면서 나중에 하겠지.
//기우 안자고 있다면 mutex를 1증가 시켜서 mutex가 1이 됨
up(&full); // 아이템으로 채워진 슬롯의 개수 증가시킴
//기우 안자고 있다면 empty 감소시켰으니 full도 1 증가시킴 -> 비어있는건 줄고 찬건 늠
//기우가 자고 있다면 깨워서 기우 down(full) 수행 완료시켜줌
// -> full이 여전히 0이지만 기우가 아이템 뽑아가고 나면 여전히 0인게 옳음
}
}
void consumer(void){ // 소비자 기우
int item;
while(TRUE) {
down(&full); // 아이템으로 채워진 슬롯의 개수를 감소시킴
//1이상이면 뽑을 애 있는거니 full 감소시킴. (하나 뽑을 예정)
//0이면 뽑을 애 없는거니 sleep 됨 -> 서연 실행
//언젠가 서연이가 넣어주고서 깨워주면 down 수행 완료됨
down(&mutex); // 임계 지역에 들어감
//1이면 0으로 바꿈 (임계지역 드감)
//0이면 서연이가 임계지역인거라 기우는 sleep 됨 -> 서연 실행
//언젠가 서연이가 임계지역에서 나오면서 깨워주면 down 수행 완료되어 임계지역 드감
item=remove_item(); // 임계지역 - 버퍼에서 아이템 꺼내옴 (하나 뽑아)
up(&mutex); // 임계지역에서 나옴
//서연이가 자고 있다면 꺠워서 서연 down(mutex) 수행 완료시켜줌
//서연이 안자고 있다면 mutex를 1증가 시켜서 mutex 1이 됨
up(&empty); // 빈 슬롯의 개수를 증가시킴
//서연이 안자고 있다면 full 감소시켰으니 empty도 1 증가시킴 -> 찬 건 줄고 비어있는건 늠
//서연이가 자고 있다며면 깨워서 서연이 down(empty) 수행 완료시켜줌
// -> empty는 여전히 0이지만 서연이가 아이템 넣으면 여전히 0인게 옳음
consume_item(item); // 아이템을 출력함
}
}
C
복사
7. 뮤텍스(Mutexes)
1) 뮤텍스 [개념적인 것만 알기]
•
정의) 세마포어의 개수 세는 능력이 빠진 단순화된 버전
◦
언락: 임게구역 사용 가능 → 0
◦
락: 임계구역 사용 안됨 → 1
•
특징)
◦
상호배제용으로 씀
▪
mutex_lock, critical region, mutex_unlock 순으로 사용
◦
뮤텍스는 세마포어가 아님
◦
스레드 패키지에 유용
◦
프로시저 레벨이 아닌 스레드 레벨에서 부르는 코드
2) TSL로 mutex 구현한 코드
mutex_lock: // mutex 잠겨있음? 나 걔 써서 잠그고 싶어
TSL REGISTER,MUTEX // mutex값을 레지스터에 저장하고, mutex에 1(lock)을 저장
CMP REGISTER,#0 // register(이전 mutex 값)를 0이랑 비교
JZE ok // (ZERO) if(mutex=0) return -> mutex가 unlock(0)상태
CALL thread_yield // else -> mutex가 lock(1)상태 -> 다른 스레드를 스케줄함
// -> 본인은 ready로 감. 락 걸려있으면 cpu내놓는거라 busy waiting은 아님.
// -> 나중에 다시 수행되면 락을 다시 검사함
JMP mutex_lock // loop
ok: RET // 임계구역 드가게 리턴함
mutex_unlock: // mutex 풀어줘. 나 다썼어
MOVE MUTEX,#0 // mutex=0(unlock)
RET // return
C
복사
•
스레드가 임계구역 접근시 mutex_lock (뮤텍스가 잠겨있닝? 나 걔 써서 잠그고 시퍼) 호출함.
◦
안잠겨있어! 너 써! mutex가 언락이면 임계구역을 사용할 수 있단 뜻으로 return되고, 스레드는 임계구역에 진입할 수 있음
◦
응~잠겨있어~ mutex가 락이면 스레드는 thread_yield해서 ready로 감. 현재 임계구역에 있는 애가 수행 다 마치고 mutex_unlock (뮤텍스 풀어줘~ 나 다썼어) 호출하고 나서, 언젠가 running으로 다시 올라가면 mutex_lock 다시 수행하고 그땐 mutex 안잠겨있어서 return되고 임계구역 진입할 수 있음
3) mutex_lock vs. enter_region
•
enter_region은 임계구역 진입 못하면 락을 반복적으로 검사함.
◦
바쁜대기(락 반환될 때까지)
•
mutex_lock은 락 획득 못해서 바쁜대기를 수행하는 스레드가 다른 프로세스가 수행될 기회조차 안줘서 영원히 락 획득 못하는 무한루프에 빠지는 걸 막기 위해, 락 획득 못할 시 thread_yield를 호출해 다른 스레드에게 cpu를 양보함
◦
바쁜 대기 존재 안함
◦
스레드 다시 수행하게 되면 락 다시 검사함
◦
thread_yield는 사용자 공간의 스레드 스케줄러를 호출해 매우 빠름
◦
커널 호출하지 않음. 사용자 레벨 스레드들은 mutex_lock 이용하면 전적으로 사용자공간에서 동기화 수행 가능함.
8. 모니터(Monitors)
1) 세마포어의 문제점
•
사용이 어려움.
예시) 프로그래머가 실수로 기우의 down(full)이랑 down(mutex) 순서 바꾼 상황
2) 모니터
•
정의) 모듈 또는 패키지에 모아진 프로시듀어, 변수, 자료구조의 모음
•
특징)
◦
높은 수준의 동기화 프리미티브
◦
상호배제 제공
단 하나의 프로세스만 한 순간에 모니터에서 활동 가능
▪
상호배제 구현은 이진 세마포어나 뮤텍스 사용해 컴파일러가 함. (잘못될 가능성↓)
•
단점
◦
컴파일러에서 지원해야 함
◦
분산 환경에서 적용할 수 없음
•
코드
monitor example
integer i;
condition c;
procedure producer();
...
end;
procedure consumer();
...
end;
end monitor;
C
복사
3) 조건 변수(Condition Variables)와 관련 연산 wait, signal
•
프로세스가 진행 불가할 때 대기할 수 있는 방법 (버퍼가 가득 차면 서연이는 어떻게 블록시켜?)
◦
조건변수와 조건변수에 대한 연산 wait, signal 도입해서 해결
•
wait
◦
서연이가 호출한 모니터 procedure가 버퍼가 가득 차 있어서 더 이상 진행할 수 없음을 알게 된 경우,
◦
조건 변수(full)에 대해 wait를 수행해 호출한 프로세스 서연이를 대기하게 만듦.(block)
◦
다른 프로세스(기우)가 모니터에 들어갈 수 있게 함.
•
signal
◦
기우가 조건 변수(full)에 대해 signal을 수행해 대기 중인 서연이를 깨움.
◦
signal 이후 서연이랑 기우가 동시에 모니터에서 활동하는걸 막을 규칙
▪
signal 수행한 프로세스인 기우가 즉시 모니터에서 나가고
▪
full에 대해 대기 중인 프로세스 중 스케줄러가 택한 프로세스(ex. 서연이) 하나가 깨어나 수행됨
▪
기우가 즉시 나가야하기에 signal은 모니터 procedure의 맨 마지막에서 사용되어야함
•
조건변수
◦
세마포어처럼 카운터가 아니라서 signal을 누적시킬 수 없음
◦
아무도 대기하고 있지 않은 조건변수에 대해 수행된 signla은 사라짐
◦
따라서 signal 전엔 무조건 wait가 수행되었어야 함
4) 모니터를 사용한 생산자-소비자 문제 해결
•
코드
monitor Producerconsumer // 모니터 정의한 것
condition full, empty; // 조건변수 [중요함]
integer count; // 버퍼에 차있는 슬롯의 개수
procedure insert(item: integer);
begin
if count = N then wait(full); // 버퍼 가득차있으면 full 변수에 대해 서연이는 블락됨
insert_item(item); // 버퍼에 아이템 넣음 (임계구역)
count:=count+1; // count 1 증가
if count = 1 then signal(empty) // count가 0에서 1이 된거면
// empty 조건변수에 의해 sleep하고 있는 기우 깨움(ready로 보냄)
// 기우 깨우면 기우도 모니터 속이고 서연이도 모니터 속임. (모니터룰 위반)
// 해결책) signal을 procedure의 가장 마지막에 call함. 서연이는 모니터 빠져나옴.
end;
function remove: integer;
begin
if count=0 then wait(empty); // 버퍼 비어있으면 empty 변수에 대해서 기우는 블락됨
// 서연이는 모니터에 들어가게 허용됨
remove = remove_item; // 버퍼에서 아이템 빼옴 (임계구역)
count:=count-1; // count 1 감소
if count = N-1 then signal(full) //
end;
count:=0 // count는 0으로 초기화
end monitor;
procedure producer
begin
while true do
begin
item=produce_item; // 아이템 하나 만듬
ProducerConsumer.insert(item) // 모니터 procedure 콜하고 아이템 넘겨줘서 아이템 넣음
end
end;
procedure comsumer;
begin
while true do
begin
item = ProducerConsumer.remove; // 모니터 procedure 콜해서 아이템 뽑아옴
consume_item(item) // 아이템 출력함
end
end;
C
복사
•
wait, signal vs. sleep, wakeup
◦
sleep, wakeup
▪
버퍼 찬 거 확인하고, sleep 하려는 사이에 process switch일어나서 다른 프로세스가 이 프로세스를 wakeup 하려하면 wakeup이 소실되고 문제 발생함
◦
wait, signal
▪
모니터 프로시듀어에 대해 자동으로 상호배제 이뤄짐
▪
모니터의 생산자 프로시듀어가 버퍼 찬 거 확인하고, wait 하려는 사이에 소비자로 process switch될 일 없이 wait 완료할 수 있음.
▪
소비자는 생산자가 wait 완료하고 더 이상 실행 중이지 않다고 표시할 때까지 모니터 진입 불가함.
9. 메시지 패싱(Message Passing)
1) 메시지 패싱
•
정의) 프로세스들이 네트워크로 연결된 각기 다른 컴퓨터에서 수행되어 메모리를 공유하고 있지 않아 공유변수 사용이 불가능한 상황에서 사용하는 ipc(프로세스 간 통신) 방식
•
특징) 시스템 호출 send와 receivce 사용
◦
send(destination, &message)
▪
지정된 목적지(프로세스)로 메시지 보냄
◦
receive(source, &message)
▪
지정된 소스(프로세스)로부터 메시지 받음
▪
메시지가 available하지 않으면 receive 시스템콜 호출한 프로세스는 메시지가 도착할 때까지 block되거나(아래 코드가 채택) receive 시스템콜이 오류 코드와 함께 즉시 return됨
2) N개 메시지에 대한 생산자-소비자 문제
•
코드 → 공유버퍼를 구현한 셈
#define N 100 // 버퍼 크기
void producer(void) {
int item;
message m; // 메시지 버퍼
while(TRUE) {
item = produce_item(); // 버퍼에 넣을 아이템 생성
receive(consumer, &m); // 기우로부터 빈 메시지가 오길 기다림
build_message(&m, item); // 빈 메시지에 아이템을 넣음
send(consumer, &m); // 기우한테 아이템 넣은 메시지 보냄
}
}
void consumer(void) {
int item, i;
message m;
for(i=0;i<N;i++) send(producer, &m); // 서연이한테 N개의 빈 메시지보냄
while(TRUE) {
receive(producer, &m); // 서연이로부터 아이템 담긴 메시지 받음
item=extract_item(&m); // 메시지로부터 아이템 추출함
send(producer, &m); // 서연이한테 빈 메시지를 보냄
consume_item(item); // 아이템 출력함
C
복사
◦
서연이가 아직 아이템 넣은 메시지 안보냈다면 기우는 while루프에서 receive system call했을 때 block이 됨
▪
동기화를 잘 구현한 것임
▪
서연이는 버퍼 꽉 찼으면 더 진행 못하고 기우는 버퍼 텅 비어있으면 더 이상 진행 못한다는 생산자-소비자 문제의 컨셉이 잘 구현됨.
10. 장벽(Barriers)
1) 장벽
•
정의) 다른 모든 프로세스가 장벽에 도착할 때까지 이미 도착한 프로세스들은 대기. 모든 프로세스가 장벽에 도착하면 다같이 장벽을 통과함.
◦
a,b,d가 barrier primitive를 call함. 아직 c가 call하지 않아서 a,b,d는 block
◦
c가 barrier primitive를 call하면 a,b,c,d가 barrier primitive로부터 return함
•
특징) 여러 프로세스 동기화할 때 사용