프로세스간 통신(IPC)1
독립적인 A, B 프로세스가 존재 이때 두 프로세스가 데이터를 주고 받으면 이걸 프로세스간 통신이라한다.
독립된 프로세스라고 하면 독립된 프로그램이라고 생각하기 쉽다.
그런데 우리가 일반적으로 말하는 프로그램의 개념을 다시 정리해보자.
하나의 프로세스가 하나의 프로그램이다 라는 일반적인 생각이 있다.
이러한 생각은 대부분의 경우에 해당하지만 반드시 해당하지는 않는다.
실제로는 하나의 프로그램은 여러개의 프로세스로 구성이 되는 경우도 있다.
하나의 프로그램이 여러개의 프로세스로 구성되어있다고 생각해보자
1,2,3 번 프로세스가 하나의 프로그램을 구성한다면 1,2,3번 프로세스간의 통신이 이루어져야 하나의 프로그램으로서 구성이 될꺼다.
IPC(Inter-Process Communication)
프로세스간 통신
- 프로세스간 데이터 송 수신 ⇒ 메모리 공유
- 프로세스간 통신이라고 하면 하나의 프로세스가 데이터를 송신하고 다른 프로세스가 수신하는 흐름을 보통 떠올린다. 이러한 이해는 본질적인 이해와는 조금 다르다.
- 좀 더 깊이 생각을 해보면 프로세스간 통신은 메모리 공유라는 결론을 내릴 수 있다.
- A, B 두 프로세스와 공유되어지는 메모리 공간이 있다고 가정하자
- A가 메모리 공간에 데이터를 가져다 놓고 B는 메모리 공간의 데이터를 가져간다.
- 결론적으로 통신이 이루어진거다. 즉 내부적인 메커니즘은 메모리 공유다.
프로세스 A와 프로세스 B가 현재 실행 중에 있다면 위와 같은 형태의 메모리 구조가 형성될 것이다.
프로세스들은 자신에게 할당된 메모리 공간 이외에는 접근이 불가능하다.
프로세스 A의 메모리 영역과 프로세스 B의 메모리 영역은 완전히 분리되어 있다.
운영체제가 위와 같이 프로세스간 영역을 분리한다.
만약 프로세스 영역에 다른 프로세스가 접근이 가능한 영역을 만들어 버리면 실행중인 하나의 프로그램이 다른 프로그램에 영향을 미치는 문제점을 만들 수 있다. 그래서 안전성 문제로 프로세스를 분리했다.
메일 슬롯 방식의 IPC
우체통의 입구를 가리켜 메일 슬롯이라고 한다. 통신의 이름으로 메일 슬롯을 붙였다.
동작원리
- sender, receiver 프로세스가 존재
- receiver 프로세스는 데이터를 받을 수 있는 공간인 우체통을 만들어야 한다. receiver 프로세스는 OS에 우체통을 만들어 달라고 요청을 하고 OS는 우체통을 만든다. 이 공간에는 주소가 부여된다.
- sender 프로세스는 저 공간으로 데이터를 보내기 위해서는 해당 공간의 주소를 알아야 한다.
- sender 프로세스가 주소를 알고 있다고 하면 보낼 데이터에 주소를 붙인다. 그러면 OS는 해당 데이터를 우체통에 집어넣어준다.
- receiver 프로세스는 우체통에 데이터가 있는지 없는지 체크하고 있으면 데이터를 가져간다.
- 현재 두 프로세스는 메모리를(우체통) 공유하고 있다.
- 메일슬롯 방식은 단방향이다. receiver 프로세스가 본인의 우체통에 데이터를 넣는다고 해서 sender 프로세스가 해당 데이터를 가져가는게 아니다. 즉 메일슬롯 방식에서 sender 프로세스는 데이터를 보낼 수만 있고 받을 수 없다.
- sender 프로세스는 데이터를 여러개 보낼 수 있다.
- receiver 프로세스들의 우체통의 주소가 같을 수가 있다.
- sender 프로세스는 주소가 같은 receiver 프로세스들에게 데이터를 보낸다고 해보자
- sender 프로세스는 데이터를 한번에 보낼때 각각의 프로세스들의 우체통에 데이터를 전송할 수 있다. 브로드캐스팅이 가능하다.
프로그래밍 모델
- receiver
- 1. 우체통을 생성한다.
- 4. 수신한다.
- sender
- 2. receiver가 우체통을 만들면 해당 주소로 연결을 한다.
- 3. 전송한다.
반대로 receiver가 전송하고 sender가 수신할 수 없다. 단방향임을 주의하자.
- CreateFile
- 파일을 생성하거나 생성된 파일을 오픈할때 사용하는 함수이다.
- WriteFile
- 파일에다 데이터를 쓰는 함수다.
- ReadFile
- 파일에 있는 데이터를 읽어들이는 함수다.
왜 sender와 receiver는 데이터를 주고 받을때 파일과 관련된 함수를 쓰는걸까?
A 프로세스가 파일에 데이터를 집어 넣는다. 그러면 B 프로세스는 해당 파일을 열어서 읽어들일 수 있다.
즉 통신이 이루어진거다. IPC 기법을 쓰지 않고 파일만 이용해도 통신이 가능하다.
물론 문제점이 많지만 기본적인 통신이 가능하다라는 의미다. 즉 파일만을 이용해서 메모리 공유가 가능하다.
메일 슬롯은 메모리 공유다. 메일 슬롯 같은 경우 실제로 파일 시스템을 기반으로 이루어져 있지만 위 처럼 단순한 방식으로 이루어져 있지는 않다. 위 방식의 문제점을 보완해서 만든게 메일슬롯이다. 대부분의 OS에서는 메일슬롯을 파일 기반으로 처리한다. 그러다보니 파일 관련된 함수를 이용해서 얼마든지 접근이 가능하다.
코드 예시
MailSender.cpp
#include "stdio.h"
#include "tchar.h"
#include "windows.h"
#define SLOT_NAME _T("\\\\\\\\.\\\\mailslot\\\\mailbox")
int _tmain(int argc, TCHAR * argv[])
{
HANDLE hMailSlot;
TCHAR message[50];
DWORD byteWritten; // number of bytes write
hMailSlot = CreateFile(
SLOT_NAME, // 메일 슬롯의 이름(주소)
GENERIC_WRITE, // 사용되는 용도
FILE_SHARE_READ,
NULL,
OPEN_EXISTING, // 생성 방식
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hMailSlot == INVALID_HANDLE_VALUE)
{
_fputts(_T("Unable to create mailslot! \\n") ,stdout);
return 1;
}
while (1)
{
_fputts(_T("MY CMD>"),stdout);
_fgetts(message , sizeof(message) / sizeof(TCHAR) , stdin);
if (!WriteFile(hMailSlot , message , _tcslen(message) * sizeof(TCHAR) , &byteWritten , NULL)){
_fputts(_T("Unable to write!") , stdout);
CloseHandle(hMailSlot);
return 1;
}
if (!_tcscmp(message, _T("exit")))
{
_fputts(_T("Good Bye!"), stdout);
break;
}
}
CloseHandle(hMailSlot);
return 0;
}
- CreateFile
- 파일을 생성하는 메소드이지만 여기서는 연결하는 용도로 사용됨
- SLOT_NAME, // 메일 슬롯의 이름(주소)
- GENERIC_WRITE, // 사용되는 용도
- 쓰기여야 한다.
MailReceiver.cpp
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#define SLOT_NAME _T("\\\\\\\\.\\\\mailslot\\\\mailbox")
int _tmain(int argc, LPTSTR argv[])
{
HANDLE hMailSlot; //mailslot 핸들
TCHAR messageBox[50];
DWORD bytesRead; // number of bytes read
/* mailslot 생성*/
hMailSlot=CreateMailslot(SLOT_NAME, 0, MAILSLOT_WAIT_FOREVER, NULL);
if(hMailSlot==INVALID_HANDLE_VALUE)
{
_fputts(_T("Unable to create mailslot!\\n"), stdout);
return 1;
}
/* Message 수신*/
_fputts(_T("******** Message ********\\n"), stdout);
while(1)
{
if(!ReadFile(hMailSlot, messageBox, sizeof(TCHAR)*50, &bytesRead, NULL))
{
_fputts(_T("Unable to read!"), stdout);
CloseHandle(hMailSlot);
return 1;
}
if(!_tcsncmp(messageBox, _T("exit"), 4))
{
_fputts(_T("Good Bye!"), stdout);
break;
}
messageBox[bytesRead/sizeof(TCHAR)]=0; //NULL 문자 삽입
_fputts(messageBox, stdout);
}
CloseHandle(hMailSlot);
return 0;
}
- CreateMailslot() 을 통해 우체통을 생성
- 첫번째 인자인 SLOT_NAME은 주소를 의미한다.
- 두번째 인자는 버퍼 크기를 의미한다. 우체통 크기라고 보면 된다.
- 세번째 인자는 ReadFile 함수 특성이다.
- 네번째 인자는 보안 설정이다.
Signaled vs Non-Signaled
커널 오브젝트의 상태(State) 에 대한 이해
- 상태: 리소스의 현재 상황을 알리기 위함
커널 오브젝트의 두 가지 상태(State)
- Signaled 상태(신호를 받은 상태)
- Non-Signaled 상태 (신호를 받지 않은 상태)
커널 오브젝트의 상태 정보는 커널 오브젝트를 구성하는 멤버 변수 중 하나에 저장되어 있다.
변수에 저장되는 값으로는 true, false가 있다. true일 경우 Signaled 상태이고 false일 경우 Non-Signaled 상태이다.
상태정보를 나타내는 멤버가 Signaled 상태가 될 수 있고 Non-Signaled 상태가 될 수 있다는 사실이 중요하다.
커널 오브젝트의 상태가 나타내는 의미도 리소스 별로 다르다. 리소스의 상태를 나타내는 커널 오브젝트의 상태가 어떠한 의미를 지니는지 리소스 별로 공부 해야 하는데 이 책에서는 프로세스를 기준으로 커널 오브젝트의 상태가 뭔지 이야기 한다.
상태가 변경되는 시점은 언제일까?
- 부모 프로세스가 자식 프로세스를 생성 했다고 가정하자.
자식 프로세스의 커널 오브젝트가 생성이 되고 핸들 값이 부모 프로세스에게 반환된다.
커널 오브젝트는 생성과 동시에 Non-Signaled 상태 (신호를 받지 않은 상태)가 된다.
Non-Signaled 상태는 프로세스가 현재 실행 중임을 의미한다.
- 프로세스가 종료가 됐다고 해보자
Non-Signaled 상태에서 Signaled 상태(신호를 받은 상태)로 바뀐다.
커널 오브젝트의 상태는 왜 존재 할까?
커널 오브젝트의 상태라는건 현재 상황(상태)을 알려주기 위함이다.
예를 들어 위 이미지를 보면 A 프로세스가 현재 Running 상태여서 A 프로세스 커널 오브젝트의 상태는 Non-Signaled 상태이고 이러 정보가 커널 오브젝트에 존재한다. 따라서 다른 프로세스가 A 프로세스의 커널 오브젝트를 참조 했을때 A 프로세스의 상태를 알 수 있다. 프로세스가 종료되면 Signaled 상태로 바뀌게 되고 다른 프로세스가 A 프로세스의 상태를 알 수 있다. 정리하자면 프로세스 커널 오브젝트는 프로세스 실행 중에는 Non-Signaled 상태에 놓인다. 그러다가 프로세스가 종료되면 Windows 운영체제에 의해서 자동적으로 Signaled 상태가 된다.
상태관찰 시나리오
커널 오브젝트의 상태를 관찰하기 위한 시나리오다.
모든 리소스의 상태를 관찰하는 방법은 아래처럼 진행한다.
- 부모 프로세스가 자식 프로세스를 생성함
- 자식 프로세스의 핸들값이 부모 프로세스에게 반환 됨
- 부모 프로세스가 자식 프로세스가 현재 실행 중인지 종료중인지 알고 싶을때 호출 하는 함수는 WaitForSingleObject 이다.
WaitForSingleObject
커널 오브젝트의 두 가지 상태를 확인하는 용도의 함수
7이라는 숫자가 의미하는 해당 커널 오브젝트의 상태가 Signaled 상태인지 Non-Signaled 상태인지를 물어보는게 WaitForSingleObject() 함수다. Signaled 상태일 경우 반환하고 Non-Signaled 상태인 경우 바로 반환하지 않고 blocking 상태가 된다. 즉 함수가 빠져나오지 않고 멈춰있는 상태가 될 수 있고 바로 빠져 나올 수 있다.
WaitForSingleObject 함수는 Signaled 상태인지 Non-Signaled 상태인지 구분하는 함수로 이해하지 말고 해당 커널 오브젝트가 Signaled 상태가 되기를 기다리는 함수라고 이해하자.
예를 들어 7이라는 숫자를 인자로 전달하면서 WaitForSingleObject() 메서드를 호출한다. 그럼 7에 해당하는 커널 오브젝트가 Signaled 가 되기만을 기다리는 함수여서 Signaled 상태가 되면 WaitForSingleObject() 메서드는 바로 빠져나오고 Signaled상태가 아니면 매개변수를 어떻게 전해주냐에 따라서 내용이 달라지지만 blocking 상태이다(멈춰있다.) Signaled상태에서 Non-Signaled상태가 되면 빠져나온다.
부모 프로세스는 2개의 자식 프로세스가 연산을 끝내고 끝낸 결과를 리턴하고 종료할때까지 연산을 진행 시키면 안된다.
근데 자식 프로세스를 생성하고 리턴값을 받기도 전에 부모 프로세스가 종료가 될 수 있다.
그래서 자식 프로세스가 종료될때까지 기다리는 작업이 필요하다.
부모 프로세스는 자식 프로세스가 종료 하기를 기다려야한다는 말의 의미는 각 프로세스의 커널 오브젝트 상태가 Signaled 상태가 되기를 기다린다 와 같다. 따라서 자식 프로세스가 종료된다는거는 Signaled 상태라는거고 그래서 WaitForSingleObject 함수를 호출해서 자식 프로세스의 커널 오브젝트의 상태가 Signaled 상태가 되기를 기다린다.
'운영체제 > 뇌를 자극하는 윈도우즈 시스템 프로그래밍' 카테고리의 다른 글
뇌를 자극하는 윈도우즈 시스템 프로그래밍 9장 (0) | 2024.09.07 |
---|---|
뇌를 자극하는 윈도우즈 시스템 프로그래밍 8장 (0) | 2024.09.05 |
뇌를 자극하는 윈도우즈 시스템 프로그래밍 6장 (0) | 2024.09.05 |
뇌를 자극하는 윈도우즈 시스템 프로그래밍 5장 (0) | 2024.05.04 |
뇌를 자극하는 윈도우즈 시스템 프로그래밍 4장 (0) | 2024.05.04 |