프로세스 간 통신
IPC가 필요한 이유
각 프로세스는 OS에 의해 완전히 독립된 메모리를 공간을 가지고, 다른 메모리 영역에 접근할 수 없도록 막혀 있다. 만약 프로세스가 다른 메모리 영역에 접근하는 것을 허용한다면, 대부분 하나의 프로그램은 하나의 프로세스를 두기 때문에 어떤 프로그램이 다른 프로그램에 영향을 미치는 문제가 발생할 수 있다. 이는 안정성의 문제를 초래한다.
이에 대한 해결책으로 OS는 IPC를 제공하며 이는 운영체제 별로 다르다. 윈도우즈에는 대표적인 세 가지 IPC가 있다.

우체통의 역할
단방향. 따라서 각 프로세스는 자신의 역할에 따라 데이터를 보내거나, 받는 것만 가능하다.
Sender 프로세스는 여러 곳에 데이터를 보낼 수 있다.
브로드 캐스팅
1) Receiver 프로세스가 데이터 수신 목적의 우체통을 생성하고 주소 부여. 이는 Receiver 프로세스가 접근가능한 영역. 어떤 프로세스건 해당 주소를 알면 이 영역에 접근할 수 있다.
2) Sender 프로세스는 이 우체통에 데이터를 보내기 위해 주소를 알아야 한다.
3) Receiver 프로세스는 우체통을 확인해서 데이터가 있으면 가져감.
4) 해당 메일 슬롯은 Receiver 프로세스와 Sender 프로세스가 공유하는 영역. 즉 메모리 공유


CreateFile(): Receiver 프로세스가 만들어둔 우체통과 연결하는 함수. 파일을 생성하거나, 생성된 파일은 열 때 사용하는 함수
WriteFile(): 파일에 데이터를 쓰는 함수
ReadFile(): 파일에서 데이터를 읽어 들이는 함수
파일을 이용하는 것 또한 메모리 공유를 이용한 IPC 통신. 이처럼 데이터를 읽고 쓰는 기능이 파일을 읽고 쓰는 과정과 내부 구현이 유사하기 때문에 시스템 함수명 또한 파일 시스템 관련 함수와 비슷하게 둔 것. 그러나 단순히 파일을 이용한 통신은 불안정. 이런 점이 개선된 방법이 메일 슬롯 방식이다.
// MailSlotReciever.cpp
#include <stdio.h>
#include <windows.h>
#include <tchar.h>
#define SLOT_NAME _T("\\\\.\\mailslot\\mailbox")
// 위 경로상에 .은 현재 컴퓨터 이름으로 치환됨. 이건 로컬 상의 통신.
// . 대신해서 네트워크 상으로 연결된 다른 컴퓨터의 이름을 적을 수도 있음.
// 그럼 완전히 독립된 두 컴퓨터 상의 프로세스 간 통신도 가능함.
// 근데 잘 안씀. 소켓을 이용한 TCP/IP 통신 기법을 씀.
int _tmain(int argc, LPTSTR argv[])
{
HANDLE hMailSlot; // mailslot 핸들
TCHAR messageBox[50];
LPDWORD bytesRead;
// 1. 메일슬롯 생성(우체통 생성)
hMailSlot = CreateMailslot(
SLOT_NAME, // 메일 슬롯 명
0, // 메일 슬롯 버퍼 크기. 0은 허용최대치
MAILSLOT_WAIT_FOREVER, // ReadFile() 함수 특성. 이 특성은 데이터가 들어올 때까지 기다림.
NULL // 보안설정
);
// MAILSLOT_WAIT_FOREVER 특성의 ReadFile() 함수는 데이터가 들어올 때까지 Blocking 상태가 됨.
ReadFile(
hMailSlot, // 메일 슬롯 지정
messageBox, // 데이터 수신버퍼(배열지정)
sizeof(TCHAR)*50, // 읽어 들일 데이터 최대크기
bytesRead, // 읽어 들인 데이터 실제크기를 얻기 위한 변수
NULL
);
CloseHandle(hMailSlot);
return 0;
}
// MailSlotSender.cpp
#include <stdio.h>
#include <windows.h>
#include <tchar.h>
#define SLOT_NAME _T("\\\\.\\mailslot\\mailbox")
int _tmain(int argc, LPTSTR argv[])
{
HANDLE hMailSlot; // mailslot 핸들
TCHAR messageBox[50] = _T("Hello, MailSlot!");
LPDWORD lpTransmitResult;
// 2. 연결작업
hMailSlot = CreateFile(
SLOT_NAME, // 메일 슬롯 명
GENERIC_WRITE, // 메일 슬롯 용도. 샌더이기에, write 밖에 안됨.
FILE_SHARE_READ,
NULL,
OPEN_EXISTING, // 생성방식. 샌더이기에 기존에 만들어져 있을 메일 슬롯을 여는 것.
FILE_ATTRIBUTE_NORMAL,
NULL
);
WriteFile(
hMailSlot, // 메일 슬롯 지정
messageBox, // 전송 데이터 버퍼
_tcslen(messageBox) * sizeof(TCHAR), // 전송 크기
lpTransmitResult, // 전송 결과
NULL
);
CloseHandle(hMailSlot);
return 0;
}
커널 오브젝트: 커널에 의해 관리되는 리소스 정보를 담고 있는 데이터 블록.
상태를 반영하기 위한 boolean타입 멤버 변수가 있을 것.
ex) true -> Signaled, false -> Non-Signaled
커널 오브젝트가 의미하는건 리소스 별로 다르기 때문에, 커널 오브젝트의 상태 또한 리소스에 따라 다르다.
1) 부모 프로세스가 자식 프로세스를 생성하면 커널 오브젝트가 생성됨. 커널 오브젝트는 Non-Signaled 상태 -> 즉, 프로세스가 현재 실행 중
2) 프로세스가 종료 -> 커널 오브젝트는 Signaled 상태로 바뀜.

상태가 바뀌는 시점
1) 부모 프로세스가 자식 프로세스를 생성. 핸들 값을 얻게 됨
2) WaitForSingleObject(): 해당 커널 오브젝트가 Signaled 상태가 되기를 기다리는 함수. 부모 프로세스는 현재 자식 프로세스가 종료됐는지, 실행 중인지 알고 싶을 때 이 함수의 반환 값을 통해 알 수 있다. 종료(Signaled)됐다면 바로 반환되고, 실행 중이라면(Non-Signaled) 블로킹된다.
블로킹: 함수를 빠져나오지 않고 잠시 멈춰있는 상태.
어떤 리소스가 Signaled 상태가 되기를 기다릴 때, 해당 리소스의 커널 오브젝트를 함수 인자로 전달하면 실행 중일 때 반환된다.

1) 하나의 프로그램 내에서 3개의 프로세스를 생성.
2) 부모 프로세스가 자식인 A, B 프로세스에게 1~10의 합 계산을 나눠서 요청하는 형태.
3) 계산이 종료된다면 자식 프로세스들은 종료코드를 반환.
4) 부모 프로세스는 자식들이 반환한 결과를 합해서 최종 결과를 도출.
이 때 부모 프로세스는 자식 프로세스들이 종료될 때까지 연산을 진행해서는 안 된다. 그러나 보통은 프로세스 생성하는데 연산이 많이 필요하기 때문에 부모 프로세스의 결과 도출 연산이 자식 프로세스가 종료되기도 전에 진행되어서 엉뚱한 결과가 나온다.
해결 방법
위와 같은 멀티 프로세스 구조의 사용 이유
