협동 프로세스는 시스탬 내에서 실행중인 다른 프로세스의 실행에 영향을 주거나 영향을 받는 프로세스다.
협동 프로세스에서 데이터 공유는 동시접근에 의한 데이터의 비 일관성을 낳을 수 있다.
협동 프로세스의 질서있는 실행을 보장하며, 이를 통해 데이터의 일관성을 유지하는 기법을 알아보자.
배경
프로세스는 동시 또는 병렬로 실행될 수 있다.
CPU 스케줄러가 프로세스 사이에서 빠르게 오가며 각 프로세스를 실행해 모든 프로세스를 동시실행 한다. 프로세스는 다른 프로세스가 스케줄 되기 전에 일부분만 진행 할 수 있다.
다른 프로세스에 속한 두개의 명령어 흐름을 다른 프로세스 코어에서 동시 실행 되는 병렬 방식이 있다.
프로세스 동기화에서는 동시 또는 병렬로 실행 될 때 여러 프로세스가공유하는 데이터의 무결성에 어떤 문제가 있는지 설명한다.
두개의 프로세스가 동시에 하나의 변수를 조작하고, 실행 결과가 접근이 발생한 특정 순서에 의존하는 경쟁상황 race condition이 발생할 수 있다.
이같은 문제를 방지하기 위해 한순간에 하나의 프로세스만 변수를 조작하도록 보장해야한다.
임계구역 문제
각 프로세스는 임계구역이라 하는 코드를 포함하고 있다. 임계구역에서는 다른 프로세스와 공유하는 변수, 테이블, 파일 등의 IO를 수행한다.
한 프로세스가 자신의 임계구역에서 수행하는 동안 다른 프로세스들은 그 임계구역에 들어갈 수 없다.
임계구역 문제는 프로세스들이 협력할 때 사용할 수 있는 프로토콜을 설계하는 것이다.
임계구역 문제에 대한 해결안은 다음 세가지를 모두 충족해야 한다. 현실적으로 어렵다.
상호 배제 mutual exclusion : 한 프로세스가 자기의 임계구역에서 실행된다면 다른 프로세스들은 자신의 임계구역에서 실행될 수 없다.
진행 progress (avoid deadlock) : 임계구역에서 실행되는 프로세스가 없고, 임계구역으로 진입하려고 하는 프로세스들이 있다면 나머지 구역에서 실행중이지 않은 프로세스들만 다음에 임계구역에 진입할 프로세스를 결정하는데 참여할 수 있다.
제한된 대기 bounded waiting (avoid starvation) : 프로세스가 자기의 임계구역에 진입하려는 요청을 한 후 부터 그 요청이 허용될 떄 까지 다른 프로세스들이 자신들의 임계구역에 진입하도록 허용되는 횟수에 한계가 있어야 한다
운영체제 내에서 임계구역을 다루기 위해 선점형 커널과 비선점형 커널 두가지 일반적인 접근법이 있다.
선점형 커널 : 커널 프로세스가 커널 모드에서 수행되는 동안 선점되는 것을 허용한다. 동일한 주장을 할 수 없기 때문에 공유되는 커널 자료구조에서 경쟁조건이 발생하지 않는 다는것을 보장하도록 신중히 설계해야 한다. 강제로 방출이 가능.
비선점형 커널 : 커널 모드에서 수행되는 프로세스의 선점을 허용하지 않고 커널 모드 프로세스는 커널을 빠져나갈때 까지 또는 블로킹 될 때 까지 자발적으로 CPU의 제어를 양보할 때 까지 계속 수행한다. 커널 안에서 실행 중인 프로세스는 하나 뿐이기에 커널 자료구조에 대한 경쟁조건을 염려할 필요가 없다.
선점형 커널을 선호하는 이유 : 커널 모드 프로세스가 대기중인 프로세스에게 프로세서를 양도하기 전 오랫동안 실행할 위험이 적기 때문. 그래서 선점형 커널은 응답이 민첩할 수 있다. 또한 실시간 프로세스가 현재 커널에서 실행중인 프로세스를 선점할 수 있어 리얼타임 프로그래밍에 더 적당하다.
피터슨 해결안 Peterson’s solution
소프트웨어 기반의 해결책.
피터슨 해결안은 임계구역과 나머지 구역을 번갈아 가며 실행하는 두개의 프로세스로 한정된다.
피터슨 해결안은 두 프로세스가 두개의 데이터 항목을 공유하도록 하여 해결한다.
(2개의 프로세스일때 flag를 두고 해당 flag의 원소 인덱스가 ture일때만 임계구역에 진입한다는 내용)
동기화 하드웨어
하드웨어 기반의 해결책
모든 해결책들은 임계구역을 보호하기 위한 잠금에 대한 가정이다.
Atomicity
원자적 동작이라는 것은 더이상 쪼갤 수 없는 동작의 단위다.
특별한 하드웨어 인스트럭션을 사용하자 – 원자적 인스트럭션
Mutex Locks (Mutual exclusion locks)
운영체제 설계자들은 임계구역 문제를 해결하기 위해 소프트웨어 도구를 개발하는데 그중 하나가 mutex lock이다.
프로세스는 임계구역에 들어가기 전에 반드시 lock을 획득해야 하고 임계구역을 빠져 나올 때 lock을 반환해야 한다.
바쁜 대기 busy waiting : 프로세스가 임계구역에 있는 동안 임계구역에 들어길 원하는 다른 프로세스들은 반복문을 계속 실행해햐 하는 스핀락 spin lock 문제가 있다.
spin lock은 context switcing을 필요로 하지 않는 것이 장점이다. 짧은 시간을 제어권을 가진다면 spin lock이 유용하다.
멀티코어 시스템에서 spin lock이 선호되는 경향이 있다.
mutex locks 예제
#include <stdio.h>
#include <pthread.h>
int sum = 0;
pthread_mutex_t mutex;
void *counter(void *param)
{
int k;
for (k = 0; k < 10000; k++){
/* 구역 진입 */
pthread_mutex_lock(&mutex);
/* 임계 구역 */
sum++;
/* 구역 종료 */
pthread_mutex_unlock(&mutex);
}
pthread_exit(0);
}
int main(void)
{
pthread_t tid1, tid2;
pthread_mutex_init(&mutex, NULL);
pthread_create(&tid1, NULL, counter, NULL);
pthread_create(&tid2, NULL, counter, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
printf("%d\n", sum);
}
실행결과
% gcc -pthread mutex_locks_test.c
% ./a.out
20000
Semaphores
mutex와 유사하게 작동하지만 세마포어는 좀 더 정교하게 동기화 할 수 있다.
세마포어 사용법
카운팅 세마포어 : 유한한 갯수를 가진 자원에 대한 접근을 제어하는데 사용한다. 세마포어 값이 0이 되면 모든 자원을 사용중이란 뜻이다.
이진 세마포어 : 0과 1사이의 값만 지정하므로 mutex lock과 유사하게 작동한다.
wait(), P() : 리소스를 사용하기 위해 세마포어의 값을 매개변수로 넘긴다.
signal(), V() : 사용한 리소스를 반환하기 위해 세마포어의 값을 매개변수로 넘긴다.
mutex와 semaphore의 간단한 이야기 뮤텍스는 하나의 자원에 대한 접근 이야기고 / 아까 뮤텍스는 그 구간전체?를 락잡는다면 세마포어는 자원을 할당할 수 있는 갯수에 대한 이야기 / 세마포어는 부분구간별로 락을 잡는 개념임 signal 은 카운트를 올리는 거고, wait는 카운트를 줄이는 거