VIII : 프로세스간 통신 (IPC) 2
뇌를 자극하는 윈도우즈 시스템 프로그래밍 을 읽고 정리한 문서입니다 ;)
핸들 테이블과 오브젝트 핸들의 상속
유능한 윈도우 프로그래머는 프로세스 핸들 테이블이 어떻게 관리되는지를 이해하고 있어야 한다는 Jeffrey Richter의 말이 있듯이, 핸들 테이블에 대한 이해는 IPC 를 알기 위한 필수 요소이다.
핸들은 그저 단순한 정수값인데, 이를 가지고 커널 모드에 있는 커널 오브젝트에 접근할 수 있는 이유는 무엇일까? 이는 프로세스 핸들 테이블이 있기 때문이다.
핸들 테이블은 핸들 정보를 저장하고 있는 테이블로서, 프로세스별로 독립적이다.
CreateProcess()
함수를 호출하면 새로운 자식 프로세스가 생성된다. 이 때 BOOL bInheritHandles
라는 이름을 가진 다섯 번째 인자가 무엇이냐에 따라 부모 프로세스 핸들 테이블에 등록되어 있는 핸들 정보는 새롭게 생성되는 자식 프로세스에게 상속될 수 있다.
실제 핸들 테이블에는 핸들마다 상속 여부를 결정짓기 위한 컬럼이 존재하는데, 이는 자식에게 만일 부모의 핸들 테이블이 상속된다 하더라도 상속가능 플래그는 그대로 유지된다.
프로세스가 핸들을 얻게 되었다는 의미는 프로세스 핸들 테이블에 해당 핸들에 대한 정보가 갱신되었음을 의미하는 것이다.
예를 들어 CreateMailslot()
함수를 통해 메일슬롯을 생성했다면, 메일슬롯 리소스 생성 → 커널 오브젝트 생성 → 핸들 정보가 프로세스 핸들 테이블에 갱신 → 해당 함수가 핸들값 반환 이라는 네 단계의 절차를 거치는 것이다.
핸들의 상속가능 여부는 리소스가 생성되는 순간에 프로그래머에 의해 결정된다.
핸들을 리턴하는 대부분의 리소스 생성 API에는 파라미터로 LPSECURITY_ATTRIBUTES
라는 값을 전달하게 되어 있는데, 이 구조체의 BOOL bInheritHandle
값을 어떻게 설정하여 넣느냐에 따라서 해당 핸들이 상속될 수 있음이 정해진다.
따라서, CreateProcess
시에는 자식 프로세스에게 부모의 핸들 테이블을 상속시킬 것인지를 결정해야 하고, 또한, 이렇게 만들어진 핸들을 상속 가능으로 할것인지를 설정해야 하는 두 가지 사항을 결정해야 하는 것이다.
Pseudo 핸들과 핸들의 중복
실행 중에 있는 프로세스 자신의 핸들을 얻는 방법으로서 GetCurrentProcess()
함수를 쓸 수 있지만, 이 함수 호출을 통해 얻는 핸들은 가짜 핸들이다. 이는 실제로 프로세스 핸들 테이블에 존재하는 것이 아닌, 자기 자신을 의미하도록 사전 정의된 특수한 값을 리턴하는 것이기 때문이다.
현재 실행 중인 프로세스가 가짜 핸들 이외에 핸들 테이블에 등록되어 있는 진짜 핸들을 얻어야 할 경우에는, DuplicateHandle()
이라는 함수를 사용한다.
BOOL DuplicateHandle(
HANDLE hSourceProcessHandle,
HANDLE hSourceHandle,
HANDLE hTargetProcessHandle,
LPHANDLE lpTargetHandle,
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwOptions
);
HANDLE hSourceProcessHandle
: 복제할 핸들을 소유하는 프로세스의 핸들이다.HANDLE hSourceHandle
: 복제할 핸들을 지정한다.HANDLE hTargetProcessHandle
: 복제된 핸들을 소유할 프로세스의 핸들이다.LPHANDLE lpTargetHandle
: 복제된 핸들이 저장될 변수의 주소이다.
이 핸들 복사 API를 이용하면, GetCurrentProcess()
를 통해 얻은 가짜 핸들을 진짜 핸들로 얻어낼 수 있게 된다.
파이프 방식의 IPC
윈도우의 파이프 매커니즘에는 두 가지 종류가 있는데, 하나는 이름없는 파이프 (Anonymous Pipe)이고, 하나는 이름있는 파이프 (Named Pipe) 이다.
- 메일슬롯
- 브로드캐스트 방식의 단방향 통신방식을 취하며, 메일슬롯 이름이 주소를 의미하므로 서로 관계 없는 프로세스간 통신이 가능해진다.
- 이름없는 파이프
- 단방향 통신방식을 취하며, 파이프를 생성하여 얻은 핸들을 통해 통신하기 때문에 서로 관련있는 프로세스간 통신을 가능하게 해준다.
- 이름있는 파이프
- 양방향 통신방식을 취하며, 파이프 주소를 통하면 서로 관계 없는 프로세스간 통신이 가능해진다.
이름없는 파이프
이름없는 파이프는 데이터를 한 쪽 방향으로 흐르게 해주는 연결 통로이다.
BOOL CreatePipe(
PHANDLE hReadPipe,
PHANDLE hWritePipe,
LPSECURITY_ATTRIBUTES lpPipeAttributes,
DWORD nSize
);
PHANDLE hReadPipe
: 파이프의 양쪽 끝단 중, 데이터를 읽기 위한 파이프 끝에 해당하는 핸들을 얻게 된다.PHANDLE hWritePipe
: 파이프의 양쪽 끝단 중 데이터를 쓰기 위한 파이프 끝에 해당하는 핸들을 얻게 된다.DWORD nSize
: 파이프의 버퍼 사이즈를 지정하는 용도이다.
이름있는 파이프
이름있는 파이프는 양방향 통신으로 서버-클라이언트의 구조를 가진다.
서버는 CreateNamedPipe
함수를 통해서 파이프를 생성하고, ConnectNamedPipe
함수를 호출하여 파이프를 연결 요청을 기다리는 파이프로 상태를 변경한다.
이후 클라이언트는 서버가 만들어놓은 파이프에 연결하기 위해 리소스를 생성하는데, 이는 CreateFile
함수가 모든 것을 처리하게 된다.
HANDLE CreateNamedPipe(
LPCTSTR lpName,
DWORD dwOpenMode,
DWORD dwPipeMode,
DWORD nMaxInstances,
DWORD nOutBufferSize,
DWORD nInBufferSize,
DWORD nDefaultTimeOut,
LPSECURITY_ATTRIBUTES lpSA
);
LPCTSTR lpName
: 파이프 이름을 지정한다.\\.\pipe\pipename
같은 형태로 구성된다.DWORD dwOpenMode
: 파일의 읽기 쓰기 모드를 지정하는 것과 같은 이치이다.PIPE_ACCESS_DUPLEX
: 읽기 쓰기가 모두 가능하도록 설정PIPE_ACCESS_INBOUND
: 파이프 생성 함수 호출자 입장에서 읽기만 가능하다.PIPE_ACCESS_OUTBOUND
: 파이프 생성 함수 호출자 입장에서 쓰기만 가능하다.
DWORD dwPipeMode
: 데이터 전송 타입, 데이터 수신 타입, 블로킹 모드를 설정할 수 있다.- 데이터 전송방식 :
PIPE_TYPE_BYTE
혹은PIPE_TYPE_MESSAGE
로서, 전자는 바이너리 형태의 전송, 후자는 텍스트 모드의 전송이다. - 데이터 수신방식 :
PIPE_READMODE_BYTE
혹은PIPE_READMODE_MESSAGE
로서, 전송 방식의 타입들과 의미가 같다. - 함수 리턴방식 :
PIPE_WAIT
혹은PIPE_NOWAIT
으로서, 반드시PIPE_WAIT
이 사용된다.
- 데이터 전송방식 :
DWORD nMaxInstances
: 생성할 수 있는 파이프의 최대 개수로서, 클라이언트 수용 능력을 의미한다.
위 함수를 통해 만든 파이프를 연결 요청 대기 상태로 변경시킬 때 사용하는 함수는 다음과 같다.
BOOL ConnectNamedPipe(
HANDLE hNamedPipe,
LPOVERLAPPED lpOverlapped
);
파이프의 최대 개수는 처음 CreateNamedPipe
함수가 호출될 때 지정되고, 그 이후부터는 단지 파이프 생성의 목적만으로 호출된다.
파이프로 데이터 전송 시에, FlushFileBuffers
함수를 통해 출력 버퍼를 비워, 즉시 전송시킬 수 있다. 또한, DisconnectNamedPipe
함수는 파이프 연결을 끊음으로서 클라이언트가 에러 메시지를 받을 수 있도록 도와준다.
서버의 파이프에 연결할 때는, WaitNamedPipe()
함수를 이용하며, 이 때도 두 번째 인자로 타임아웃을 설정할 수 있다.
마지막으로, 이름있는 파이프의 속성을 변경하기 위해서는 SetNamedPipeHandleState
를 사용한다.
프로세스 환경변수
부모 프로세스가 자식 프로세스에게 데이터를 전달하기 위해서 쓸 수 있는 방법 중 하나로서, 프로세스 환경변수를 사용하는 방법이 있다. 프로세스별로 별도의 메모리 공간에 문자열 데이터를 저장하고 관리할 수 있도록 되어있기 때문이다.
SetEnvironmentVariable()
함수와 GetEnvironmentVariable()
함수를 사용하면 이 값을 쓰고 읽을 수 있다.
만일 CreateProcess()
함수를 사용할 때, 환경 변수를 의미하는 일곱 번째 인자로 NULL
을 전달하게 되면 부모 프로세스에 등록되어 있는 환경변수를 등록하게 된다.
이것만은 알고 갑시다
- 핸들 테이블
- 커널 오브젝트와 핸들 사이에 핸들 테이블이 존재한다. 덕분에 핸들의 정수값만 가지고도 커널 오브젝트의 식별이 가능하다.
- 핸들과 핸들 테이블
- 핸들 테이블은 프로세스별로 독립적이다. 그리고 숫자가 핸들로서 의미를 지니기 위해서는 해당 숫자가 핸들 테이블에 등록되어야 한다.
- 핸들의 상속
- 핸들은 자식 프로세스를 생성하는 과정에서 상속할 수 있다. 핸들이 자식 프로세스에게 상속된다는 말은 부모 프로세스의 핸들 테이블 정보가 자식 프로세스의 핸들 테이블에 복사된다는 것이다.
- 가짜 핸들 (Pseudo 핸들)
GetCurrentProcess
함수를 통해 얻은 핸들은 가짜 핸들이다. 이는 핸들 테이블에 등록된 값이 아닌 이미 의미를 가지는 상수값이기 때문이다. 핸들 테이블의 핸들을 얻기 위해서는DuplicateHandle
함수를 사용해야 한다.
- 파이프
- 이름없는 파이프와 이름있는 파이프로 나뉘며, 이름없는 파이프는 단방향 통신이라는 점과 관계가 있는 프로세스간의 통신이 가능하다는 특징이 있다. 이름있는 파이프는 양방향 통신이며 주소 체계를 통해 관계 없는 다수의 프로세스간에도 통신이 가능하다.
'공부한 이야기 > 윈도우 OS' 카테고리의 다른 글
X : 컴퓨터 구조에 대한 세 번째 이야기 (0) | 2023.04.29 |
---|---|
IX : 스케줄링 알고리즘과 우선순위 (0) | 2023.04.29 |
VII : 프로세스간 통신 (IPC) 1 (0) | 2023.04.29 |
VI : 커널 오브젝트와 오브젝트 핸들 (0) | 2023.04.29 |
V : 프로세스의 생성과 소멸 (0) | 2023.04.29 |