봉황대 in CS

[Chapter 6. 프로세스 동기화] 원소적 실행과 임계 구역 본문

Computer Science & Engineering/Operating System

[Chapter 6. 프로세스 동기화] 원소적 실행과 임계 구역

등 긁는 봉황대 2022. 7. 21. 11:24

* 본 글은 '운영체제(Operating System: Concepts) 9th edition'의 내용과 2021학년도 1학기에 수강한 '운영체제' 과목 강의 내용을 함께 정리하여 작성하였습니다.

 

 

협력적 프로세스(Cooperating Process)가 병행 또는 병렬로 실행될 때

여러 프로세스가 공유하는 데이터의 무결성에 어떤 문제가 일어나는가?

 

* 협력적 프로세스 : 시스템 내에서 실행 주인 다른 프로세스의 실행에 영향을 주거나 받는 프로세스

 


생산자-소비자 문제를 다시 보자.

* 생산자 프로세스 : 정보를 생산하는 프로세스

* 소비자 프로세스 : 생산자 프로세스가 생산한 정보를 소비하는 프로세스

 

생산자와 소비자 프로세스들이 병행으로 실행되도록 하기 위해서

공유하는 메모리 영역에 원형 공유 버퍼를 생성, 생산자가 정보를 채워 넣고 소비자가 소모할 수 있도록 만들어주는 것이 해결책이었다.

 

이때 두 포인터 변수 in과 out을 통해 구현할 수 있었는데, (데이터들의 위치를 가리키는 변수들)

이 두 변수는 공유 변수이지만 한쪽에서만 값을 변경하기 때문에 간섭 문제가 발생하지는 않는다.

 

#include <stdio.h>
#include <pthread.h>

void consumer (void);
char buffer[n];
int n, in=0, out=0;

// 생산자 코드
int main()
{
	char nextp;
	int i;
	pthread_t tid;
    
	pthread_create (&tid, NULL, consumer, NULL);
	for (i = 0; i < 500; i++) {
		produce an item in nextp
		while ((in+1) % n == out); // 대기 loop
		buffer[in] = nextp;
		in++;
		in %= n;
	}
	pthread_join (tid);
}

// 소비자 코드
void consumer(void)
{
	char nextc;
	for (i = 0; i < 500; i++) {
		while (in == out);
		nextc = buff[out];
		out++;
		out %= n;
		...
		consume the item in nextc;
	}
}

 

하지만 위의 코드에서 볼 수 있듯이

생산자 측에서 입력 후 in++를 하며, 소비자 측에선 in==outempty로 간주하므로 in < out -1 이어야 fullempty를 구분할 수 있다.

즉, 원형 공유 버퍼의 크기 n 중 n-1개만 사용하고 있다는 것이다.

 

이를 해결하기 위해서는 공유 변수 counter를 사용할 수 있다.

counter를 통해 버퍼 내의 데이터의 개수를 세는 것이다.

 

// 생산자 코드
while (1) {
	...
	produce an item in nextp
	...
	while (counter == n); // wait for an empty buffer
	buffer[in] = nextp;
	in = (in + 1) % n;
	counter = counter + 1;
}

// 소비자 코드
while (1) {
	while (counter == 0); // wait for an item
	nextc = buffer[out];
	out = (out + 1) % n;
	counter = counter -1;
	...
	consume the item in nextc
	...
}

 

이렇게 한다면 문제가 해결될 것 같지만, 실제로 이들을 병행적으로 수행시켜보면 올바르게 동작하지 않는다.

그 이유는 두 개의 프로세스가 동시에 공유 변수 counter를 조작하도록 허용했기 때문이다.

 

현재 counter의 값이 5라고 했을 때 해당 코드를 실행시켜보면 counter의 값은 5가 되어야 하지만, 4 또는 6이 될 수 있다.

 

 

이처럼 동시에 여러 프로세스가 동일한 자료를 접근 & 조작하여 그 실행 결과가 접근이 발생한 특정 순서에 의존하는 상황을

경쟁 상황(race condition)이라고 한다.

 

따라서 한 순간에 하나의 프로세스만이 공유 변수를 조작하도록 보장해야 하며,

이것을 위해 어떤 형태로는 프로세스들이 동기화(Synchronization) 되도록 해야 한다.

 

원소적(Atomic) 실행

공유 변수를 통해 상호작용하는 프로세스 또는 스레드 간에 문맥 교환이 언제 일어나도 간섭이 없는 실행이 보장되는 것을 말한다.

원소적 실행을 위해서는 공유 변수를 사용하는 코드 영역에 임계 구역을 설정한다.

 

임계 구역 (Critical Section)


원소적 실행을 위해

각 프로세스 또는 스레드가 공유 변수, 자료구조, 파일 등을 배타적으로 읽고 쓸 수 있도록 설정코드 세그먼트

 

각 프로세스는 임계 구역이라는 코드 부분을 포함하고 있으며,

한 프로세스가 자신의 임계 구역에서 수행하는 동안에 다른 프로세스들은 그들의 임계 구역에 들어갈 수 없다.

 

코드 상에 임계 구역을 설정하여 프로세스들의 진입과 진출을 순서화(Serialize)하는 것을 통해

프로세스들 간에 동기화(Synchronization)가 일어날 수 있는 것이다.

 

프로세스의 일반적인 구조

 

* 진입 구역 (entry section) : 자신의 임계 구역으로 진입하기 위해 진입 허가 요청을 해야 하는 코드 부분

* 퇴출 구역 (exit section) : 임계 구역을 빠져나가는 코드 부분

* 나머지 구역 (remainder section) : 나머지 코드 부분을 총칭

 

임계 구역의 설정에는 mutex를 기본적으로 사용하며, (mutex : mutual exclusion, 상호 배제)

생산자-소비자 문제 코드를 다음과 같이 변경할 수 있다.

 

// 생산자 코드
while (1) {
	...
	produce an item in nextp
	...
	while (counter == n); // wait for an empty buffer
	buffer[in] = nextp;
	in = (in + 1) % n;
    
	Enter_Mutex(lock);
	counter = counter + 1;
	Exit_Mutex(lock);
}

// 소비자 코드
while (1) {
	while (counter == 0); // wait for an item
	nextc = buffer[out];
	out = (out + 1) % n;
    
	Enter_Mutex(lock);
	counter = counter -1;
	Exit_Mutex(lock);
    
	...
	consume the item in nextc
	...
}

 

임계 구역에 대한 요구사항

1. 상호 배제 (mutual exclusion)

한 프로세스가 임계 구역을 실행하는 중일 때 다른 어떤 프로세스도 임계 구역을 실행할 수 없다.

 

2. 진행 (progress)

자신의 임계 구역에서 실행되고 있는 프로세스가 없고 그들 자신의 임계 구역에 진입하려고 하는 프로세스들이 있는 상황이라면

나머지 구역에서 실행 중이지 않은 프로세스들만 다음에 누가 그 임계 구역으로 진입할 수 있는 지를 결정하는 데에 참여할 수 있으며

이 결정은 무한정 연기될 수 없다.

 

3. 제한된 대기 (bounded waiting)

한 프로세스가 자신의 임계구역에 대한 진입 요청을 한 후부터 그 요청이 허용될 때까지의 기간 내에

다른 프로세스가 그들 자신의 임계구역에 진입하도록 허용되는 횟수에는 제한이 있어야 한다.

 

 

반응형
Comments