봉황대 in CS

[Chapter 3. 프로세스] 프로세스간 통신 본문

Computer Science/Operating System

[Chapter 3. 프로세스] 프로세스간 통신

등 긁는 봉황대 2022. 7. 12. 00:21

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

 

 

운영체제 내에서 실행되는 병행 프로세스들은 다음의 두 가지로 구분될 수 있다.

 

1. 독립적 프로세스

     프로세스가 시스템에서 실행 중인 다른 프로세스들에게 영향을 주거나 받지 않는 프로세스

 

2. 협력적 프로세스

     프로세스가 시스템에서 실행 중인 다른 프로세스들에게 영향을 주거나 받는 프로세스

 

즉, 독립적인 프로세스는 다른 프로세스와 데이터를 공유하지 않으며, 협력적인 프로세스는 공유한다.

 


협력적 프로세스의 대표적인 예시로는 생산자-소비자 문제(Producer-Consumer Problem)가 있다.

생산자 프로세스는 정보를 생산하는 프로세스이며, 소비자 프로세스는 생산자 프로세스가 생산한 정보를 소비하는 프로세스이다.

 

생산자와 소비자 프로세스들이 병행으로 실행되도록 하려면 어떻게 해야 할까?

 

이에 협력적 프로세스들은 데이터와 정보를 교환할 수 있는 프로세스 간 통신(interprocess communication, IPC) 기법을 필요로 한다.

 

IPC의 모델에는 공유 메모리 시스템메시지 전달 시스템이 있으며, 현재 많은 시스템들이 두 가지를 모두 구현한다.

 

먼저 각각의 모델에 대하여 알아본 후 서로를 비교해보자.

 

공유 메모리(Shared Memory) 시스템


공유 메모리 시스템을 사용하기 위해서는 먼저 통신하는 프로세스들이 공유 메모리 영역을 구축하여야 한다.

 

공유 메모리 영역을 구축하기 위해서는 시스템 호출이 필요하며, 

구축 후에는 이 영역에 대한 모든 접근은 일반적인 메모리 접근으로 취급되어 커널의 도움이 필요 없다.

 

공유 메모리 영역은 공유 메모리 세그먼트(조각)를 생성하는 프로세스의 주소 공간에 위치한다.

해당 공유 메모리 세그먼트를 통해 통신하고자 하는 다른 프로세스들은 이 세그먼트의 id를 자신의 주소 공간에 추가해야 한다.

 

공유 메모리 생성을 위한 함수는 다음과 같다.

#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);

 

shmget() 함수를 부르면 key를 접근 번호로 하는 공유 메모리 공간을 할당해줄 것을 커널에 요청한다.

커널에서 성공적으로 공유 메모리 공간을 할당하게 되면 해당 공유 메모리를 가리키는 식별자(id)를 return 해준다.

 

일반적으로 운영체제는 한 프로세스가 다른 프로세스의 메모리를 접근하는 것을 금지한다.

따라서 공유 메모리는 둘 이상의 프로세스가 이 제약 조건을 제거하는 것에 동의하는 것을 필요로 하며,

프로세스들은 동시에 동일한 위치에 쓰지 않도록 책임져야 한다.

 


위에서 잠깐 나온 생산자-소비자 문제는 공유 메모리를 통해 해결할 수 있다.

 

생산자 프로세스와 소비자 프로세스가 공유하는 메모리 영역에 원형 공유 버퍼를 생성하여, 생산자가 정보를 채워 넣고 소비자가 소모할 수 있도록 만들어주면 된다.

 

하지만 버퍼가 크기가 고정되어 있는 유한 버퍼(bounded buffer)인 경우

1. 버퍼가 비어 있는 경우 소비자 프로세스는 받아올 수 있는 데이터가 없으므로 대기하여야 하며,

2. 버퍼가 꽉 차 있는 경우 데이터를 저장할 수 있는 공간이 더 없으므로 생산자 프로세스는 대기하여야 한다.

 

이는 다음의 두 포인터 변수를 통해 구현할 수 있다.

     버퍼 내 다음으로 비어 있는 위치를 가리키는 포인터 변수 in

     버퍼 내에서 첫번째로 데이터가 차 있는 위치를 가리키는 포인터 변수 out

 

* in == out인 경우 버퍼는 비어 있다는 신호

* ((in+1) % BUFFER_SIZE) == 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;
	}
}

 

메시지 전달(Message Passing) 시스템


프로세스 간의 통신은 send와 receive 프리미티브(primitive)에 대한 호출에 의해 발생한다.

메시지 전달 시스템은 최소한 이 두 가지 연산을 제공하여야 한다.

 

* primitive : 컴퓨터 프로그래밍 언어에서 프리미티브는 이용 가능한 가장 단순한 요소들

프리미티브는 주어진 기계(machine)의 프로그래머에게 이용 가능한 가장 작은 처리(processing)의 단위이거나 언어에서 표현의 원자 요소가 될 수 있다.

 

만약 두 프로세스가 통신을 원한다면 이들 사이에는 통신 연결(communication link, 링크)이 설정되어야 한다.

하나의 링크와 send, receive 연산을 논리적으로 구현하는 방법은 다양하게 존재하며, 이는 다음과 같다.

 

1. 직접 또는 간접 통신

2. 동기식 또는 비동기식 통신

3. 자동 또는 명시적 버퍼링

 

이 3가지에 대하여 차례차례 살펴보자.

 

직접 또는 간접 통신

통신을 원하는 프로세스들은 서로를 가리킬 수 있는 방법이 존재해야 한다.

명명(Naming)의 방법에는 직접 통신과 간접 통신이 있다.

 

[ 직접 통신 ]

 

대칭적 기법

통신을 원하는 각 프로세스는 통신의 수신자 또는 송신자의 이름을 명시해야 한다.

send(P, message)  # 프로세스 P에게 메시지를 전송한다.
receive(Q, message)  # 프로세스 Q로부터 메시지를 수신한다.

 

비대칭적 기법

송신자만 수신자 이름을 지명한다. 수신자는 송신자의 이름을 제시할 필요가 없다.

send(P, message)  # 프로세스 P에게 메시지를 전송한다.
receive(id, message)  # 임의의 프로세스로부터 메시지를 수신한다.

변수 id는 통신을 발생시킨 프로세스의 이름으로 후에 설정된다.

 

* 직접 통신의 특성

(1) 통신을 원하는 각 프로세스의 쌍들 사이에 연결이 자동적으로 구축된다.

      프로세스들은 통신하기 위해 서로 상대방의 신원(identity)만 알면 된다.

(2) 연결은 정확히 두 프로세스들 사이에만 연관된다.

(3) 통신하는 프로세스들의 각 쌍 사이에는 정확하게 하나의 연결이 존재해야 한다.

 

 

[ 간접 통신 ]

 

메시지들은 메일박스(mailbox) 또는 포트(port)로 송, 수신된다.

메일박스는 프로세스들에 의해 메시지들이 넣어지고 제거될 수 있는, 각각이 고유의 id를 가지는 객체이다. 

 

프로세스들은 여러 메일박스를 통해 여러 프로세스들과 통신이 가능한데,

두 프로세스들이 공유 메일박스를 가질 때만 이들 프로세스가 통신할 수 있다.

send(A, message)  # 메시지를 메일박스 A로 송신한다.
receive(Q, message)  # 메시지를 메일박스 A로부터 수신한다.

 

* 간접 통신의 특성 (직접 통신의 특성과 비교하면서 보길 바란다.)

(1) 한 쌍의 프로세스들 사이의 연결은 이들 프로세스가 공유 메일박스를 가질 때만 구축된다.

(2) 연결은 두 개 이상의 프로세스들과 연관될 수 있다.

(3) 통신하고 있는 각 프로세스들 사이에는 다수의 서로 다른 연결이 존재할 수 있고, 각 연결은 하나의 메일박스에 대응된다.

 

동기식 또는 비동기식 통신

send와 receive 프리미티브를 구현하기 위해서는 서로 다른 설계 옵션이 존재한다.

즉, 메시지 전달은 봉쇄형(blocking) 방식 (동기식) 또는 비봉쇄형(non-blocking)방식 (비동기식)으로 전달될 수 있다.

 

봉쇄형 보내기

송신하는 프로세스는 메시지가 수신 프로세스 또는 메일 박스에 의해 수신될 때까지 봉쇄된다.

 

비 봉쇄형 보내기

송신하는 프로세스가 메시지를 보내고 작업을 재시작한다.

 

봉쇄형 받기

메시지가 이용 가능할 때까지 수신 프로세스가 봉쇄된다.

 

비 봉쇄형 받기

송신하는 프로세스가 유효한 메시지 또는 null을 받는다.

 

자동 또는 명시적 버퍼링 (Buffering)

버퍼링은 정보의 송수신을 원활하게 하기 위해서 정보를 일시적으로 저장하여 처리 속도의 차이를 흡수하는 방법을 발한다.

(Buffering : 완충)

 

통신하는 프로세스들에 의해 교환되는 메시지들은 임시 큐에 들어 있는데,

이 큐를 구현하는 방식에는 세 가지가 존재한다.

 

무용량 (zero capacity)

큐의 최대 길이가 0이다. 버퍼가 없는 메시지 시스템이라고도 부른다.

송신자는 수신자가 메시지를 수신할 때까지 기다려야 한다.

 

유한 용량 (bounded capacity)

큐는 유한한 길이 n을 가지고 있어, 최대 n개의 메시지가 그 안에 들어 있을 수 있다.

 

큐가 꽉 차지 않았다면 메시지는 큐에 놓여 송신자는 대기하지 않고 실행을 계속할 수 있으나,

큐가 꽉 찼다면 큐 안에 공간이 이용 가능할 때까지 송신자는 봉쇄되어야 한다.

 

무한 용량 (unbounded capacity)

큐는 무한한 길이를 갖고 있다.

따라서 메시지들이 얼마든지 큐 안에서 대기 가능하여 송신자는 봉쇄되지 않는다.

 

 

반응형
Comments