[운영체제/OS] 동기화 이슈의 원인과 임계 구역(Critical Section)의 개념
kindof
·2021. 6. 14. 20:38
0. 들어가면서
동기화(Synchronization)는 사전적 의미로 시스템을 동시에 작동시키기 위해 여러 사건들을 조화시키는 것을 뜻합니다.
이전에 멀티프로세싱과 멀티쓰레딩에 대해서 공부했는데, 동기화 이슈는 이러한 멀티태스킹 환경에서 정말 중요한 역할을 합니다.
1. 동기화는 왜 필요한가?
멀티쓰레딩에 대해 공부했을 때를 떠올려보겠습니다.
멀티쓰레딩이란 기본적으로 프로세스 이미지를 공유 가능한 부분과 공유 가능하지 않은 부분으로 나눠서 공유 가능한 부분을 Thread란 개념으로 나눈 개념이었습니다.
그런데 공유 데이터(Global Data)에 대해 아무런 통제를 해주지 않을 때, 어떤 프로세스나 쓰레드가 이 데이터를 읽고 쓰고 있는 상황에서 다른 녀석이 들어와서 읽고 쓰게 되버릴 수 있고 그렇게 되면 결국 프로그램의 수행 결과가 Non-Deterministic(예측 불가능)해집니다.
이런 문제 상황을 어떻게 방지할 수 있을까요? 이 질문에 대한 답이 동기화(Synchronization)입니다.
아래 예시를 보겠습니다.
// Producer = 데이터를 추가하는 역할(counter 증가)
register A := counter; // load
register A := registerA + 1; // add
counter := registerA; // store
// Consumer = 데이터를 빼는(소모하는) 역할(counter 감소)
registerB := counter; // load
reigsterB := registerB - 1; // sub
counter := registerB; // store
위 상황에서 만약 현재 counter 값을 5라고 해봅시다. 만약 동기화 이슈에 대한 처리를 하지 않았을 때는 어떤 문제가 생길까요?
- Register A, B가 counter 변수를 동시에 Load
- A는 +1을 하고, B는 -1을 한다.
- A가 counter를 store하고, B가 store하면 최종 counter는 4가 된다.
- 만약 B가 먼저 store하면 결과는 6이 된다.
위와 같이 문제가 발생화는 상황을 Race Condition이라고 부릅니다. 프로그램의 수행 결과가 예상하는 대로 나올 수 없는 상황이죠.
지금까지 이야기 한 내용을 그림을 가지고 정리해보겠습니다.
맨 왼쪽 그림은 유저 레벨에서 하나의 데이터에 대해 여러 쓰레드가 접근하는 것이고, 두번째 그림은 커널 영역에 있는 데이터에 대해 여러 시스템 콜 핸들러가 접근하는 것입니다.
세번째 그림은 커널 영역의 데이터에 대해 시스템 콜 핸들러와 인터럽트 핸들러가 접근하는 것이고, 마지막 그림은 두 개의 인터럽트 핸들러가 접근하는 것입니다.
이런 식으로 커널의 Global 데이터가 공유될 수 있으면 위와 같은 Race Condition 문제가 생기게 됩니다. 그리고 비단 멀티쓰레드 환경 뿐만 아니라, 멀티프로세싱 환경에서도 파일과 같은 메모리를 공유하고 있기 때문에 System call 핸들러가 동시에 겹칠 수 있겠죠.
2. Critical Section
Critical Section이란 동기화 문제가 발생한 Global Data 영역을 가리키는 말입니다. 동기화 이슈를 방지하기 위해 앞으로 각 프로세스는 코드 조각 안에 Critical Section을 설정하고 그 안에 존재하는 공유 데이터에 대한 접근을 컨트롤할 예정입니다.
한 예시로 Critical Section을 Lock 개념으로 구현해볼 수 있는데요. Lock은 화장실에 들어갈 때 노크를 해서 안에 사람이 있으면 기다리고, 화장실을 쓰는 사람은 문을 잠그고 사용하다가 나올 때 문을 열어주는 상황과 매우 유사합니다.
- Critical Section에 들어올 때 프로세스는 "입장한다"라는 신호로 Lock을 건다. // Entry Section
- Critical Section에서 나갈 때 프로세스는 Lock을 해제하여 "다 썼음"을 표시한다. // Exit Section
이런 식으로 acquire() / lock() 과 release() / unlock()을 이용해서 자원을 할당받고 할당해제 하는 식으로 Critical section을 관리할 수 있습니다.
한편, Critical Section을 구현하기 위해서는 아래와 같은 조건이 충족되어야 합니다.
- Mutual Exclusion(상호 배제): 내가 쓰면 다른 프로세스는 못 쓰게 하는 것
- Progress(진행): 다른 프로세스가 쓰고 있지 않으면 내가 쓸 수 있는 것
- Bound Waiting(한계 대기): 다른 프로세스가 쓰고 있으면 기다리되, 기다리는 시간에 한계치(bound)를 설정하는 것
Mutual Exclusion과 Progress는 직관적으로 당연해보입니다. 동기화 이슈를 해결하기 위해서는 당연히 내가 어떤 자원을 쓰고 있을 때 다른 프로세스가 접근하지 못하게 막아야하고, 어떤 자원을 아무도 안 쓰고 있다면 내 프로세스가 가서 자원을 점유할 수 있어야하죠.
그런데 Bound Waiting 조건은 왜 있어야 할까요? 프로세스가 임계구역을 들어가기 위한 요청을 하고 무한정으로 기다리게 된다면, 해당 프로세스는 자신의 일을 아무것도 할 수 없고 만약 해당 자원을 쓰는 쓰레드가 Block이 된다면 영영 진입점에서 기다려야하는 불상사가 생기겠죠? 이런 문제를 방지하기 위한 조건이라고 이해하면 될 것 같습니다.
이번 시간에는 이렇게 동기화 이슈를 해결해야 하는 필요성과 그 핵심 아이디어를 살펴봤습니다.
다음 포스팅에서는 Critical Section을 구현하는 여러가지 방법들에 구체적으로 알아보도록 하겠습니다.
감사합니다.
'CS > OS' 카테고리의 다른 글
[운영체제/OS] 데드락(Deadlock)의 개념과 발생조건 (0) | 2021.06.14 |
---|---|
[운영체제/OS] 동기화 이슈 처리하기 - (1) (0) | 2021.06.14 |
[운영체제/OS] CPU 스케쥴링 - (3) (0) | 2021.06.14 |
[운영체제/OS] CPU 스케쥴링 - (2) (0) | 2021.06.14 |
[운영체제/OS] CPU 스케쥴링 - (1) (0) | 2021.06.14 |