cooperating process는 협동적인 프로세스들. 시스템 내 실행중에 다른 process들에 의해 영향을 주고받는 프로세스를 말한다. logical한 주소공간(code,data)에 의해 직접 공유하거나(thread : 해당 공간 share), 파일이나 메세지를 통하여 데이터를 공유하는 등 협동한다. (반대 개념은 Independent process)
많은 환경에서 cooperating process가 우세하다(많이 존재함). cooperating process들은 서로 영향을 미치고 데이터,흐름에 대한 동기화 문제가 발생할 수 있다. 이 프로세스들 사이에 동기화 하는 것을 process synchronization이라고 하며 현대에는 대부분 thread 기준으로 switching을 하므로 thread synchroniation이라고도 한다. synchronization은 공유되는 자원의 일관성을 유지하는 것.
하나의 코드블록 또는 메소드를 한 순간에 정해진 스레드만이 이용하도록 제어하는 것.
프로세스 동기화는 하나의 자원(context switchng 단위)을 한 순간에 정해진 프로세스만이 이용하도록 제어하는 것.
현대의 운영체제는 thread switching을 위주로(P1의 a thread -> P2의 b thread) 처리가 이루어지므로 (context switching 단위가 thread) 프로세스 동기화는 하나의 자원(코드블록,메소드,cpu자원)을 한 번에 하나 프로세스의 스레드가 자원의 이용하도록 제어하는 것이다.
컴퓨터 자원은 한정되어있고 context switching을 통해 여러 프로세스들이 실행된다. 이 떄 context switching이 빈번하게 발생하게되고 (스케줄링 등) 이 때 데이터 무결성을 해치는 등 작업이 꼬이게 될 수 있으므로, 이를 처리하기 위한 것이 synchronization.
동시에 여러 process(thread)들이 동일 데이터에 접근하여 변경하는 등, 실행 결과가 접근특정 순서에 의존하는 현상, 어떤 순서로 접근하느냐에 따라 결과가 달라질 수 있는 상황. (경쟁상황)
-> 데이터 불일치 문제를 발생시킬 수 있다.
kernel모드 수행 중 interrupt 발생 경우
count라는 변수에 값을 1 증가시켜주는 연산(++)은
(1) Load
(2) Increase
(3) Store
세 가지의 작업으로 이루어져있다.
--연산도 마찬가지.
만약 ++연산 후 --연산해 기존 값을 유지하는 작업에서,
++연산 도중에 intterupt되어 count--연산을 하게된다면,
count++흐름은 --되기 전 값을 load한 상태에서 문맥정보를 저장 한다. 그리고 interrupt된 스레드,프로세스가 --연산을 수행하고 저장한다. --연산 완료 후 다시 이전 ++연산의 문맥정보를 가지고와서 작업을 완료한다. 이 때 ++연산은 이미 --되기 전 값을 load했기 때문에 --연산은 무시된다. : 동기화 문제 발생.
=> 커널모드의 수행이 끝나기 전에는 interrupt를 받지 않도록 하는 방법(disable/enable)로 문제 해결이 가능하다.( 커널모드 작업에 원자성 부여:인터럽트제한 )
프로세스가 시스템콜을 호출해서 커널모드로 수행중인데 context switch가 발생하는 경우
두 프로세스 주소공간에서는 데이터를 공유하지 않지만 system call 수행동안에는 둘 다 커널 주소공간의 데이터에 접근한다. 커널 주소공간에서 작업 도중 CPU 점유 전환이 일어나면 race condition이 발생한다.
이는 kernel모드 수행 중일 시 cpu가 preempt(전환)되지 않도록 하고, kernel모드에서 user모드로 돌아갈 때 preempt되도록 함으로 써 해결 가능.( 커널모드 작업에 원자성 부여 : context switching(스케줄링 등) 제한 )
multiprocessor에서 공유 메모리 내 커널 데이터에 접근하는 경우
어떤 cpu가 마지막으로 count를 저장했는지에 따라 결과값이 달라지는데, single processor의 경우 인터럽트 제한으로 해결이 가능하지만 멀티 프로세서의 경우 해당 방법으로 해결이 안된다. 한 번에 한 CPU만 커널에 들어가는 방법은 비효율적. 커널 내부에 있는 각 공유 데이터에 접근할 때 마다 해당 데이터에 대해서만 lock/unlock하는 방식으로 해결 가능.( 이용중인 데이터에 대한 접근 제한 )
Critical Section은 코드 상에서 Race Condition이 발생할 수 있는 특정 부분을 말한다. : 공유 데이터를 접근하는 코드 부분
Critical Section 해결을 위한 조건
Critical Section problem 해결 방법론
단일 코어 시스템이라면 그저 공유 변수 수정중이면 interrupt를 disable해 해결할 수 있다.
멀티 코어 시스템 + 비선점형 커널이라면 선점을 허용하지 않는 프로세스 스케줄링 특성상 Race condition이 발생할 일 조차 없다.
하지만 현대 운영체제는 빠른 속도를 이유로 멀티 코어 시스템 + 선점형 커널을 사용하고 있고 이를 해결하기 위한 다양한 방법론들이 있다.
SW관점, HW관점, 일반적 관점으로 생각해볼 수 있다.
peterson's solution은 flag, turn변수를 사용한다.
mutual exclusion
프로세스 진입하고싶음을 flag로 나타내지만, turn은 둘 중 하나로만 나타낸다.
flag[i]==flag[j]==true이더라도, turn==i일경우 j는 진입이 불가능하다. 상대 프로세스가 flag가 false이거나, turn을 i로 바꾸어주어야한다. (한 번에 한 프로세스만 들어갈 수 있음)
progress
Pi가 ciritical section에 들어갈 준비가 안되어있다면(진입하고자 하는 상태가 아니라면) flag[i]==false이다. critical section에 들어갈 때는 flag를 true로 만들고, critical section에서 나오면서 flag를 false로 만들기 때문에 flag[i]가 false이면 Pi는 실행중이 아니다. 이 때 Pj가 들어가고 싶다면, while문에서 flag[i]는 False문으로 해당 while문은 False이고, Critical Section에 진입할 수 있다.
bounded waiting
Pi,Pj모두 Critical section에 진입하고싶을 때, turn flag를 상대방으로 만들어준다. 그렇기 때문에 먼저 대기중이던 프로세스가 있다면 해당 프로세스를 우선적으로 실행시켜주고, 해당 프로세스가 끝나면 flag를 false로 만들음으로써 while문에서 flag[상대]==False로 while문은 false로 critical section에 진입이 가능해진다. 그러므로 최대 1번만 기다리고 ciritical section을 수행할 수 있다.
문제점
Critical section 진입을 기다리면서 CPU와 메모리를 사용하는 Busy Wating 문제점 존재. 또한 현대 컴퓨터 아키텍처에서는 종속성이 없는 읽기/쓰기 작업을 컴파일러나 프로세서가 재정렬할 수 있기 때문에 사용 불가할 수 있음.
n개의 프로세스를 처리하며 번호를 부여받고 가장 낮은 수의 번호를 가지고있는 process가 Critical Section에 진입하는 구조.
busy wating과 현대 컴퓨터 구조의 문제로 sw적 방법에는 단점 존재하거나 사용불가하므로 HW에서 제공하는 방법 사용한다.
HW적 방법은 Atomic(인터럽트되지 않는)한 명령어를 사용한다.(locking scheme)
Test and set은 atomic하게 실행되며, 읽고 쓰는 것을 하나의 명령어로 동시에 수행할 수 있게 구현해준다.
hw적인 명령어는 bounded waiting을 만족하지 못하는 단점이 있다.
: waiting array를 사용하여 보완.
Mutex Lock은 Critical Section을 해결하기 위한 소프트웨어 도구 중 가장 간단한 방법이다.
프로세스가 ciritical section에 들어가기 전에 lock을 획득하고, 나올 때 lock을 반환한다.
available이라는 변수를 통해 lock의 가용 여부를 판단한다.
단점으로는 역시 sw적인 방법으로 busy waiting(CPU낭비)
Semaphore : 정수 변수. wait()과 signal() 두 개의 atomic한 연산으로만 변경할 수 있게 제공하는 방법.
ex) A class와 B class가 Critical Section(공통된 변수)를 가지고있을 경우, A 클래스가 들어가면 B클래스가 들어가지 못하게 하는 개념.)
mutext와 다르게 counting이 가능하고, 가용 프로세스 동시 실행이 가능하다.
binary semaphore(이진 세마포)
counting semaphore
busy-wait 방식
block-wake up 방식
Block을 수행하면, 커널은 block을 호출한 프로세스를 suspend 시키고 해당 프로세스의 PCB를 wait queue에 넣어준다.
Wakeup을 수행하면 block 된 프로세스 P를 깨운 다음, 이 프로세스의 PCB를 Ready Queue로 이동시킨다.
critical section의 길이가 긴 경우에는 block-wakeup방식이 유리하다. 그러나 짧다면 context switching이 잦아져 오버헤드가 증가하고 ciritical section이 짧은 경우에는 busy-wait방식이 유리.
세마포어의 문제점은 코딩하기가 힘들고 실수하기 쉬우며, 정확성을 입증하기가 어렵다. 세마포가 어셈블리 수준이었다면 monitor는 하이레벨.
Monitor(모니터)는 동시 수행 중인 프로세스 사이에서 추상 데이터의 안전한 공유를 보장하기 위한 High-level(고급 언어 수준) 동기화 구조
공유 데이터를 접근하기 위해서는 모니터의 내부 Procedure(wait,signal)를 통해서만 접근할 수 있도록 한다.
세마포어와의 가장 큰 차이점은, 모니터는 락(Lock)을 걸 필요가 없다. 세마포어는 프로그래머가 직접 wait와 signal을 사용하여 Race condition을 해결해야 하지만 모니터는 자체적인 기능으로 해결할 수 있게 된다.
monitor의 경우 2가지 queue를 가진다.
Mutual Exclusion Queue, Conditional Synchronization Queue로 이루어져있음.
Mutex + Condition value을 합친 것을 monitor라 한다. (mutex 상위 호환)