[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 08. 프로세스간 통신(IPC) 2

2023. 10. 3. 21:16·독서/[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ]

01. 핸들 테이블과 오브젝트 핸들의 상속

이번 장에서는 오브젝트 핸들의 상속과 핸들 테이블에 대한 개념을 이야기한다.

 

도입 배경

유능한 Windows 프로그래머가 되려면, 프로세스 핸들 테이블이 어떻게 관리되는지 이해하고 있어야 한다. 하지만, 마이크로소프트에서는 Windows 운영체제를 공개하지 않고 있으며, Windows운영체제의 종류 및 버전마다 핸들 테이블이 관리되는 방법에 다소 차이가 있기 때문에 정확한 방법은 알 수 없다. 따라서, 가장 일반적인 형태로 프로세스 핸들 테이블이 관리되는 방식을 알아보고자 한다.

 

프로세스의 커널 오브젝트 핸들 테이블

시스템 리소스의 생성 과정에서 커널 오브젝트가 생성되면, 생성된 커널 오브젝트를 가리킬 때 사용되는 핸들이 반환된다.

 

프로세스의 핸들 테이블 도입

아래의 그림에서는 프로세스로 전달되는 핸들 정보가 어떻게 전달 및 저장되는지 구체적으로 보여주고 있다. 이처럼 핸들 테이블은 핸들 정보를 저장하고 있는 테이블로서 프로세스별로 독립적이다. 즉, 각각의 프로세스가 자신만의 핸들 테이블을 하나씩 구성하고 관리한다.

 

핸들의 상속

CreateProcess 함수를 호출하면 새로운 자식 프로세스가 생성된다. 이때, 자식 프로세스를 위한 핸들 테이블도 함께 생성된다. 이때, CreateProcess 함수 호출 시 전달되는 인자(다섯 번째 인자)에 따라 부모 프로세스 핸들 테이블에 등록되어 있는 핸들 정보는 새롭게 생성되는 자식 프로세스에게 상속된다.

 

핸들의 상속에 대한 이해

자식 프로세스는 부모 프로세스의 핸들 테이블에 등록되어 있는 핸들 정보를 상속받을 수 있다. 하지만, 모든 핸들 정보를 상속받는 것은 아니다. 다음 그림에서 알 수 있다.

 

위처럼 핸들 테이블에는 해당 핸들의 상속 여부를 결정짓기 위한 요소가 존재한다. 또한, 이러한 요소는 상속될 때 상속 여부에 대한 정보도 변경 없이 그대로 상속된다. 이는 자식 프로세스가 또 다른 자식 프로세스를 생성할 경우에도 이 핸들에 대한 정보는 계속해서 상속된다.

이러한 상속 여부의 결정은 리소스를 생성하는 함수의 전달인자를 통해서 프로그래머가 결정할 수 있다.

 

핸들의 상속을 위한 전달인자

모든 자식 프로세스가 무조건 부모 프로세스의 핸들을 상속하는 것은 아니다. 다음은 앞에서 이미 소개되었던 CreateProcess의 선언이다.

 

BOOL CreateProcess(
    LPCSTR lpApplicationName,
    LPSTR lpCommandLine,
    LPSECURITY_ATTRIBUTES lpProcessAttributes,
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    BOOL bInheritHandles,
    DWORD dwCreationFlags,
    LPVOID lpEnvironment,
    LPCSTR lpCurrentDirectory,
    LPSTARTUPINFOA lpStartupInfo,
    LPPROCESS_INFORMATION lpProcessInformation
);
    //if the function fails, the return value is zero.

 

이 함수의 다섯 번째 전달인자 bInheritHandles는 자식 프로세스에게 핸들 테이블에 등록되어 있는 핸들 정보를 상속해 줄 것인지 결정하는 요소이다. 핸들의 상속 여부를 결정하는 것이 아니라, 부모 프로세스가 소유하고 있는 핸들 테이블 정보의 상속 여부를 결정하는 것이다.

 

핸들의 상속과 커널 오브젝트의 Usage Count

프로세스가 핸들을 얻게 되었다는 의미는 핸들 테이블에 해당 핸들에 대한 정보가 갱신(추가)되었음을 의미한다. 따라서 다음과 같은 그림을 이해할 수 있다.

 

 

위 그림에서 부모 프로세스의 핸들 테이블에만 핸들 정보가 등록되어 있는 관계로 UC의 값은 모두 1로 설정되어 있다.

다음으로, 부모 프로세스가 자식 프로세스를 생성하면서 핸들 테이블을 상속했다고 가정하면 다음과 같아진다.

 

 

위 그림을 보면, 자식 프로세스가 생성되면서 상속 여부가 Y인 핸들이 상속되었고, 따라서 일부 커널 오브젝트의 UC(Usage Count)가 증가한 것을 확인할 수 있다.

 

 

상속이 되기 위한 핸들의 조건

핸들의 상속 여부(핸들 테이블에서의 Y or N)는 리소스가 생성되는 순간에 프로그래머에 의해서 결정된다.

 

HANDLE CreateMailSlot(
	LPCTSTR lpName,
	DWORD nMaxMessageSize,
	DWORD lReadTimeout,
	LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

 

 

위 함수에서 네 번째 전달인자를 보면, LPSECURITY_ATTRIBUTES는 구조체 SECURITY_ATTRIBUTES의 포인터로 정의되어 있다. 이 전달인자에 NULL이 전달되면 여기서 생성되는 메일슬롯의 핸들은 상속되지 않는다. (N)

다음은 SECURITY_ATTRIBUTES 구조체의 선언을 보여준다.

 

typedef struct _SECURITY_ATTRIBUTES
{
	DWORD nLength;
	LPVOID lpSecurityDescriptor;
	BOOL bInheritHandle;
} 	SECURITY_ATTRIBUTES, * PSECURITY_ATTRIBUTES;

 

첫 번째 인자는 구조체 변수 크기를 바이트 단위로 설정한다.

두 번째 인자는 핸들의 상속 관점에서는 의미를 가지지 않기 때문에 생략한다.

세 번째 인자는 상속 여부를 결정짓는 요소이다.

 

CreateProcess 함수에서는 세 번째 전달 인자에서 위와 같은 구조체를 전달하여 핸들의 상속 여부를 결정하고, 다섯 번째 인자에서 부모 프로세스가 자식 프로세스에게 핸들 테이블을 상속할 것인지 결정한다.

 

메일슬롯 예제의 고찰

7장에서 MailSender1.cpp와 MailReceiver.cpp를 기반으로 핸들 상속을 다음과 같은 그림에서 확인할 수 있다.

 

그림의 왼쪽 부분에 있는 핸들 테이블을 보면, 핸들 127은 0x1200번지에 있는 커널 오브젝트를 가리키고 있는데, 이는 CreateMailSlot 함수에 의해 생성된 것이다. 더불어 이 함수의 호출에서 네 번째 인자를 보면, NULL을 전달하고 있기 때문에 핸들 상속 여부가 N으로 결정된 것을 알 수 있다.

또한, 오른쪽에 있는 Sender의 핸들 테이블을 보면, 핸들 127은 0x1700번지에 있는 커널 오브젝트를 가리키고 있는데, 이는 CreateFile 함수 호출에 의해 생성된 것이다. 더불어 이 함수가 호출될 때 상속을 위한 인자는 전달되지 않았기 때문에 핸들 상속 여부는 N이다.

 

코드를 수정하여 MailSender가 CreateProcess 함수 호출을 통해 자식 프로세스를 생성하도록 하였다. 이때, 핸들 정보를 상속할 수 있도록 인자를 설정하였다.

 

 

따라서, 위와 같은 결과를 확인할 수 있다. 또한, 핸들 정보를 자식 프로세스에게 상속하고 있지만 핸들 정보를 운영체제에서 관리하고 있기 때문에, 등록된 핸들 정보를 자식 프로세스는 확인할 방법이 없다. 따라서, 파일에 저장하여 자식 프로세스에게 넘겨주는 방식을 사용하고 있다.

 

Pseudo 핸들과 핸들의 중복(Duplicate)

현재 실행 중에 있는 프로세스 자신의 핸들을 얻는 방법으로 GetCurrentProcess 함수가 있었다. 실제로 히 함수를 통해 얻은 핸들을 이용해 프로세스 자신의 커널 오브젝트에 접근이 가능하다. 하지만, 이 함수 호출을 통해 얻은 핸들을 가짜 핸들(Pseudo 핸들)이라 한다. 왜냐하면 이는 핸들 테이블에 등록되어 있지 않은 핸들이고, 현재 실행 중인 프로세스를 참조하기 위한 용도로 정의해 놓은, 약속된 상수가 반환되는 것이기 때문이다.

 

만약, 현재 실행 중인 프로세스의 Real 핸들을 얻고 싶다면 DuplicateHandle 함수를 사용하면 된다.

 

BOOL DuplicateHandle(
    HANDLE hSourceProcessHandle,
    HANDLE hSourceHandle,
    HANDLE hTargetProcessHandle,
    LPHANDLE lpTargetHandle,
    DWORD dwDesiredAccess,
    BOOL bInheritHandle,
    DWORD dwOptions
);

 

이는 핸들을 복사하는 함수로, 핸들이 복사된 이후에는 당연히 Usage Count가 증가하기 때문에 복사된 핸들에 대해서도 CloseHandle을 진행해야 한다. 또한, GetCurrentProcess 함수 호출을 통해 얻은 핸들을 복사한다면(가짜 핸들을 복사한다면), 진짜 핸들이 생성되어 핸들 테이블에 등록된다.

 

부모 프로세스의 핸들을 자식 프로세스에게 전달하기

일반적으로 부모 프로세스가 자식 프로세스의 핸들을 소유하는 것이 보통이지만, 경우에 따라서 자식 프로세스가 부모 프로세스의 핸들을 얻어야 하는 경우가 있다. 이때, DuplicateHandle을 사용하여 부모 프로세스는 핸들 복사를 통해서 자신의 핸들을 테이블에 등록하여 자식 프로세스의 핸들 테이블에 상속하는 방식을 거쳐 전달하게 된다.


02. 파이프 방식의 IPC

다음으로, 프로세스간 통신기법 중 가장 대표적인 파이프에 대해서 설명하고자 한다.

 

메일슬롯에 대한 회고와 파이프의 이해

Windows의 파이프 매커니즘에는 “이름없는 파이프(Anonymous Pipe)”와 “이름있는 파이프(Named Pipe)의 두 가지 종류가 있다. 앞으로 이 둘을 각각 메일슬롯과 비교하여 설명하고자 한다.

 

메일슬롯은 서로 관련이 없는 프로세스들(네트워크로 연결되어 통신하는 프로세스들이나 부모 자식 간의 연관관계가 전혀 없는 프로세스들)사이에서 통신할 때 유용한 IPC 기법이다. 반면에, 이름없는 파이프는 관계가 있는(부모 자식 관계, 혹은 형제 관계)프로세스들 사이에서 통신하는 경우에 유용하다. 이때 형제 관계란 학문적으로 통용되는 용어는 아니지만, 하나의 부모 프로세스로부터 생성되는 자식 프로세스들 사이의 관계를 일컫는다.

이번에는 이름있는 파이프와 메일슬롯을 비교해 보자. 이름이 있다는 것은 주소 정보가 있다는 것으로, 메일슬롯처럼 서로 관계가 없는 프로세스들 사이에서도 주소 정보를 공유함으로써 데이터를 주고 받을 수 있다는 뜻이다.

실제로 이름있는 파이프는 메일슬롯과 마찬가지로 서로 관계가 없는 프로세스들 사이에서도 데이터를 주고 받을 수 있지만, 메일슬롯과 달리 양방향 통신이 가능하다. 따라서, 채팅 프로그램을 구현하고자 한다면, 이름있는 파이프가 용이하다.

반면에, 메일슬롯은 브로드캐스트 방식의 데이터 전송이 가능하여 회사 내의 전체 사원 공지와 같은 시스템을 구현하는 데에는 메일슬롯이 용이하다. 각각의 특성을 정리하자면 다음과 같다.

 

메일슬롯(MailSlot): 브로드캐스트(BroadCast) 방식의 단방향 통신방식을 취하며, 메일슬롯에 할당된 주소를 기반으로 통신하기 때문에 관계없는 프로세스들 사이에서도 통신이 가능하다.

 

이름없는 파이프(Anonymous Pipe): 단방향 통신을 취하며, 파이프를 통해서 생성된 핸들을 기반으로 통신하기 때문에 프로세스들 사이에는 관계가 있어야만 한다.

 

이름있는 파이프(Named Pipe): 메일슬롯과 유사하다. 차이가 있다면, 브로드캐스트 방식을 지원하지 않는 대신에 양방향 통신을 지원한다는 점이다.

 

이름없는 파이프(Anonymous Pipe)

이름없는 파이프는 우리가 아는 배수 파이프를 생각하면 이해하기 쉽다. 이 파이프를 이용해서 데이터를 한쪽 방향으로 전송할 수 있다. 다음은 이름없는 파이프를 생성하기 위한 함수이다.

 

BOOL CreatePipe(
    PHANDLE hReadPipe,
    PHANDLE hWritePipe,
    LPSECURITY_ATTRIBUTES lpPipeAttributes,
    DWORD nSize
);

 

파이프는 두 개의 끝을 가지는데, 첫 번째 인자는 데이터를 읽기 위한 파이프 끝에 해당하는 핸들이며, 두 번째 인자는 다른 한쪽 끝의 데이터를 쓰기 위한 핸들이다.

 

이름있는 파이프(Named Pipe)

이름있는 파이프의 핵심은 양방향 통신이다. 이는 CreateNamedPipe 함수를 통해 파이프를 생성하며, 이렇게 생성되는 파이프는 아직 제 역할을 하지 못한다.

파이프가 제 역할을 하기 위해서는 ConnectNamedPipe 함수가 호출되어야 하는데, 이 함수 호출에 의해서 파이프는 연결 요청을 기다리는 파이프로 상태 변경된다.

다음으로, 클라이언트가 연결 요청을 해야 할 차례이다. 클라이언트는 서버가 만들어 놓은 파이프에 연결하기 위해서 연결 요청을 위한 리소스를 생성하고 연결을 시도해야 하는데, CreateFile 함수가 이 모든 것을 처리한다. CreateFile 함수는 파일 생성 함수이다. 그러나 이는 파이프로 연결 요청을 하는 경우에도 사용된다.

앞서 소개한 함수들의 정의는 다음과 같다.

 

HANDLE CreateNamedPipe(
    LPCSTR lpName,
    DWORD dwOpenMode,
    DWORD dwPipeMode,
    DWORD nMaxInstances,
    DWORD nOutBufferSize,
    DWORD nInBufferSize,
    DWORD nDefaultTimeOut,
    LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

BOOL ConnectNamedPipe(
    HANDLE hNamedPipe,
    LPOVERLAPPED lpOverlapped
);

03. 프로세스 환경변수

앞에서 자식 프로세스에게 핸들 정보를 전달하기 위해 파일을 활용하였다. 하지만 이는 안정적이지 못한 방식이다. 이보다는 프로세스 생성 시 main 함수의 매개변수를 활용하는 것이 훨씬 안정적이다.

프로세스별로 별도의 메모리 공간에 문자열 데이터를 저장하고 관리할 수 있도록 되어있다. 문자열의 구조는 다음과 같으며, 이를 환경변수라 한다.

 

key = value

 

[key, value]의 형태를 가지므로, 둘 이상의 데이터를 관리하기가 좋다. 뿐만 아니라, 부모 프로세스는 자식 프로세스 생성 시, 자식 프로세스의 환경변수를 등록할 수도 있고, 부모 프로세스의 환경변수를 상속시킬 수도 있다.

다음은 프로세스 환경변수를 등록할 때 사용하는 함수이다.

 

BOOL SetEnvironmentVariable(
    LPCWSTR lpName,
    LPCWSTR lpValue
);

 

 

다음은 위 함수를 통해서 등록한 환경변수를 참조할 때 사용하는 함수이다.

 

DWORD GetEnvironmentVariable(
    LPCWSTR lpName,
    LPWSTR lpBuffer,
    DWORD nSize
);

참고 자료:

윤성우. 『뇌를 자극하는 윈도우즈 시스템 프로그래밍』.한빛미디어, 2007.

'독서 > [ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ]' 카테고리의 다른 글

[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 10. 컴퓨터 구조에 대한 세 번째 이야기  (3) 2023.10.08
[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 09. 스케줄링 알고리즘과 우선순위  (3) 2023.10.03
[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 07. 프로세스간 통신(IPC) 1  (2) 2023.09.24
[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 06. 커널 오브젝트와 오브젝트 핸들  (2) 2023.09.24
[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 05. 프로세스의 생성과 소멸  (1) 2023.09.17
'독서/[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ]' 카테고리의 다른 글
  • [ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 10. 컴퓨터 구조에 대한 세 번째 이야기
  • [ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 09. 스케줄링 알고리즘과 우선순위
  • [ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 07. 프로세스간 통신(IPC) 1
  • [ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 06. 커널 오브젝트와 오브젝트 핸들
coding-l7
coding-l7
  • coding-l7
    coding-l7rl0
    coding-l7
  • 글쓰기 관리
  • 전체
    오늘
    어제
    • 분류 전체보기 N
      • 기타
      • 유니티
        • OfficeWorkerRunning
      • 프로그래밍 언어 N
        • C N
        • C#
        • C++
      • CS
        • 컴퓨터 구조
        • 운영체제
      • 물리 기반 시뮬레이션
        • 기초
        • Cloth Simulation
        • Fluid Simulation
      • 코딩 테스트
        • 프로그래머스
        • 백준
      • 독서
        • [ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ]
        • [ 혼자 공부하는 컴퓨터 구조 + 운영체제 ]
        • [ CUDA 기반 GPU 병렬 처리 프로그래밍 ]
      • 영어
        • Basic Grammar In Use
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 깃허브
    • 포트폴리오
  • 공지사항

  • 인기 글

  • 태그

    fluid simulation
    물리 기반 시뮬레이션
    position based dynamics
    컴퓨터 구조
    narrow range filter screen-space fluid rendering
    wave simulation
    GLSL
    실수
    pbd
    RAM
    액체 시뮬레이션
    파동 난류
    surface turbulence
    cloth simulation
    jump table
    입자 기반 방법
    그리드 기반 방법
    유체 시뮬레이션
    Flip
    정수 승격
    screen space fluid rendering
    fluid implicit particle
    시스템 프로그래밍
    screen-space rendering
    bilateral blur
    C언어
    상수
    collision
    명령어
    OpenGL
  • 최근 댓글

  • hELLO· Designed By정상우.v4.10.3
coding-l7
[ 뇌를 자극하는 윈도우즈 시스템 프로그래밍 ] Chapter 08. 프로세스간 통신(IPC) 2
글쓰기
상단으로

티스토리툴바