전체 보기
4️⃣

[운영체제론] Chapter 2.3 IPC(프로세스간 통신)

작성일자
2022/10/10
태그
SUB PAGE
프로젝트
운영체제론
책 종류
1 more property
목차

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함
특징) 여러 프로세스 동기화할 때 사용