오다기리 박의 알고리즘 노트

8장. 프로세스간 통신(IPC) 2 본문

운영체제

8장. 프로세스간 통신(IPC) 2

오다기리 박 2018. 8. 25. 20:50

8장. 프로세스간 통신(IPC) 2

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

  • 도입배경

    • MS에서는 Windows운영체제를 공개하지 않고 있기 때문에 핸들테이블이 어떻게 관리되는지 자세하게 알 수 없다.

    • Windows운영체제의 종류 및 버전마다 핸들 테이블이 관리되는 방법에 다소 차이가 있다.

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

    • 핸들 테이블 : 핸들 정보를 저장하고 있는 테이블로서 프로세스별로 독립적이다.


    • 프로세스가 CreateMailslot() 호출을 통해 메일슬롯 리소스를 생성 요구 -> 커널 오브젝트 생성 -> 커널 오브젝트의 핸들 정보를 얻게됨 -> 프로세스 자신에게 속해 있는 핸들 테이블에 해당 정보가 등록됨 -> 256이 0x2400번지에 존재하는 커널 오브젝트에 접근이 가능하게 됨 -> CreateMailslot() 함수를 빠져 나오면서 핸들값 반환

  • 핸들의 상속

    • CreateProcess() 함수를 호출하면 새로운 자식 프로세스가 생성되고 자식 프로세스를 위한 핸들 테이블도 더불어 생성된다.

    • CreateProcess() 함수 호출 시 bInheritHandles 인자에 따라(TRUE : 상속 여부가 Y인 핸들에 한해서만 상속, FALSE : 상속X) 부모 프로세스 핸들 테이블에 등록되어 있는 핸들 정보가 자식 프로세스에게 상속될 수 있다.

      상속여부 정보도 그대로 상속됨 -> 또 다른 자식 프로세스 생성시 핸들 정보 계속 상속

    • 핸들이 상속될 때 커널 오브젝트의 Usage Count

      -> 자식 프로세스 생성 이전의 부모 프로세스 구성형태.

      -> 부모 프로세스가 자식 프로세스를 생성하면서 핸들 테이블을 상속한 상태.

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

//메일슬롯의 핸들을 이후에 생성되는 자식 프로세스에게 상속하기 위한 코드

SECURITY_ARRTIBUTES sa;

sa.nLength=sizeof(sa);

sa.lpSecurityDescriptor=NULL;

sa.bInheritHandle=TRUE;

CreateMailslot(... , &sa , ...);   //4번째 전달인자

...



//자식 프로세스의 핸들을 이후에 생성되는 자식 프로세스에게 상속하기 위한 코드

SECURITY_ATTRIBUTESsa;

sa.nLength=sizeof(sa);

sa.lpSecurityDescriptior=NULL;

sa.bInheritHandle=TRUE;

CreateProcess(... , &sa , ...);   //3번째 전달인자

...


    • CreateProcess() 호출 시 핸들 상속과 관련해서 결정할 두 가지 사항

      • 자식 프로세스에게 핸들 테이블을 상속할 것인가

      • 자식 프로세스 생성 시 얻게 되는 핸들(자식 프로세스를 가리키는 핸들)의 상속 여부


  • 메일슬롯 예제

  • Pseudo 핸들과 핸들의 Duplicate

    • GetCurrentProcess() 함수는 현재 실행 중인 프로세스를 참조하기 위한 용도로 정의해 놓은 약속된 상수를 반환한다. 즉 pseudo핸들을 반환한다.

    • 진짜 핸들 얻기

      BOOL DuplicateHandle(
      HANDLE hSourceProcessHandle,
      HANDLE hSourceHandle,
      HANDLE hTargetProcessHandle,
      LPHANDLE lpTargetHandle,
      DWORD dwDesireAccess,
      BOOL bInheritHandle,
      DWORD dwOptions
      );
      함수 실패시 반환값은 0

      • hSourceProcessHandle : 복제할 핸들을 소유하는 프로세스

      • hSourceHandle : 복제할 핸들

      • hTargetProcessHandle : 복제된 핸들을 소유할 프로세스

      • lpTargetHandle : 복제된 핸들값을 저장할 변수의 주소

      • dwDesireAccess : 복제된 핸들의 접근권한 지정. DUPLICATE_SAME_ACCESS를 dwOptions의 인자로 전달시 이 인자 무시

      • bInheritHandle : 복제된 핸들의 상속 여부 .TRUE 전달 시 새로운 자식 프로세스로 상속될 수 있음.

      • dwOptions : DUPLICATE_SAME_ACCESS 전달 시 원본 핸들과 동일한 접근권한을 갖게됨. DUPLICATE_CLOSE_SOURCE 전달 시 원본 핸들 종료 시킴. OR연산자로 이들을 동시 전달 가능.

    • 핸들 테이블에 등록되어야 진정한 복사이다.

    • 핸들 테이블은 프로세스별로 독립적이기 때문에 핸들이 복사된다고 해서 핸들값까지 똑같은 것은 아니다.

    • DuplicateHandle() 함수에 의해 핸들이 복사되면 UC는 증가한다. 따라서 복사된 핸들에 대해서도 CloseHandle() 호출을 통해 핸들을 반환해야 한다.

2. 파이프 방식의 IPC

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

    • 메일슬롯

      • 서로 관련이 없는 프로세스들(네트워크로 통신하거나 부모 자식간의 연관 관계가 없는 프로세스들)사이에서 통신할 때 유용한 IPC 기법.

      • 메일슬롯에 할당된 주소를 기반으로 통신하기 때문에 관계없는 프로세스들 사이에서 통신 가능

      • 브로드캐스트 방식의 단방향 통신방식

    • Anonymous 파이프

      • 단방향 통신방식을 취하며, 파이프를 통해서 생성된 핸들을 기반으로 통신하기 때문에 관계가 있는(부모 자식 관계, 형제 관계) 프로세스들 사이에서 통신하는 경우에 유용

    • Named 파이프

      • 메일슬롯과 유사. 차이점은 브로드캐스트 방식을 지원하지 않는 대신 양방향 통신을 지원

  • Anonymous 파이프

    • ‘비가 올 때 옥상에 고인 물을 배수하기 위해 땅으로 연결되어 있는 배수 파이프’

    • BOOL CreatePipe(
      PHANDLE hReadPipe,
      PHANDLE hWritePipe,
      LPSECURITY_ATTRIBUTES lpPipeAttributes,
      DWORD nSize
      );
      함수 실패시 반환값은 0

      • hReadPipe : 파이프 끝(데이터를 읽기 위한)에 해당하는 핸들을 얻게됨

      • hWritePipe : 다른 한쪽 끝(데이터를 쓰기 위한)에 해당하는 핸들을 얻게됨

      • lpPipeAttributes : 보안 관련 정보 전달. 핸들의 상속 특성을 지정할 때 사용

      • nSize : 파이프의 버퍼 사이즈를 지정. 0 전달시 디폴트 사이즈

  • Named 파이프

    • HANDLE CreateNamedPipe(
      LPCTSTR lpName,
      DWORD dwOpenMode,
      DWORD dwPipeMode,
      DWORD nMaxInstances,
      DWORD nOutBufferSize,
      DWORD nInBufferSize,
      DWORD nDefaultTimeOut,
      LPSECURITY_ATTRIBUTES lpSecurityAttributes
      );
      함수 실패시, 반환값은 INVALID_HANDLE_VALUE

      • lpName : 파이프 이름 지정. ex) \\.\pipe\pipename

      • dwOpenMode : 다음 셋 중 하나를 지정. 읽기/쓰기 모드를 지정

        • PIPE_ACCESS_DUPLEX : 읽기, 쓰기 모두 가능

        • PIPE_ACCESS_INBOUND : 서버 입장에서 읽기만 가능

        • PIPE_ACCESS_OUTBOUND : 서버 입장에서 쓰기만 가능

      • dwPipeMode : 데이터 전송 타입, 데이터 수신 타입, 블로킹 모드 설정

        • 데이터 전송방식

          • PIPE_TYPE_BYTE(바이너리 형태)

          • PIPE_TYPE_MESSAGE(텍스트 형태)

        • 데이터 수신방식

          • PIPE_READMODE_BYTE(바이너리 방식)

          • PIPE_READMODE_MESSAGE(텍스트 방식)

        • 함수 리턴방식

          • PIPE_WAIT(블로킹)

          • PIPE_NOWAIT(논블로킹 쓰지않음)

        • ex) 문자열 주고 받을 때는 PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT

        • ex) 바이너리 데이터를 주고 받을 때는  PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT

      • nMaxInstances : 생성할 수 있는 파이프의 최대 개수 지정. 지정한 수만큼 파이프 클라이언트의 연결 요청을 수용 가능. 범위는 1~PIPE_UNLIMITED_INSTANCES(생성가능한 최대 개수)

      • nOutBufferSize : 이름있는 파이프의 출력 버퍼 사이즈 지정. 0입력시 디폴트값

      • nInBufferSize : 이름있는 파이프의 입력 버퍼 사이즈 지정. 0입력시 디폴트값

      • nDefaultTimeOut : WaitNamedPipe() 함수에 적용할 기본 만료 시간

      • lpSecurityAttributes : 보안 속성 지정

    • BOOL ConnectNamedPipe(
      HANDLE hNamedPipe,
      LPOVERLAPPED lpOverlapped
      );
      함수 실패시, 반환값은 0

      • hNamePipe : CreateNamedPipe() 함수 호출을 통해서 생성한 파이프의 핸들

      • lpOverlapped : 중첩 I/O를 위한 전달인자. 보통 NULL

    • BOOL WaitNamedPipe(
      LPCTSTR lpNamedPipeName,
      DWORD nTimeOut
      );
      타임아웃이 흐르기 전에 파이프의 인스턴스가 사용불가라면 반환값은 0이다.

      • lpNamedPipeName : 상태 확인의 대상이 되는 파이프 이름

      • nTimeOut : 타임아웃 시간. 이 시간이 지나면 FALSE 반환. NMPWAIT_WAIT_FOREVER로 설정시 연결가능한 상태가 될 때까지 기다리게 되며, NMPWAIT_USE_DEFAULT_WAIT로 설정시 서버에서 CreateNamedPipe() 함수를 호출하면서 전달한 7번째 인자를 통해 결정된 디폴트 시간만큼만 기다림.

    • BOOL SetNamedPipeHandleState(
      HANDLE hNamedPipe,
      LPDWORD lpMode,
      LPDWORD lpMaxCollectionCount,
      LPDWORD lpCollectDataTimeOut
      );
      함수 실패시 반환값은 0

      • hNamedPipe : 파이프와의 연결 속성을 변경시키기 위한 핸들

      • lpMode : 읽기 모드와 함수 리턴방식에 대한 값을 OR 연산으로 전달.

        • 데이터 전송방식

          • PIPE_TYPE_BYTE(바이너리 형태)

          • PIPE_TYPE_MESSAGE(텍스트 형태)

        • 데이터 수신방식

          • PIPE_READMODE_BYTE(바이너리 방식)

          • PIPE_READMODE_MESSAGE(텍스트 방식)

        • 함수 리턴방식

          • PIPE_WAIT(블로킹)

          • PIPE_NOWAIT(논블로킹 쓰지않음)

      • lpMaxCollectionCount : 서버로 데이터를 보내기에 앞서서 버퍼링할 수 있는 최대 바이트 크기. 서버와 클라이언트가 같은 PC일 경우 NULL전달

      • lpCollectDataTimeOut : 서버로 데이터를 보내기에 앞서서 버퍼링을 허용하는 최대 시간. 서버와 클라이언트가 같은 PC일 경우 NULL전달


    • HANDLE CreateFile(
      LPCTSTR lpFileName,
      DWORD dwDesireAccess,
      DWORD dwShareMode,
      LPSECURITY_ATTRIBUTES lpSecurityAttributes,
      DWORD dwCreationDisposition,
      DWORD dwFlagsAndAttributes,
      HANDLE hTemplateFile
      );
      함수 실패시 반환값은 INVALID_HANDLE_VALUE

      • lpFileName : 개방할 파일 이름

      • dwDesireAccess : 읽기/쓰기 모드지정

        • GENERIC_READ : 읽기 모드

        • GENERIC_WRITE : 쓰기 모드

      • dwShareMode : 파일 공유방식 지정

        • 0 : 다른 프로세스에 공유 불가. 이미 개방된 파일은 중복 개방 불가.

        • FILE_SHARE_READ : 다른 프로세스에서 이 파일에 동시 읽기 접근 가능

        • FILE_SHARE_WRITE : 다른 프로세스에서 이 파일에 동시 쓰기 접근 가능. 단 동시에 같은 영역에 데이터를 쓰는 문제를 피해야 함.

      • lpSecurityAttributes : 보안 속성 지정. 핸들을 자식 프로세스에게 상속할 것인지 말 것인지

      • dwCreationDisposition : 파일이 생성되는 방법 지정

        • CREATE_ALWAYS : 항상 새 파일을 생성

        • CREATE_NEW : 새 파일 생성. 같은 이름의 파일이 존재하면 생성 실패

        • OPEN_ALWAYS : 기존 파일 개방. 없으면 새로 생성

        • OPEN_EXISTING : 기존 파일 개방. 존재하지 않으면 함수 호출 실패!

        • TRUNCATE_EXISTING : 기존 파일의 내용 지우고 개방. 파일이 존재하지 않으면 호출 실패!

        • dwFlagsAndAttributes : 파일의 특성 정보를 설정. 기본적으로 FILE_ATTRIBUTE_NORMAL사용

        • hTemplateFile : 일반적으로 NULL

    • BOOL ReadFile(
      HANDLE hFile,
      LPVOID lpBuffer,
      DWORD nNumverOfBytesToRead,
      LPDWORD lpNumberOfBytesRead,
      LPOVERLAPPED lpOverlapped
      );
      함수 실패시 반환값은 0

      • hFile : 데이터를 읽을 파일의 핸들

      • lpBuffer : 읽어 들인 데이터를 저장할 버퍼(배열, 메모리)의 주소(포인터)

      • nNumverOfBytesToRead : 파일로부터 읽고자 하는 데이터의 크기를 바이트 단위로 지정

      • lpNumberOfBytesRead : 실제 읽어 들인 데이터 크기를 얻기 위한 변수 주소

      • lpOverLapped :

    • BOOL WriteFile(
      HANDLE hFile,
      LPCVOID lpBuffer,
      DWORD nNumberOfBytesToWrite,
      LPDWORD lpNumberOfBytesWritten,
      LPOVERLAPPED lpOverlapped
      );
      함수 실패시 반환값은 0

      • hFile : 데이터를 저장할 파일의 핸들

      • lpBuffers : 데이터를 저장하고 있는 버퍼(배열, 메모리)의 주소(포인터)

      • nNumberOfBytesToWrite : 파일에 저장하고자 하는 데이터 크기(바이트 단위)

      • lpNumberOfBytesWritten : 파일에 실제 저장된 데이터 크기를 얻기 위한 변수 주소

      • lpOverlapped


3. 프로세스 환경변수

  • 프로세스 환경변수

    • 프로세스별로 별도로 메모리 공간에 문자열 데이터를 저장하고 관리할 수 있도록 되어 있다.

    • 문자열의 구조는 [key, value] 형태이며 이를 환경변수라 한다.

    • 부모 프로세스는 자식 프로세스 생성 시, 자식 프로세스의 환경변수를 등록할 수도 있고, 그냥 부모 프로세스의 환경변수를 상속시킬 수도 있다.

  • 환경변수를 등록할 때 사용하는 함수

    • BOOL SetEnvironmentVariable(
      LPCTSTR lpName,
      LPCTSTR lpValue
      );
      함수 실패시 반환값은 0

      • lpName : Key에 해당하는 값을 지정. 이후에 Key를 통해서 value값을 참조 가능

      • lpValue : value에 해당하는 값을 지정

  • SetEnvironmentVariable() 를 통해서 등록한 환경변수를 참조할 때 사용하는 함수

    • DWORD GetEnvironmentVariable(
      LPCTSTR lpName,
      LPTSTR lpBuffer,
      DWORD nSize
      );
      함수 실패시 반환값은 0
      함수 성공시 lpBuffer에 저장된 문자열의 길이 반환

      • lpName : key 전달. key에 해당하는 value를 얻게 된다,

      • lpBuffer : value 값을 저장하기 위한 메모리의 주소를 지정

      • nSize : lpBuffer가 가리키는 메모리의 크기