배경

공유 메모리 시스템 에서 언급했던 것처럼 버퍼에 여러 프로세스가 동시에 값을 넣는다면 어떻게 되는지 알아본다. 간단하게 변수에 1을 더하고 값을 가져와보자. 단 2개의 프로세스가 동시에 작업을 수행한다.

Untitled

같은 명령을 수행했는데 동시에 실행했다는 이유만으로 결과가 달라진다. 이런 상황을 경쟁 상황(race condition)이라 한다. 이런 문제는 다중스레드 시스템에서 빈번히 발생하기 때문에 협력하는 프로세스들 간의 프로세스 동기화와 조정은 중요하다.

치명적인 구역 문제

치명적인 구역(critical section) 혹은 임계 구역은 반드시 한 번에 하나의 프로세스만 접근해야 하는 구역을 말한다. 치명적인 구역 문제는 한 번에 하나의 프로세스만 접근할 수 있도록 접근을 관리하고 요청을 처리하는 문제이다. 코드에서 진입을 요청하는 부분을 집입 구역(entry), 나오는 부분을 퇴장 구역(exit), 그 외의 코드를 나머지 구역이라 한다. 치명적인 구역 문제를 해결하기 위해서는 최소한 아래 조건을 충족해야 한다.

선점형 커널과 비선점형 커널

선점형(preemption)은 하나의 프로세스가 CPU를 점유하고 있을 때 다른 프로세스가 CPU를 빼앗는 기법을 말한다. 비선점형 커널은 프로세스가 커널을 빠져나가거나 blocking되거나 제어를 양도할 때까지 기다리는 방식이다. 비선점형 커널은 한 순간에 커널에서 실행중인 프로세스가 하나임을 보장하기 때문에 경쟁 상황이 발생하지 않는다. 하지만 그렇기 때문에 응답이 민첩하지 못하다. 선점형 커널은 경쟁 상황을 처리하는 것이 특히 어렵지만 응답이 민첩하고 실시간 프로그래밍에 더 적합하다. 대부분 선점형 커널을 더 선호한다.

피터슨 해결안

치명적인 구역을 해결하는 가장 고전적인 방법이다. 현대 컴퓨터 구조가 save, load를 구현하는 방식 때문에 지금은 조금 안맞지만 치명적인 구역의 세 가지 요구 조건을 만족하는 데는 충분하다.

do {
  flag[i] = true; // 곧 들어갑니다.
  turn = j;       // 당신부터 하십시오.
  while (flag[j] && turn==j); // 내 차례가 아닐 경우 대기
  // 치명적인 구역 코드는 여기에
  flag[i] = false; // 이제 나갑니다.
} while (true);

flag는 치명적인 구역으로 진입할 준비가 되었다는 것을, turn은 실제 내가 사용한다는 것을 뜻한다. 또 지금은 i 프로세스와 j 프로세스만 있다고 가정한다.

먼저 flag로 내가 곧 치명적인 영역으로 들어간다는 것을 알리고, turn을 상대 프로세스에게 넘긴다. 조금 의아할 수 있는데 이렇게 상대에게 turn을 넘기면 어떤 프로세스도 무기한 기다리지 않아도 된다. 또한 turn은 동시에 두 값이 될 수는 없기 때문에 둘 중 하나만 대기하던 while문을 빠져나갈 수 있다 .

동기화 하드웨어

현대 컴퓨터 구조에서는 하드웨어부터 소프트웨어까지 모두 합심해 치명적인 구역을 보호하는 방법을 제공한다. 모든 방식은 치명적인 구역에 자물쇠를 걸어 잠그는 방식(locking)이다.

치명적인 구역 문제는 공유 변수가 변경되는 동안 인터럽트를 발생시키지 않으면 해결할 수 있다. 인터럽트가 곧 선점이기 때문이다. 하지만 이렇게 하면 멀티 프로세서 환경에서 메시지를 교환하는데 시간이 오래 걸리게 되고 성능이 떨어지게 된다. 그래서 현대 하드웨어들은 인터럽트가 되지 않고 원자적으로(atomically) 수행되는 명령을 제공한다. 원자적이라는 말은 명령어 묶음을 한 번에 실행하는 것을 말한다. 이 명령들을 실행하는 동안에는 다른 명령은 실행될 수 없다.