프로세스 간 통신 (IPC)
Intro
프로세스는 각각 독립적으로 실행될 수도, 프로세스끼리 협력하며 실행될 수도 있다. 협력하는 이유는 첫째, 프로세스 간 정보 교환을 위해서다. 같은 정보를 여러 프로세스가 공유하며 유저에게 더 나은 경험을 줄 수 있다. 둘째, 작업을 더 작은 작업으로 쪼개어 병렬적으로 실행해 더 빠르게 작업을 완료하기 위해서다. 셋째, 모듈화를 위해 프로세스를 여러 모듈로 나누어 각각 협력하여 동작하도록 하기 위해서다. 이러한 협력을 위해 필연적인 프로세스 간 통신을 Interprocess Communication (IPC) 이라고 한다.
IPC에는 두 가지 기반 모델이 있다.
- Shared Memory
- Message Passing
Shared Memory
이름 그대로 통신하는 두 프로세스가 같은 메모리 공간을 활용하여 통신하는 것이다. 이를 위해 두 프로세스는 같은 메모리 공간을 공유할 수 있어야 한다.
장점
- 통신하는 프로세스가 같은 메모리를 공유하며 통신하기 때문에 메모리를 읽고 쓰는 시간만큼 통신에 소요되기 때문에 빠르다. 공유 메모리 영역을 만들 때만 system call을 사용하고, 통신할 때는 일상적인 메모리 접근으로 취급되기 때문이다.
단점
- 여러 프로세스가 동시에 메모리에 접근해야 하기 때문에 복잡도가 높다.
- 두 프로세스가 같은 메모리 공간을 공유하기 때문에, 두 프로세스가 동시에 메모리에 접근할 수 있다. 데이터 불일치가 발생할 수 있다.
특징
- 공유 메모리 영역은 공유 메모리 segment를 생성하는 프로세스의 주소 공간에 위치한다.
- 데이터의 형식과 위치는 프로세스에 의해 결정되고, OS가 상관하지 않는다.
- 대신 프로세스는 동일한 위치에 쓰지 않도록 알아서 주의해야 한다.
Producer - Consumer model
생산자 - 소비자 모델은 협업하는 두 프로세스 간 흔히 사용되는 모델이다. 한 쪽은 정보를 생산하고, 다른 한 쪽은 그 정보를 소비한다.
예시
- 컴파일러는 어셈블러를 위해 어셈블리 코드를 생산한다.
- 어셈블러는 로더를 위해 오브젝트 모듈을 생산한다.
- 웹 서버가 콘텐츠를 생성하고, 클라이언트가 그 콘텐츠를 소비한다.
Buffer
생산자 - 소비자 모델을 구현하는 한 가지 방식 중 하나는 공유 메모리 공간에 버퍼를 두는 것이다. 생산자는 버퍼에 데이터를 쓰고 소비자는 버퍼에서 데이터를 읽는다. 생산되지 않은 데이터를 읽지 않도록 두 프로세스는 동기화 되어야 한다.
버퍼는 unbounded buffer와 bounded buffer로 나뉜다. unbounded buffer를 사용하면 생산자는 계속해서 생산할 수 있고, 소비자는 계속해서 아이템을 대기할 수 있다. 버퍼의 사이즈의 제한이 없어서 unbounded buffer이다. bounded buffer를 사용하면 생산자는 버퍼가 가득 차면 대기하고, 소비자는 버퍼가 비면 대기한다.
Message Passing
메시지 전달 방식은 공유 메모리 공간 없이도 두 프로세스가 통신할 수 있는 메커니즘이다. 따라서 다른 컴퓨터에 위치한 프로세스와 통신할 때 유용하다. 메시지 전달 방식은 send와 receive로 나뉜다.
장점
- 작은 양의 데이터를 교환할 때 효과적이다. 분산 시스템을 구현할 때 shared memory보다 쉽다.
단점
- Shared memory에 비해 느리다.
- Shared memory는 메모리를 읽고 쓰는 시간만큼만 통신에 소요되지만, message passing은 system call을 통해 커널을 거쳐 상대적으로 더 오래 걸리는 작업을 해야 하기 때문이다.
메시지 크기
메시지의 크기는 고정될 수도 있고, 가변적일 수도 있다.
-
고정된 크기
- 시스템에서는 구현이 쉽다. 크기를 고정해버리면 상대적으로 많은 것을 고려하지 않아도 된다.
- 하지만 프로그래머에게는 고정된 크기가 제약조건이 된다.
-
가변 크기
- 시스템에서의 구현이 복잡하다.
- 하지만 프로그래머가 이를 유연하게 사용할 수 있다.
Link
두 프로세스가 통신하기 전에 논리적인 연결이 있어야 한다. 이를 link라고 한다.
link와 send, receive를 활용하여 다음과 같이 통신할 수 있다.
-
직접 통신과 간접 통신
- 프로세스 간 직접 통신하거나, 메일함 등을 이용해 간접 통신한다.
-
동기 통신과 비동기 통신
- 동기 통신은 상대가 다 받았을 때 작업이 완료된다. 비동기 통신은 일단 보내고 다른 일을 할 수 있다. 반대의 경우도 마찬가지다.
-
자동 버퍼링과 명시적 버퍼링
- 메시지를 저장하는 큐의 용량이 zero거나, 고정이거나, 가변적이다.
Naming
통신을 원하는 프로세스는 서로를 지칭해야 한다. 직접 통신과 간접 통신 각각에서 명명하는 법을 살펴보자.
직접 통신
send()
와 receive()
에 프로세스의 이름을 명시해야 한다. 보내는 프로세스는 받는 프로세스를 인자로 넘겨야 하고, 받는 프로세스는 보내는 프로세스를 인자로 넘겨야 한다. 두 프로세스 간 정확히 하나의 link가 생성된다.
Addressing 방법은 다음과 같다.
- Symmetry in addressing (대칭적 명명)
- 통신하려는 두 프로세스가 서로의 이름을 알고 있어야 한다.
send(P, message)
,receive(Q, message)
- Asymmetry in addressing (비대칭적 명명)
- 전송하는 프로세스만 상대 프로세스의 이름을 알고 있으면 된다.
send(P, message)
,receive(id, message)
직접 연결 통신의 단점은 프로세스를 정의할 때 모듈화 정도가 떨어진다는 점이다. 특히 id를 하드코딩하는 것은 간접적으로 명시하는 것에 비해 바람직하지 않은 방법이다.
간접 통신
메일함을 이용한 통신이다. 메일함으로 보내고, 메일함에서 꺼낸다. 메일함은 고유한 id 값을 가지고 있다. 마치 공유 메모리처럼 공유 메일함을 가질 때만 두 프로세스가 통신할 수 있다.
send(A, message)
, receive(A, message)
와 같이 사용한다. 이 때 A는 공유 메일함의 id이다. 두 프로세스가 모두 공유 메일함을 가질 때만 link가 생성된다. 또한 직접 통신에서 두 프로세스가 정확히 하나의 link를 가지는 것과 달리 간접 통신에서는 여러 프로세스와 연결될 수 있다.
메일함의 소유는 프로세스일 수도, OS일 수도 있다.
- 프로세스 소유
- 프로세스가 종료되면 메일함도 사라진다.
- 메일함을 소유한 프로세스가 owner. 메시지 수신에 혼란이 없다.
- OS 소유
- 프로세스가 종료되어도 메일함이 사라지지 않는다.
- 메일함을 생성하는 프로세스가 owner가 된다.
- 그리고 이 권한은 system call로 다른 프로세스에 전달할 수 있다.
Blocking and Non-blocking
프로세스 간 통신은 send()
와 receive()
로 이루어져 있고, 이를 다음과 같은 메커니즘으로 나눌 수 있다.
-
Blocking send
- 메시지를 보내는 프로세스는 메시지를 받는 프로세스가 메시지를 받을 때까지 기다린다.
- 데이터 전송이 완료되면 제어를 반환한다.
-
Blocking receive
- 메시지를 받는 프로세스는 메시지를 받을 때까지 기다린다.
- 데이터 수신이 완료되면 제어를 반환한다.
-
Non-blocking send
- 데이터를 전송한 후 즉시 제어를 반환한다. 데이터가 완전히 수신되지 않아도 다른 작업을 수행한다.
- 송신 버퍼의 크기가 충분한지 확인하지 않는다.
-
Non-blocking receive
- 데이터를 수신하지 않았어도 제어를 반환한다. 데이터가 완전히 송신되지 않아도 다른 작업을 수행한다.
비차단적 수신은 우편함과 비슷합니다. 가정해 봅시다. 당신은 집에 도착한 우편물을 확인하려고 우편함을 열고 있습니다. 만약 우편물이 이미 도착했다면, 당신은 바로 확인하고 처리할 수 있습니다. 그런데 만약 아직 아무것도 도착하지 않았다면, 당신은 우편함을 계속 열고 다른 작업을 처리할 수 있습니다. 그리고 나중에 우편물이 도착하면 우편함을 다시 확인합니다. - GPT-3.5
- 데이터를 수신하지 않았어도 제어를 반환한다. 데이터가 완전히 송신되지 않아도 다른 작업을 수행한다.