1. IPC란?

IPC는 Inter-Process Communication의 약자로,
둘 이상의 프로세스가 데이터를 주고받는 행위를 의미한다.

프로세스는 기본적으로 각자 독립된 메모리 공간을 가진다.
따라서 한 프로세스가 다른 프로세스의 메모리에 직접 접근할 수 없다.

그래서 프로세스 간 데이터를 주고받기 위해서는 별도의 통신 수단이 필요하다.


2. IPC가 필요한 이유

프로세스 간에 공유하는 메모리 영역이 있다면 데이터를 주고받기 쉽다.

하지만 일반적으로 프로세스는 독립된 주소 공간을 가지기 때문에, 서로의 메모리를 직접 공유하지 않는다.

따라서 운영체제가 제공하는 별도의 보조 수단을 이용해 데이터를 주고받아야 한다.

이러한 보조 수단들을 IPC 기법이라고 한다.


3. IPC 기법의 예시

대표적인 IPC 기법에는 다음과 같은 것들이 있다.

  1. 메일 슬롯
  2. 파이프
  3. 공유 메모리
  4. 메시지 큐
  5. 소켓
  6. RPC

이번 정리에서는 메일 슬롯과 파이프를 중심으로 본다.


메일 슬롯 기법

1. 메일 슬롯이란?

메일 슬롯은 프로세스 간 데이터를 전달하기 위한 IPC 기법 중 하나다.

개념적으로는 Sender와 Receiver가 함께 접근할 수 있는 우체통 같은 영역을 두는 방식이다.

Sender→ 메일 슬롯에 데이터 쓰기
Receiver→ 메일 슬롯에서 데이터 읽기

즉, Sender는 메일 슬롯에 데이터를 넣고, Receiver는 메일 슬롯에서 데이터를 꺼낸다.


2. Sender와 Receiver

메일 슬롯 통신에는 두 역할이 있다.

역할설명
Sender데이터를 보내는 프로세스
Receiver데이터를 받는 프로세스

Receiver는 먼저 메일 슬롯을 생성하고 준비한다.
Sender는 해당 메일 슬롯의 주소를 알고 있어야 데이터를 보낼 수 있다.


3. 메일 슬롯과 커널 오브젝트

메일 슬롯은 운영체제 커널에 의해 관리되는 리소스다.

따라서 메일 슬롯을 생성하면 내부적으로 커널 오브젝트가 생성되고, 해당 커널 오브젝트에 접근하기 위한 핸들이 반환된다. (생성 시 참조 카운트는 1)

즉, 메일 슬롯도 파일, 프로세스, 이벤트처럼 핸들을 통해 접근하는 커널 관리 대상이다.


4. 메일 슬롯 주소

Sender가 Receiver의 메일 슬롯에 데이터를 보내기 위해서는 메일 슬롯의 주소를 알아야 한다.

메일 슬롯 주소는 대략 다음과 같은 의미를 가진다.

어느 컴퓨터의 어느 메일 슬롯으로 데이터를 보낼 것인가

여기서 IP는 네트워크 상에서 대상 컴퓨터를 찾기 위한 주소 개념으로 이해할 수 있다.

즉, Sender는 Receiver가 만들어 둔 메일 슬롯의 위치를 알아야 한다.


5. 메일 슬롯은 파일 입출력 방식으로 사용된다

메일 슬롯은 Windows의 파일 시스템 기반으로 구현되어 있다.

그래서 메일 슬롯을 사용할 때도 기본적인 파일 입출력 함수 형태를 사용한다.

예를 들어 개념적으로는 다음과 같다.

CreateMailslot(...); // Receiver가 메일 슬롯 생성
CreateFile(...);     // Sender가 메일 슬롯 열기
WriteFile(...);      // Sender가 데이터 쓰기
ReadFile(...);       // Receiver가 데이터 읽기
CloseHandle(...);    // 핸들 닫기

즉, 메일 슬롯은 이름은 통신 기법이지만, 사용 방식은 파일 입출력과 유사하다.


6. 메일 슬롯의 동작 흐름

메일 슬롯의 기본 흐름은 다음과 같다.

1. Receiver가 메일 슬롯을 생성한다.
2. Receiver는 메일 슬롯에 데이터가 들어오기를 기다린다.
3. Sender는 메일 슬롯 주소를 이용해 메일 슬롯을 연다.
4. Sender는 메일 슬롯에 데이터를 쓴다.
5. Receiver는 메일 슬롯에서 데이터를 읽는다.

Receiver는 보통 데이터를 읽기 위해 대기한다.
데이터가 아직 없다면 읽기 작업에서 블록 상태로 대기할 수 있다.


7. Receiver가 먼저 실행되어야 하는 이유

메일 슬롯 방식에서는 Receiver가 먼저 메일 슬롯을 만들어야 한다.

Sender는 이미 존재하는 메일 슬롯 주소로 데이터를 보내기 때문이다.

따라서 Receiver가 아직 메일 슬롯을 생성하지 않았다면, Sender는 해당 메일 슬롯을 열거나 데이터를 보낼 수 없다.

올바른 순서
1. Receiver 실행
2. 메일 슬롯 생성
3. Sender 실행
4. 메일 슬롯에 데이터 전송

8. 메일 슬롯은 단방향 통신이다

메일 슬롯은 기본적으로 단방향 전달 방식이다.

즉, 하나의 메일 슬롯은 다음과 같은 흐름을 가진다.

Sender → Receiver

따라서 채팅 프로그램처럼 양방향 통신이 필요하다면 메일 슬롯을 두 개 만들어야 한다.

프로세스 A → 프로세스 B 메일 슬롯
프로세스 B → 프로세스 A 메일 슬롯

또는 처음부터 양방향 통신을 지원하는 다른 IPC 기법을 사용할 수도 있다.

대표적으로 Named Pipe를 사용할 수 있다.


9. 메일 슬롯의 브로드캐스팅

메일 슬롯은 브로드캐스팅 방식의 통신을 지원할 수 있다.

즉, 하나의 Sender가 여러 Receiver에게 동일한 메시지를 보낼 수 있다.

여러 Receiver가 동일한 이름의 메일 슬롯을 생성하고 있다면, Sender는 해당 주소로 메시지를 보내 여러 Receiver에게 전달할 수 있다.

Sender ├─ Receiver A 
	   ├─ Receiver B 
	   └─ Receiver C

이 특징 때문에 메일 슬롯은 여러 대상에게 간단한 메시지를 뿌리는 용도로 사용할 수 있다.

파이프 방식의 IPC

파이프 메커니즘

파이프는 프로세스 간 데이터를 주고받기 위한 IPC 기법 중 하나이다.

파이프 방식에는 크게 두 종류가 있다.

  • 이름 없는 파이프
  • 이름 있는 파이프

두 방식 모두 데이터를 스트림처럼 주고받을 수 있지만, 사용 목적과 프로세스 간 관계 조건이 다르다.


이름 없는 파이프

이름 없는 파이프는 단방향 통신을 제공하는 IPC 기법이다.

파이프를 생성하면 운영체제는 두 개의 핸들을 반환한다.

읽기 핸들쓰기 핸들

쓰기 핸들에 데이터를 쓰면, 읽기 핸들을 가진 쪽에서 데이터를 읽을 수 있다.

쓰기 핸들 -> 파이프 -> 읽기 핸들

이름 없는 파이프는 별도의 이름이나 주소를 가지지 않는다. 따라서 관계없는 프로세스가 임의로 접근할 수 없다.

결국 파이프 핸들을 공유할 수 있는 관계가 필요하다.

대표적으로 다음과 같은 관계에서 사용된다.

  • 부모 프로세스와 자식 프로세스
  • 같은 부모에게서 생성된 형제 프로세스
  • 핸들 상속이나 핸들 복제를 통해 파이프 핸들을 전달받은 프로세스

즉, 이름 없는 파이프는 핸들을 공유할 수 있는 관계 있는 프로세스 간 통신에 적합하다.

이름 없는 파이프-> 핸들 기반 통신-> 단방향 통신-> 주소 없음-> 관계 있는 프로세스 간 통신에 적합

이름 있는 파이프

이름 있는 파이프는 이름을 가진 파이프이다. 이름이 있다는 것은 곧 접근 가능한 주소가 있다는 의미이다.

따라서 메일슬롯처럼 관계없는 프로세스 사이에서도 통신할 수 있다.

다만 메일슬롯과는 차이가 있다. 메일슬롯은 단방향 통신과 브로드캐스트에 적합한 반면, 이름 있는 파이프는 양방향 통신을 지원할 수 있다.

대신 메일슬롯처럼 브로드캐스트 방식으로 데이터를 뿌리는 용도는 아니다.

이름 있는 파이프-> 이름 기반 통신-> 관계없는 프로세스 간 통신 가능-> 양방향 통신 가능-> 브로드캐스트 목적은 아님

세 IPC 방식 비교

구분메일슬롯이름 없는 파이프이름 있는 파이프
통신 방향단방향단방향양방향 가능
프로세스 관계관계없어도 가능관계가 필요함관계없어도 가능
접근 방식주소 기반핸들 기반이름 기반
브로드캐스트가능불가능불가능
대표 용도여러 프로세스에 메시지 전달부모-자식 간 데이터 전달서버-클라이언트 구조 통신

이름 없는 파이프

개념

이름 없는 파이프는 데이터를 한 방향으로 전달하기 위한 파이프이다. 파이프 생성 시 읽기 핸들과 쓰기 핸들이 만들어진다.

CreatePipe(&readHandle, &writeHandle, ...);

읽기 핸들은 데이터를 읽는 데 사용하고, 쓰기 핸들은 데이터를 쓰는 데 사용한다.

writeHandle -> 데이터 쓰기readHandle  -> 데이터 읽기

이름 없는 파이프가 관계 있는 프로세스에 적합한 이유

이름 없는 파이프에는 이름이나 주소가 없다. 따라서 다른 프로세스가 다음과 같이 찾아갈 수 없다.

"이 이름의 파이프에 연결하겠다"

대신 파이프를 사용할 프로세스가 읽기 핸들이나 쓰기 핸들을 직접 가지고 있어야 한다.

그래서 일반적으로 부모 프로세스가 파이프를 생성한 뒤, 자식 프로세스에게 필요한 핸들을 상속시킨다. 예를 들어 부모가 자식에게 쓰기 핸들을 넘겨주면 다음과 같은 구조가 된다.

자식 프로세스 writeHandle로 데이터 쓰기
부모 프로세스 readHandle로 데이터 읽기

이 구조에서는 자식 프로세스가 파이프의 이름을 아는 것이 아니라, 부모에게서 전달받은 핸들을 사용한다.

그래서 이름 없는 파이프는 부모-자식 관계처럼 핸들 전달이 가능한 프로세스 간 통신에 적합하다.


이름 있는 파이프

개념

이름 있는 파이프는 이름을 가진 파이프이다.

서버 프로세스가 특정 이름으로 파이프를 만들면, 클라이언트 프로세스는 그 이름을 이용해 파이프에 연결할 수 있다.

대표적인 파이프 이름 형태는 다음과 같다.

\\.\pipe\PipeName

이름이 있기 때문에 부모-자식 관계가 아니어도 접근할 수 있다.

즉, 이름 있는 파이프는 서버-클라이언트 구조의 IPC에 적합하다.


이름 있는 파이프의 흐름

서버 측

서버는 CreateNamedPipe 함수를 사용해서 이름 있는 파이프를 생성한다.

CreateNamedPipe(...)

그 후 ConnectNamedPipe 함수를 호출하여 클라이언트의 연결 요청을 기다린다.

ConnectNamedPipe(...)

흐름으로 보면 다음과 같다.

서버 프로세스 
1. CreateNamedPipe로 파이프 생성
2. ConnectNamedPipe로 클라이언트 연결 대기
3. 연결 후 ReadFile / WriteFile로 데이터 송수신

클라이언트 측

클라이언트는 서버가 만들어둔 이름 있는 파이프에 접근하기 위해 CreateFile을 호출한다.

CreateFile(...)

여기서 CreateFile은 일반 파일만 여는 함수가 아니다.

Windows에서는 파일, 장치, 파이프 같은 여러 커널 오브젝트를 파일처럼 다룰 수 있다.

따라서 클라이언트는 CreateFile을 통해 이름 있는 파이프에 연결할 수 있다.

클라이언트 프로세스 
1. CreateFile로 서버의 파이프 이름에 접근
2. 연결 성공 시 파이프 핸들 획득
3. ReadFile / WriteFile로 데이터 송수신

CreateFile이 파이프 연결에 사용되는 이유

이름 있는 파이프는 이름을 가진 커널 오브젝트이다.

클라이언트 입장에서는 이미 존재하는 파이프 이름에 접근해야 한다.

그래서 다음과 같은 이름을 대상으로 CreateFile을 호출한다.

\\.\pipe\PipeName

이때 CreateFile은 실제 디스크 파일을 새로 만드는 의미가 아니라, 해당 이름의 파이프를 열고 연결 요청을 수행하는 역할을 한다.

즉, 이름 있는 파이프에서의 CreateFile은 다음 의미에 가깝다.

서버가 만든 이름 있는 파이프에 연결하고,그 파이프를 사용할 수 있는 핸들을 얻는다.

파이프 관련 추가로 알면 좋은 것들

1. 파이프는 내부적으로 커널 버퍼를 사용한다

파이프는 프로세스끼리 직접 메모리를 공유하는 구조가 아니다.

중간에 운영체제가 관리하는 커널 버퍼가 있고, 한쪽 프로세스가 데이터를 쓰면 커널 버퍼에 저장된다. 다른 프로세스는 그 버퍼에서 데이터를 읽는다. 그래서 파이프는 공유 메모리보다 느릴 수 있지만, 동기화와 데이터 전달 구조가 비교적 단순하다.

2. 이름 있는 파이프는 방향 설정이 가능하다

이름 있는 파이프는 반드시 양방향만 가능한 것은 아니다.

CreateNamedPipe에서 파이프의 방향을 설정할 수 있다.

대표적으로 다음과 같은 방식이 있다.

PIPE_ACCESS_INBOUND   // 서버가 읽기 전용
PIPE_ACCESS_OUTBOUND  // 서버가 쓰기 전용
PIPE_ACCESS_DUPLEX    // 서버가 읽기/쓰기 모두 가능

즉, 이름 있는 파이프는 설계에 따라 단방향 또는 양방향으로 사용할 수 있다.

3. 이름 있는 파이프는 여러 클라이언트를 처리할 수 있다

이름 있는 파이프는 서버-클라이언트 구조에 적합하다. 다만 한 번 만든 파이프 인스턴스 하나가 모든 클라이언트를 동시에 처리하는 것은 아니다.

여러 클라이언트를 처리하려면 서버가 파이프 인스턴스를 여러 개 생성하거나, 연결이 끝난 뒤 다시 연결 대기 상태로 돌아가야 한다.

서버
CreateNamedPipe
ConnectNamedPipe

클라이언트 1 연결

추가 클라이언트 처리를 위해
새 파이프 인스턴스 생성 또는 재대기

면접에서는 이 부분에서 “이름 있는 파이프는 서버 구조로 확장할 수 있는가?” 같은 질문이 나올 수 있다.


4. 메시지 모드와 바이트 모드가 있다

이름 있는 파이프는 데이터를 다루는 방식에 따라 크게 두 가지 모드로 사용할 수 있다.

모드특징
바이트 모드데이터를 연속된 바이트 스트림으로 처리
메시지 모드쓰기 단위의 메시지 경계를 유지

바이트 모드는 TCP 스트림처럼 데이터가 쭉 이어지는 느낌이다.

메시지 모드는 한 번 보낸 메시지를 하나의 단위로 구분할 수 있다.

바이트 모드:"ABC" + "DEF" -> "ABCDEF"처럼 읽힐 수 있음
메시지 모드:"ABC", "DEF" -> 메시지 단위 구분 가능

이름 없는 파이프는 일반적으로 바이트 스트림 방식으로 이해하면 된다.


5. ReadFile은 데이터가 없으면 대기할 수 있다

파이프에서 읽기를 시도했는데 아직 데이터가 없으면, 동기 방식에서는 ReadFile이 대기 상태에 들어갈 수 있다.

즉, 다음 상황이 발생할 수 있다.

읽는 쪽: ReadFile 호출
쓰는 쪽: 아직 WriteFile 안 함
결과: 읽는 쪽이 블로킹될 수 있음

이것이 파이프 통신에서 동기화 문제와 연결된다. 잘못 설계하면 서로 읽기만 기다리다가 데드락처럼 멈춘 구조가 될 수 있다.


6. 파이프의 반대쪽 핸들이 닫히면 읽기 결과가 달라진다

파이프의 쓰기 핸들이 모두 닫힌 상태에서 읽기를 시도하면, 읽는 쪽은 더 이상 데이터가 오지 않는다는 것을 알 수 있다.

보통 이런 상황은 EOF처럼 볼 수 있다.

반대로 읽기 핸들이 닫힌 상태에서 쓰기를 시도하면 쓰기 작업이 실패할 수 있다. 그래서 파이프에서는 사용하지 않는 핸들을 적절히 닫는 것이 중요하다.

특히 부모-자식 프로세스 구조에서는 부모가 읽기만 할 것이라면 부모 쪽 쓰기 핸들을 닫고, 자식이 쓰기만 할 것이라면 자식 쪽 읽기 핸들을 닫아야 한다.


면접 대비 질문

  1. IPC란 무엇인가요?
  2. 프로세스 간 통신이 필요한 이유는 무엇인가요?
  3. 메일 슬롯이란 무엇인가요?
  4. 메일 슬롯 통신에서는 Sender와 Receiver 중 누가 먼저 준비되어야 하나요?
  5. 메일 슬롯은 왜 파일 입출력 함수와 비슷한 방식으로 사용되나요?
  6. 메일 슬롯으로 채팅 프로그램을 만들려면 어떤 구조가 필요한가요?
  7. 메일 슬롯에서 블로킹 상태란 무엇인가요?
  8. 메일 슬롯에서 브로드캐스팅이 가능하다는 말은 무슨 뜻인가요?
  9. 메일 슬롯은 어떤 상황에 적합한 IPC 기법인가요?
  10. 메일 슬롯 핸들을 닫지 않으면 어떤 문제가 생길 수 있나요? 사용이 끝났는데도 CloseHandle을 호출하지 않으면, 해당 프로세스의 핸들 테이블에 핸들이 계속 남아 있게 됨
    1. 커널 리소스가 불필요하게 유지됨
    2. 핸들 누수가 발생함
    3. 장시간 실행되는 프로그램에서는 리소스 부족으로 이어질 수 있음
    4. 커널 오브젝트의 참조 횟수가 줄지 않아 정리 시점이 늦어질 수 있음
  11. 메일 슬롯은 프로세스에 종속적인가요, 운영체제에 의해 관리되나요?
  12. 메일 슬롯 방식의 장점은 무엇인가요?
  13. 메일 슬롯 방식의 한계는 무엇인가요?
    • 기본적으로 단방향 통신이다.
    • 복잡한 요청-응답 구조에는 불편하다.
    • 대용량 데이터 전달에는 적합하지 않다.
    • 안정적인 양방향 통신이 필요하면 Named Pipe가 더 적합하다.
    • Receiver가 먼저 메일 슬롯을 생성해두어야 한다.
  14. 이름 없는 파이프와 이름 있는 파이프의 차이는 무엇인가요?
  15. 이름 없는 파이프가 관계 있는 프로세스 간 통신에 적합한 이유는 무엇인가요?
  16. 이름 있는 파이프는 왜 관계없는 프로세스 간에도 통신할 수 있나요?
  17. 이름 있는 파이프에서 서버와 클라이언트의 흐름을 설명해보세요.
  18. 이름 없는 파이프와 핸들 상속은 어떤 관계가 있나요?
  19. 이름 없는 파이프에서 부모가 자식에게 쓰기 핸들을 넘겨줬는데, 부모도 쓰기 핸들을 닫지 않으면 어떤 문제가 생기나요?