운영체제의 프로세스 관리 서비스 중 가장 중요한 두가지를 꼽자면 스케줄링과 동기화이다.
동시다발적으로 실행되는 프로세스들은 공동의 목적을 올바르게 수행하기 위해서 서로 협력하여 실행되며, 실행 순서와 자원의 일관성을 보장해야 하기에 반드시 동기화(synchronization)되어야 한다.
프로세스 동기화란 프로세스들 사이의 수행 시기를 맞추는 것을 의미한다. 프로세스들 사이의 수행 시기를 맞추는 것은 무엇을 의미할까?
가령 파일에 값을 저장하는 Writer라는 프로세스와 파일에 저장된 값을 읽어 들이는 Reader라는 프로세스가 동시에 실행 중이라고 가정한다면, 이 두 프로세스는 무작정 아무 순서대로 실행되어서는 안 된다. Reader프로세스는 Writer프로세스 실행이 끝나야 비로소 실행 될 수 있기 때문이다. 이렇게 동시에 실행되는 프로세스를 올바른 순서대로 실행하는 것이 첫 번째 프로세스 동기화 이다.
상호 배제(mutual exclusion)는 공유가 불가능 자원의 동시 사용을 피하기 위해 사용하는 알고리즘이다. 예시를 들어보면 10만원이 있는 계좌에 프로세스 A는 2만원을 저축하고, 프로세스 B는 5만원을 저축하는 프로세스이다. 둘의 수행순서는 다음과 같다.
프로세스 A와 B가 동시에 실행되었다고 가정하면 당연히 17만원이 계좌에 남을 것이라고 예상되지만, 동기화가 제대로 이루어지지 않은 경우 아래와 같은 결과가 나올 수 있다.
왜 이런 일이 발생할까? A와 B는 '잔액'이라는 데이터를 동시에 사용하는데, A가 끝나기도 전에 B가 잔액을 읽어버렸기 때문이다. A와 B를 올바르게 실행하기 위해서는 한 프로세스가 잔액에 접근했을 때 다른 프로세스는 기다려야 한다.
생산자와 소비자 문제는 물건을 계속해서 생산하는 프로세스인 생산와 물건을 계속해서 소비하는 프로세스인 소비자로 이루어져 있습니다.
생산자와 소비자는 '총합'이라는 데이터를 공유하고 있습니다. 생산자는 버퍼에 물건을 넣은 후, 물건의 총합에 해당하는 변수를 1 증가시키고, 소비자는 1 감소시킨다.
총합의 변수가 10이라고 가정하고, 위와 같은 상태에서 생산자와 소비자를 각각 100,000번 동시에 실행해 보자. 총합 변수가 10으로 변하지 않을거라 예상되지만 예상치 못한 결과를 받을 수 있다.
이는 생산자 프로세스와 소비자 프로세스가 제대로 동기화 되지 않았기 때문에 발생한 문제이다. 계좌 잔액 문제와 생산자 소비자 문제는 동시에 접근해서는 안 된느 자원에 동시에 접근 했기에 발생한 문제라고 볼 수 있다.
계좌잔액문제와 생산자 소비자 문제 예시에서 동시에 실행되는 프로세스들은 '잔액', '총합'이라는 공동의 자원을 두고 작업을 했다. 이러한 자원을 공유 자원(shared resource)라고 한다. 공유 자원은 전역변수가 될 수 도있고, 파일, 입출력장치, 보조기억장치가 될 수 있다.
그리고 이 공유 자원 중에는 두 개 이상의 프로세스를 동시에 실행하면 문제가 발생하는 자원이 있다. 앞서 '잔액', '총합'변수가 이러한 경우이다. 이렇게 동시에 실행하면 문제가 발생하는 자원에 접근하는 코드 영역을 임계 구역(critical section)이라고 한다.
두 개 이상의 프로세스가 임계구역에 진입하고자 하면 둘 중 하나는 대기해야한다. 임계구역에 먼저 진입한 프로세스의 작업이 마무리되면 그제서야 비로소 기다렸던 프로세스가 임계 구역에 진입한다.
하지만 잘못된 싱행으로 인해 여러 프로세스가 동시 다발적으로 임계구역의 코드를 실행하여 자원의 일관성이 깨지는 등 문제가 발생하는 경우를 레이스 컨디션(race condition)이라고 한다.
레이스 컨디션이 발생하는 근본적인 이유는 우리가 사용하는 코드인 고급 언어를 컴퓨터 내부에서 여러 줄의 저급 언어로 변환되어 실행하는 과정에서 문맥 교환이 일어나 문제가 발생하는 것이다.
이때, 상호 배제를 위한 동기화는 이와 같은 일이 발생하지 않도록 두 개 이상의 프로세스가 임계 구역에 동시에 접근하지 못하도록 관리하는 것을 의미한다.
상호 배제를 위한 동기화를 위해서는 아래 세가지 원칙이 반드시 지켜져야한다.
보기
상호배제, 실행 순서 제어, 임계구역
(1)
를 위한 동기화 : 프로세스를 올바른 순서대로 실행하기(2)
를 위한 동기화 : 동시에 접근해서는 안 되는 자원에 하나의 프로세스만 접근하게 하기답 : 1 - 실행 순서 제어, 2 - 상호배제
답 : 1 - 동시에 실행하면 레이스 컨디션이 발생한다. 문제가 생길 수 있고, 이를 방지하기 위해 상호 배제를 위한 동기화를 한다.
동기화는 어떻게 이루어져 있을까? 어떻게 해야 임계 구역에 오직 하나의 프로세스만 진입하게 하고, 올바른 실행 순서를 보장 할 수 있을까? 이를위해 뮤텍스 락, 세마포, 모니터를 학습하자
상호배제를 위한 동기화 도구이다. 예시로 한명의 인원만 들어갈 수 있는 탈의실은 '임계 구역', 손님들은 '프로세스' 이다. 탈의실을 이용하기 위해서는 일단 탈의실을 열어 보고 자물쇠가 걸려 있다면 탈의실에 사람이 있다고 판단한다.
이 자물쇠 기능을 코드로 구현한것이 뮤텍스 락(Mutex lock)이다. 뮤텍스 락은 동시에 접근해서는 안 되는 자원에 동시에 접근하지 않도록 만드는 도구이다. 뮤텍스 락의 매우 단순한 형태는 하나의 전역 변수와 두 개의 함수로 구현할 수 있다.
aquire 함수는 프로세스가 임계구역에 진입하기 전에 호출하는 함수이다. 만일 임계 구역이 잠겨져 있다면 임계 구역이 열릴때까지 반복적으로 확인하고 열려 있다면 임계구역을 잠그는 함수이다.
release 함수는 임계 구역에서의 작업이 끝나고 호출하는 함수이다. 현재 잠긴 구역을 열어주는 함수라고 보면된다
acquire() {
while (lock == true) // 임계 구역이 잠겨있는 동안
; // 잠겨있는지를 반복적으로 확인
lock = true; // 잠겨있지 않다면 잠금
}
release() {
lock = false; // 임계 구역 작업이 끝났으니 작업해체
}
하지만 acquire 함수를 다시보면 임계구역이 잠겨있을 경우 프로세스는 반복적으로 lock을 확인하는 것을 알 수 있다. 이는 쉴 새없이 반복하며 확인해 보는 것으로 이런 대기방식을 바쁜 대기(busy waiting)이라고 한다.
세마포(semaphore)는 뮤텍스락과 비슷하지만, 조금 더 일반화된 방식의 동기화 도구이다. 세마포는 공유 자원이 여러 개 있는 상황에서도 적용이 가능한 동기화 도구이다.
세마포는 뮤텍스 락과 두 개의 함수로 단순하게 구현할 수 있다.
세마포도 사용할 때 임계 구역 진입 전후로 wait()와 signal()을 호출한다.
wait 함수는 아래와 같이 만든다.
wait() {
while(S <= 0) {
;
}
S--
signal 함수는 다음과 같이 만든다
signal() {
S++;
}
여기서 한 가지 문제가 있다. 이는 뮤텍스 락에도 해당하는 문제로, 사용할 수 있는 공유 자원이 없는 경우 프로세스는 무작정 무한히 반복하며 S를 확인해야한다. 이렇게 바쁜 대기를 반복할 경우 CPU주기를 낭비한다는 점에서 큰 손해이다.
그래서 세마포는 더 좋은 방법을 사용한다. wait함수는 만일 사용할 수 있는 자원이 없을 경우 해당 프로세스 상태를 대기 상태로 만들고, 그 프로세스의 PCB를 세마포를 위한 대기 큐에 집어 넣는다. 그리고 다른 프로세스가 임계 구역에서의 작업이 끝나고 signal함수를 호출하면 signal 함수는 대기중인 프로세스를 대기 큐에서 제거하고, 프로세스 상태를 준비상태로 변경한뒤 준비 큐로 옮겨준다.
이를 간단한 코드로 보자.
wait() {
S--;
if( S < 0) {
add this process to Queue;
sleep();
}
}
signal() {
S++;
if(S<=0) {
remove a process p from Queue;
wakeup(p);
}
}
지금까지는 세마포를 이용한 상호 배제를 위한 동기화 기법이었다면, 이번에는 세마포를 이용해 프로세스의 순서를 제어하는 방법에 대해 알아보자.
세마포를 이용한다면 동시에 실행되는 프로세스의 실행 순서도 원하는 대로 제어할 수 있다.
방법은 간단하다. 세마포의 변수 S를 0으로 두고 먼저 실행할 프로세스 뒤에 signal 함수, 다음에 실행할 프로세스 앞에 wait함수를 붙이면된다.
p1 p2
wait()
// 임계 구역 // 임계구역
signal()
세마포는 매우 훌륭하지만, 사용하기가 불편한 면이 있다. 임계 구역 앞뒤로 일일이 wait와 signal함수를 명시하는것은 번거로운 일이기 때문이다.
이에 최근에 등장한 동기화 도구가 모니터(monitor)이다. 세마포에 비하면 사용하기에 훨씬 편리한 도구이다. 모니터는 공유 자원과 공유 자원에 접근하기 위한 인터페이스(통로)를 묶어 관리한다. 그리고 프로세스는 바늗시 인터페이스를 통해서만 공유 자원에 접근하도록 한다.
이를 위해 모니터를 통해 공유 자원에 접근하고자 하는 프로세스를 큐에 삽입하고, 큐에 삽입된 순서대로 하나씩 공유 자원을 이용하도록 한다.
이 밖에도 모니터는 세마포와 마찬가지로 실행 순서 제어를 위한 동기화도 제공한다. 특정 조건을 바탕으로 프로세스를 실행하고 일시 중단하기 위해 모니터는 조건 변수(condition variable)를 사용한다. 조건 변수는 프로세스나 스페드의 실행 순서를 제어하기 위해 사용하는 특별한 변수이다.
조건 변수로는 wait와 signal 연산을 수행할 수 있다. 우선 wait는 호출한 프로세스의 상태를 대기상태로 전환하고 일시적으로 조건 변수에 대한 대기 큐에 삽입하는 연산이다.
여기서 모니터에 진입하기 위해 삽입되는 큐와 wait가 호출되어 실행이 중단된 프로세스들이 삽입되는 큐는 다르다 !
- 전자는 모니터에 한 번에 하나의 프로세스만 진입하도록 하기 위함
- 후자는 모니터에 이미 진입한 프로세스의 실행 조건이 만족될 때까지 잠시 실행이 중단되어 기다리기 위해 만들어진 큐
모니터에 진입한 어떤 프로세스가 x.wait()를 통해 조건 변수 x에 대한 wait를 호출했다고 가정하면, 조건 변수 x에 대한 큐에 삽입되므로 모니터는 다시 비게된다.
그럼 다른 프로세스가 들어올 수 있고, wait연산으로 일시 중지된 프로세스는 다른 프로세스의 signal연산을 통해 실행이 재개 될 수 있다. 어떤 프로세스가 x.signal()을 통해 조건 변수 x에 대해 대기 상태에 있던 프로세스가 깨어나 모니터안으로 다시 들어 올 수 있게된다.
하지만 모니터 안에는 하나의 프로세스만이 이을 수 있으므로 wait를 호출했던 프로세스는 signal을 호출한 프로세스가 모니터를 떠난 뒤에 실행되거나, signal을 호출한 프로세스의 실행을 일시 중단하고 자신이 실행된 뒤 signal을 호출한 프로세스의 수행을 제개한다.
중요한 점은 모니터는 조건 변수를 이용하여 아래와 같은 프로세스 실행 순서 제어를 위한 동기화를 제공한다는 것이다.
1. 특정 프로세스가 아직 실행될 조건이 되지 않았을 때에는 wait를 통해 실행을 중단한다.
2. 특정 프로세스가 실행될 조건이 충족되었을 때에는 signal을 통해 실행을 재개한다.
답 : 4 - 세마포는 바쁜대기를 방지 하기 위해 대기 큐를 사용해서 CPU주기의 낭비를 막을 수 있다.
답 : B, y.signal을 호출하였으므로 조건변수 y의 wait를 호출한 스레드를 깨우게 된다.
보기 : 실행순서제어, 상호배제, 입출력장치
세마포를 이용하면 동시에 실행되는 프로세스 혹은 스레드간에 (1
)를 위한 동기화와 (2
)를 위한 동기화를 할 수 있다.
답 : 상호배제, 실행순서제어
러시아워의 교통상태처럼 두 개 이상의 프로세스가 각자 가지고 있는 자원을 무작겅 기다린다면 그 어떤 프로세스도 더 이상 진행할 수없는 교착상태가된다. 교착상태는 정확히 무엇이고, 언제 발생할까?
이 문제는 교착 상태를 설명하기 위한 아주 고전적인 문제이다. 이 문제는 교착상태가 어떤 상황에서 왜 발생하는지, 나아가 교착상태를 어떻게 해결할 수 있는지 엿볼 수 있는 가상의 문제 시나리오이다.
원탁에 다섯명의 철학자가 앉아있고, 식사에 필요한 포크가 철학자들 사이 사이 있다. 그리고 철학자들 앞에 있는 식사는 두개의 포크로 먹을 수 있다. 그리고 이와 같은 순서대로 식사를 한다.
언뜻 보면 위 순서에는 아무런 문제가 없어보이지만, 모든 철학자가 동시에 포크를 집어 실사를 하면 어떤 철학자도 식사를 할 수 없고, 영원이 생각만 해야할 수있다. 이렇게 일어나지 않을 사건을 기다리며 진행이 멈춰버리는 현상을 교착 상태(deadlock)이라고 한다.
식사하는 철학자 문제에서 철학자는 프로세스 혹은 스레드, 포크는 자원, 생각하는 행위는 자원을 기다리는 것에 빗대어 볼 수 있다. 그리고 포크는 한 번에 하나의 프로세스 혹은 스레드만 접근 할 수 있으니 임계 구역이라고 볼 수 있다.
교착 상태는 아주 다양한 상황에서 발생한다. 이러한 교착상태를 해결하기 위해서는 첫째, 교착 상태가 발생했을 때의 상황을 정확히 표현해보고, 둘째, 교착 상태가 일어나는 근본적인 이유에 대해 알아야한다.
자원할당 그래프 (Resource Allocation Graph)의 개념은 교착상태 발견을 위해 프로세스와 자원과의 관계와 할당 상태를 표현하는 그래프이다.
교착 상태가 발생할 조건에는 네 가지가 있다. 바로 상호 배제, 점유와 대기, 비선점, 원형 대기이다. 즉 조건 중 하나라도 만족하지 않으면 교착 상태가 발생하지 않지만, 조건이 모두 만족될 때 교착 상태가 발생할 가능성이 생긴다고 보면된다.
한 프로세스가 사용하는 자원을 다른 프로세스가 사용할 수 없을 때, 즉 상호배체 상황에서 교착 상태가 발생할 수 있다.
어떠한 자원을 할당받은 상태에서 다른 자원을 할당받기를 기다린다면 교착 상태가 발생할 수 있다. 이렇게 '자원을 할당받은 상태에서 다른 자원을 할당받기를 기다리는 상태'를 점유와 대기라고 한다.
철학자 문제에서 모두 점잖게 포크를 기다리기만 했다. 이처럼 교착 상태가 발생하게된 또 하나의 근본적인 문제는 프로세스가 자원을 비선점하고 있었기 때문이다. 비선점 자원은 그 자원을 이용하는 프로세스의 작업이 끝나야만 비로소 이용할수 있다. 즉, 어떤 프로세스도 다른 프로세스의 자원을 강제로 빼앗지 못했기 때문에 교착 상태가 발생했다고 볼 수 있다.
자원 할당 그래프가 원의 형태로 그려지면 교착 상태가 발생할 수 있다. 이렇게 프로세스들이 원의 형태로 자원을 대기하는 것을 원형 대기라고 한다.
자원 할달 그래프가 원의 형태를 띄지 않으면 교착 상태는 발생하지 않으나, 원의 형태를 띈다고 해서 반드시 교착 상태가 일어나는것은 아니다!!
답 : 4. 모든 철학자가 동시에 식사를 하려고 해서 교착 상태가 발생한 것이었다.!!
답 : 상호배제, 점유와 대기, 비선점, 원형 대기
운영체제는 애초에 교착 상태가 일어나지 않도록 교착 상태 발생 조건에 부합하지 않게 자원을 분배하여 교착 상태를 예방할 수 있다. 교착 상태가 발생하지 않을 정도로 조금씩 자원을 할당하다가 교착 상태의 위험이 있다면 자원을 할당하지 않는 방식으로 교착 상태를 회피도 할 수있다. 그리고 자원을 제약 없이 할당하다가 교착 상태가 검출되면 교착 상태를 회복하는 방법을 취할 수 있다.
교착 상태 발생 필요 조건 네 가지 중 하나를 충족하지 못하게 하는 방법과 같다. 네 가지 조건 중 하나의 조건이라도 만족시키지 않게 할당하면 교착상태는 발생하지 않는다.
자원의 상호 배제를 없앤다는 말의 의미는 모든 자원을 공유 가능하게 만든다는 말과 같다. 다만 이론적으로는 교착상태를 없앨 수 있지만, 현실적으로 모든 자원의 상호 배제를 없애기는 어렵기에 현실에서 사용하기는 다소 무리가 있다.
점유와 대기를 없애면 운영체제는 특정 프로세스에 자원을 모두 할당하거나, 아예 할당하지 않는 방식으로 배분한다. 이 방식도 이론적으로는 교착상태를 해결 할 순 있지만, 단점도 있다.
우선 자원의 활용률이 낮아질 우려가 있다. 점유와 대기를 금지하면 한 프로세스에 필요한 자원들을 몰아주고, 그 다음에 다른 프로세스에 필요한 자원들을 몰아줘야 한다. 이는 당장 자원이 필요해도 기다리는 프로세스와 사용되지 않으면서 오랫동안 할당되는 자원을 다수 양산하기 때문에 자원의 활용률이 낮아진다.
게다가 점유와 대기를 금지하면 많은 자원을 사용하는 프로세스는 자원을 사용할 타이밍을 확보하기가 어렵고, 무한정 기다리게 되는 기아현상을 야기할 우려가 있다.
비선점 조건을 없애면 자원을 이용 중인 프로세스로부터 해당자원을 빼앗을 수있다. 이 방식은 선점하여 사용할 수 있는 일부 자원에 대해서는 효과적이다.
하지만 모든 자원이 선점 가능한 것이 아니다. 한 프로세스의 작업이 끝날 때까지 다른 프로세스가 기다려야 하는 자원도 얼마든지 있다. 그렇기에 비선점 조건을 없애 모든 자원을 빼앗을 수있도록 하여 교착상태를 예방하는 방법은 다소 범용성이 떨어진다.
모든 자원에 번호를 붙이고, 오름차순으로 자원을 할당하면 원형대기는 발생하지 않는다.
이 방식은 앞선 세방식에 비하면 비교적 현실적이고 실용적인 방식이지만, 단점은 있다. 모든 컴퓨터 시스템 내에 존재하는 수많은 자원에 번호를 붙이는 일은 그리 간단한 작이 아니고, 어떤 번호를 붙이는지에 따라 특정 자원의 활용률이 떨어질 수 있다.
교착 상태의 발생 조건을 원천적으로 제거하여 교착 상태를 사전에 방지하는 예방 방식은 교착 상태가 발생하지 않음을 보장할 수는 있지만 여러 부작용이 따른다 !
교착 상태 회피는 교착 상태가 발생하지 않을 정도로만 조심 조심 자원을 할당하는 방식이다. 교착 상태 회피 방식에서는 교착 상태를 한정된 자원의 무분별한 할당으로 인해 발생하는 문제로 간주한다,
프로세스들에 할당할 수있는 자원이 한정된 상황에서 모든 프로세스들이 한 번에 많은 자원을 요구하면 교착 상태가 발생할 위험이 증가한다.
그렇기 때문에 프로세스들에 배분할 수 있는 자원의 양을 고려하여 교착 상태가 발생하지 않을 정도의 양만큼만 자원을 배분하는 방법이 교착 상태 회피이다.
교착 상태를 회피하는 방법을 학습하기 위해서는 안전 상태, 불안전 상태, 그리고 안전 순서열이라는 용어를 알아야한다.
운영체제가 교착 상태를 회피하기 위해서는 시스템 상태가 안전 상태에서 움직이는 경우에만 자원을 할당하면된다. 즉, 교착 상태 회피 방식은 항시 안전 상태를 유지하도록 자원을 할당하는 방식이라 보면 된다.
교착 상태 검출 후 회복은 교착 상태 발생을 인정하고 사후에 조치하는 방식이다.
이 방식에서 운영체제는 프로세스들이 자원을 요구할 때마다 그때그때 모두 할당하며, 교착 상태 발생 여부를 주기적으로 검사한다. 그리고 교착 상태가 검출되면 그때 비로소 다음과 같은 방식으로 회복한다.
교착 상태가 해결될 때까지 한 프로세스씩 자원을 몰아주는 방식이다. 교착 상태가 해결될 때까지 다른 프로세스로부터 자원을 강제로 뺴앗고 한 프로세스에 할당하는 방식이다.
가장 단순하면서 확실한 방식이다. 운영체제는 교착 상태에 놓인 프로세스를 모두 강제 종료할 수도 있고, 교착 상태가 없어질 떄까지 한 프로세스씩 강제 종료할 수도 있다. 전자는 한 방에 해결할 수있는 강력한 방법이지만, 많은 프로세스들이 작업내역을 잃게 될 가능성이 있고, 후자는 작업 내역을 잃는것을 최대한 줄이지만, 교착 상태가 없어졌는지 여부를 확인하는 과정에서 오버헤드를 야기한다.