본문 바로가기
C/[서적] 뇌를 자극하는 윈도우즈 시프

Chapter 07. 프로세스 간 통신(IPC) 1

by GameStudy 2022. 2. 4.

7.1 프로세스간 통신(IPC)의 의미와 메일슬롯 방식의 IPC

 

7.1-1 Inter-Process Communication(IPC)

  Def) 프로세스간 통신

    프로세스간의 데이터 송수신. 메모리 공유.

    프로세스 A와 프로세스 B가 서로 데이터를

    주고 받는 것을 프로세스 간의 통신이라 함.

    혹은 둘 이상의 프로세스 간의 데이터 송수신을 뜻함.

 

  Note) 독립된 프로세스간의 통신이지, 프로그램 간의 통신이 아님.

    "하나의 프로그램 == 하나의 프로세스"가 대부분 이긴 하지만,

    하나의 프로그램이 여러 프로세스를 생성하기도 함,

    ex. server-side 프로그램, ...

    이때 하나의 프로그램 속 여러 프로세스 간에는 통신이 가능해야지만

    적절한 하나의 프로그램으로 동작할 것이라는 건 추측가능함.

    따라서 프로세스간의 통신은 아주 중요한 개념임에 틀림없음.

 

  Note) 그러나, 프로세스 A가 Sender가 되고 프로세스 B가 Reciever가 되어서

    프로세스 A가 보낸 데이터가 순차적으로 프로세스 B에 저장되는 

    어떤 스트림의 개념이 아님. 

    그보다는 공유되어지는 메모리 공간을 만들어 둔 후에,

    프로세스 A가 해당 공간에 데이터를 가져다 둠.

    그럼 프로세스 B가 해당 공간에서 데이터를 가져감.

    즉, 내부적인 메커니즘이 스트림 보다는 메모리 공유에 가까움.

 

  Note) 프로세스별 메모리 영역(Virtual Memory 영역임.)

    근데 위와 같이 메모리 공유를 하더라도 통신은 어려움.

    프로세스별로 아래 그림과 같이 완전히 분리되어 있기 때문.

    즉, 이렇게 분리해서 OS, 혹은 커널이 관리하게 됨.

    따라서 프로세스 A는 다른 메모리 영역에 접근할 수 없고

    프로세스 B도 마찬가지. 그래서 둘 사이에 어떤 공유 메모리 공간을 만들순 없음.

    예로들어 프로세스 A가 data 영역에 일부분을 다른 프로세스가 접근 가능하게 하고

    이 부분에 데이터를 두면 프로세스 B가 가져가는 식이 안된다는 것. 본질적으로 분리되어 잇기 때문.

출처. 윤성우, "뇌를 자극하는 윈도우즈 시스템 프로그래밍"

 

  Note) 근데 왜 분리해 둔걸까? 

    앞서 언급했듯이, 대부분의 경우에는 "1 Program == 1 Process"임.

    만약 다른 프로세스의 메모리 영역에 접근하게 뒀다면

    다른 프로세스의 실행에 영향을 끼칠 수 있게 한다는 뜻과 마찬가지임.

    결론은 안전성 때문에 OS가 분리해 둔것임.

 

  Note) OS가 분리해 뒀으니, 공유 가능한 메모리 공간도 OS가 제공하면 될듯.

    이런 통신 기법을 IPC 기법이라 함. 우리는 Windows OS에서 제공하는

    3가지 기법에 대해 배울 예정.

 

  Note) 첫 번째 통신 기법. 메일 슬롯

    우체통의 입구를 메일 슬롯이라 함.

 

7.1-2 메일슬롯

  Note) 메일 슬롯의 동작 원리

    - Sender Process: 데이터를 주고 싶은 프로세스

    - Reciever Process: 데이터를 받고 싶은 프로세스

    - 리시버 프로세스가 우체통을 만들어야 함.

      즉, 내가 우체통을 통해서 데이터를 받겠다고 OS에게 선언.

      그럼 이 메모리 영역은 리시버 프로세스가 접근 가능한 영역이 됨.

      물론 함수 호출을 통해서 간접 접근임. 직접 접근이 아님.

      또한, 이 메모리 영역에는 주소가 부여됨. 

      어떤 프로세스던지 간에 주소를 알면 접근 가능하게끔 함.

    - 즉, 센더 프로세스는 메모리 영역의 주소를 알고 있어야 함.

      그리고, 데이터에다가 메모리 영역의 주소를 붙혀서 OS에게 전달

      그럼 OS는 해당 메모리 영역 주소(우체통 주소)에 데이터를 넣어줌.

    - 그럼 리시버 프로세스는 우체통에 데이터가 있는지 없는지 확인 후

      가져가기만 하면 됨.

    - 이제 이 메일 슬롯이 메모리 공유의 역할을 하는 것.

출처. 윤성우, "뇌를 자극하는 윈도우즈 시스템 프로그래밍"

 

  Note) 우체통이라는 예가 메일 슬롯과 아주 유사함.

    우리가 편지를 우체통에 넣는다고 해서 다른 사람에게 편지가 발송되진 않음.

    마찬가지로, 리시버 프로세스가 메일 슬롯에 데이터를 넣는다고해서

    샌더 프로세스에게 데이터가 전달되진 않음.

    즉, 단방향의 특성을 지님.

 

  Note) 마찬가지로 센더 프로세스는 여러 우체통에 통신할 수도 있음.

    근데 그 방식이 현실의 우체통과는 다르게, 

    같은 주소값으로 메일 슬롯을 만듦으로써 구현해냄.

    즉 센더 프로세서는 한 번의 센딩으로 여러 리시버 프로세스에게

    보낼 수 있는 것. 이걸 브로드 캐스팅이라고 함.

 

  Note) 프로그래밍 모델

    현실에서 우체통은 우체부가 배달해 주지만,

    프로그래밍에서는 그렇지 않음. 연결고리를 만들어야 함.

출처. 윤성우, "뇌를 자극하는 윈도우즈 시스템 프로그래밍"

 

  Note) 근데, 시스템 함수명이 대부분 파일 관련 함수랑 비슷함.

    이건 다분히 내부 구현사항을 오픈하겠다는 뜻이 내포되어 있음.

    사실, 프로세스간 통신은 메모리 공유라 하였는데 지금도 가능하긴 함.

    a.txt라는 파일을 A 프로세스가 만들고, B 프로세스는 a.txt의 경로만 알면

    열어서 읽어볼 수 있음. 즉, 통신이 이뤄짐. 우리가 IPC 기법을 쓰지 않아도

    불안정하지만 통신이 가능함. 메일 슬롯도 마찬가지로 파일 시스템을 기반으로

    구현되어 있음. 다만, 우리가 생각한 단순한 방법은 아님. 안전성에 문제가 생김.

    이런 부분들을 잘 개선한 방법이 메일 슬롯 방식.

 

  Ex7-2,7-3)

<hide/>

// 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;
}
<hide/>

// 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;
}

 

7.2 Signaled Vs. Non-Signaled

 

7.2-1 커널 오브젝트의 상태

  Note) IPC와 별개의 내용이지만, 아주 중요한 단원.

 

  Note) 커널 오브젝트의 상태란,

    커널이 리소스를 관리하기 위해서 필요한 데이터 블록이 커널 오브젝트.

    여기에는 boolean(혹은 다른 자료형)으로 리소스의 현재 상태를

    다른 리소스에게 알리기 위해서 상태 정보가 포함되어 있음.

    이게 true가 되면 signaled 상태, false가 되면 non-signaled 상태.

출처. 윤성우, "뇌를 자극하는 윈도우즈 시스템 프로그래밍"

 

  Note) 왜 커널 오브젝트의 상태가 중요하고, 언제 변경되는지가 중요함.

    리소스를 생성하면 해당 리소스의 커널 오브젝트가 생성됨.

    그럼 이 커널 오브젝트의 핸들값이 반환됨.

    그러나 이 핸들값을 얻는 구체적인 방법은 모두 다름

    ex. 메일 슬롯은 만드는 함수의 반환값, 자식 프로세스는 인자로 넘긴 구조체의 멤버.

    지금은 프로세스를 기준으로 핸들값을 얻고 커널오브젝트의 상태에 대해 배워보려함.

 

  Def) Signaled와 Non-signaled

    Signaled는 프로세스가 종료될 때,

    Non-signaled는 프로세스가 실행 중인 상태라고 약속함.

    이를 통해서 해당 프로세스의 커널 오브젝트 핸들값을 

    가지고 있는 프로세스가 해당 프로세스의 현재 상태를

    알 수 있게끔 하기 위해서임.

 

  Note) Non-signaled에서 Signaled로는 변경이 안됨.

 

  Note) 그럼 커널 오브젝트의 상태를 접근하는 방법은 뭘까?

 

7.2-2 상태 관찰 시나리오

  Note) 상태 관찰 시나리오 도식화

    1. 부모 프로세스가 자식 프로세스를 생성함

      생성과 동시에 자식 프로세스의 커널 오브젝트가 생성됨.

      그럼 부모 프로세스에게는 자식 프로세스의 커널 오브젝트 

      핸들값이 반환되어 짐. 이 예에서는 7

    2. 부모 프로세스는 자식 프로세스의 현태 상태가 궁금함.

      그럼 WaitForSingleObject() 함수를 호출함.

      이 함수에 커널 오브젝트 핸들값을 넘겨주면

      해당 프로세스의 커널 오브젝트 상태 멤버의 값을 반환함.

    3. 근데 조금 특이하게도 해당 프로세스가

      Signaled 상태라면 바로 상태 멤버의 값을 반환함.

      Non-signaled 상태라면 해당 프로세스가 종료될 때까지 블로킹.

      즉, WaitForSingleObject() 함수를 현재 상태 멤버를 얻어온다고

      기억하기 보다는, 해당프로세스가 Signaled 상태가 되길

      기다리는 함수라고 기억하자.

출처. 윤성우, "뇌를 자극하는 윈도우즈 시스템 프로그래밍"

 

7.2-3 예제 모델

  Note) 예제 모델 도식화

    1. 하나의 프로그램 내에 3개의 프로세스를 만들고픔.

      1부터 1억 사이의  값을 0.00001 단위로 합 하고자 함.

      B 프로세스에게는 1부터 0.5억, C프로세스에게는 0.6억부터 1억

      혹은 cpu가 다중 코어라면 더 많은 프로세스가 쪼개서 작업 가능.

    2. 부모 프로세스가 2 개의 자식 프로세스를 생성함

      그리고 각각의 프로세스에게 작업량의 절반씩 나눠줌.

    3. 연산의 결과는 자식 프로세스에게 종료코드를 동해 

      부모 프로세스에게 알려주게끔 코드를 짯음.

    5. 따라서 부모 프로세스는 자식 프로세스에게 연산의

      결과를 받아야지만 진행 가능해짐.

      즉, 자식 프로세스의 연산결과를 받기 위해서 WaitForSingleObj() 함수를 씀.

출처. 윤성우, "뇌를 자극하는 윈도우즈 시스템 프로그래밍"

 

댓글