728x90

멀티 프로세스 기반 운영체제는 실행중인 프로세스들에게 골고루 CPU를 할당하는 일이 필요한데, 운영체제의 일부분인 스케줄러가 이러한 일을 담당한다.

 

일반 OS와 리얼타임(Real-Time)OS의 차이점

RTOS와 일반 OS의 차이는 응답성(응답 속도)에 있다.

RTOS는 어떠한 작업이 생기기만을 기다리다가 작업이 생기면 그 일을 끝낼때 까지 다른일은 하지 않는다.

그렇기 때문에 RTOS는 응답성은 매우 좋지만 범용적인 OS에 비해 사용하는 영역이 제한적이다.

 

일반 OS는 여러가지 일(다양한 프로세스)을 동시에 진행하기 때문에 응답성은 조금 떨어지지만 범용적으로 사용이 가능하다.

 

RTOS는 일반 OS에 비해 사용되는 목적이 구체적이고 제한적이여서 단순하게 디자인 되어있고 일반 OS에 비해서 훨씬 가볍다.

 

SoftRTOS vs Hard RTOS

SoftRTOS의 경우 일반 OS와 별 차이가 없다. 하는일이 특화되어 일반 OS에 비해 응답성이 좋은 것 뿐이다.

 

전통적인 RTOS는 Hard RTOS를 말한다. Hard RTOS에서 중요한 것은 데드라인(Dead Line)이다. 자동차 또는 핵발전소 등등 데드라인이 중요한 시스템에서 데드라인을 충족시키는 능력이 있는 RTOS를 Hard RTOS라고 한다.

 

선점형(preemptive)OS와 비선점형(Non-Preemptive)OS

비선점형 OS는 현재 실행중인 프로세스 보다 높은 우선순위의 프로세스가 등장한다고 해서 실행의 대상을 바로 변경하지 않는다. 새로 등장한 우선순위의 프로세스가 실행될려면 현재 실행중인 프로세스가 CPU를 양보하거나 블로킹 상태에 놓일 때까지 기다려야 한다.

 

선점형 OS는 현재 실행중인 프로세스보다 높은 우선순위의 프로세스가 등장하면 스케줄러에 의한 실행순서 조정이 적극적으로 가해진다. 비선점형 OS에 비해 스케줄러가 하는일이 많다. 그리고 우선순위가 높은 프로세스가 먼저 실행되는 구조이다. Windows도 선점형 OS이다.

 

우선  순위(Priority) 스케줄링 알고리즘

우선순위 스케줄링 알고리즘이란 각각의 프로세스마다 우선순위를 부여해서 우선순위가 높은 프로세스를 먼저 실행시키는 방식이다. 우선순위가 7인 프로세스가 실행중일 때 우선순위가 2인 프로세스는 결코 실행되지 않는다. 이러한 상황을 기아(Starvation)상태라 한다.

우선순위 스케줄링 알고리즘은 우선순위가 높은 프로세스를 먼저 실행하는 알고리즘이다. 우선순위가 높은 프로세스가 종료되어야 다음 우선순위 프로세스가 작동한다.

그러나 기아(Starvation)상태에 빠지는 것은 드물다. 높은 우선순위 프로세스가 I/O작업을 하는 과정에서 우선순위가 낮은 프로세스가 실행기회를 얻을 수도 있기 때문이다.

 

라운드 로빈(Round-Robin) 스케줄링 알고리즘

우선순위가 동일한 프로세스 간에 실행시간 배분을 위해 Windows는 라운드 로빈 스케줄링 알고리즘도 적용하고 있다.

라운드 로빈 알고리즘은 같은 우선순위 프로세스들에게 정해진 시간 간격 만큼만 실행을 하고 우선순위가 동일한 다른 프로세스에게 CPU할당을 넘기는 방식을 제공한다.

실행의 최소 시간간격을 가리켜 퀀텀(Quantum)혹은 타임 슬라이스(Time Slice)라 한다. 동일한 우선순위의 모든 프로세스는 이 타임 슬라이스 기준으로 CPU할당을 넘긴다.

 

Windows 운영체제는 프로세스를 스케줄링 하는데 있어서 우선순위, 그리고 라운드 로빈 기반의 알고리즘을 적용하고 있다.

 

스케줄링알고리즘에 의해서 스케줄링이 진행되는 시점

스케줄러가 언제 동작하는지 이해하는 것도 중요하다.

라운드 로빈 알고리즘에 의하면 프로세스의 실행시간 간격에 해당하는 매 타임 슬라이스 마다 스케줄러는 동작해야한다.

 

우선순위 알고리즘 관점에서는 새로운 프로세스가 등장할 때 마다 스케줄러는 현재 실행중인 프로세스와 새로운 프로세스를 비교해야 한다. 즉 스케줄러는 새로운 프로세스가 생성될 때 마다 동작해야 한다. 또한 실행중인 프로세스가 종료되면 다른 프로세스를 실행시켜야 하므로 이 경우에도 스케줄러가 동작해야한다.

 

블로킹 상황에서는 현재 실행중인 프로세스가 블로킹 상태에 놓이면, 다음 실행될 프로세스 선정을 위해 스케줄러가 동작한다.

 

Windows 프로세스 우선순위

 Windows는 총 6단계의 우선순위 계층을 제공한다.

         Priority                                                             Meaning

IDLE_PRIORITY_CLASS                                             기존 우선순위 4

NORMAL_PRIORITY_CLASS                                      기존 우선순위 9

HIGH_PRIORITY_CLASS                                            기존 우선순위 13

REALTIME_PRIORITY_CLASS                                      기존 우선순위 24

 

 

 

 

프로그램 코드상에서 실행속도 조절을 원한다면 Sleep함수의 호출을 통해서 Blocked상태로 만드는것이 좋은 방법이다.

비록 잠시이긴 하지만 자신보다 우선순위가 낮거나 같은 프로세스에게 Running기회를 넘겨주기 때문이다.

728x90
728x90

Windows의 파이프 메커니즘의 두가지 종류.

1.이름없는 파이프(Anonymous Pipe)

2.이름있는 파이프(Named Pipe)

 

메일 슬롯과 파이프

메일슬롯은 네트워크로 연결되어 통신하는 프로세스들이나, 부모 자식간의 연관 관계가 전혀 없는 프로세스들 사이에서 통신 할 때 유용한 IPC 기법이다. 이름없는 파이프는 관계가 있는(부모 자식 관계, 형제 관계)프로세스들 사이에서 통신하는 경우에 유용하다.

 

이름있는 파이프, 이름이 있다는 것은 주소정보가 있다는 뜻이다. 즉 메일 슬롯처럼 서로 관계가 없는 프로세스들 사이에서도 주소정보를 공유하여 데이터를 주고받을 수 있다는 뜻이다. 또한 메일 슬롯과 달리 양방향 통신이 가능하다.

 

메일슬롯은 단방향 통신만을 지원하지만 브로드 캐스트(Broad Cast)방식의 데이터 전송이 가능하다. 따라서 여러사람에게 동시에 메시지를 전달하는 시스템 구현시 메일슬롯이 유용하다.

 

특징 정리

메일슬롯 : 단방향 통신 방식, 브로드캐스트 전송가능, 메일슬롯에 주소가 할당되어 주소를 기반으로 통신하기 때문에                   관계없는 프로세스들 사이 통신가능

이름없는 파이프 : 단방향 통신 방식, 파이프를 통해서 생성된 핸들 기반 통신 -> 프로세스들 사이에 관계 필요

이름있는 파이프 : 양방향 통신 방식, 브로드 캐스트 X

 

이름없는 파이프(Anonymous Pipe)

BOOL CreatePipe(

    PHANDLE hReadPipe,

    PHANDLE hWritePipe,

    LPSECURITY_ATTRIBUTES lpPipeAttributes,

    DWORD nSize

);

1.hReadPipe : 파이프는 생성시 두개의 핸들을 얻게된다. 각각 데이터가 들어가는 쪽과 나가는 쪽이다. (데이터를 쓰는쪽, 읽는 쪽) 첫번째 인자는 데이터를 읽기위한 파이프 쪽의 핸들을 얻게된다.

2.hWritePipe : 데이터를 쓰는쪽에 해당하는 핸들을 얻는다.

3.lpPipeAttributes : 보안관련 정보

4.nSize : 파이프의 버퍼 사이즈를 지정하는 용도이다. 0을 전달하면 디폴트 사이즈로 버퍼크기가 결정된다.

 

파이프 생성시 생성되는 출력용핸들, 입력용 핸들을 상속한다면 부모 자식 프로세스간 메시지 전송이 가능해진다.

 

 

이름있는 파이프(Named Pipe)

1.CreateNamedPipe 함수를 통해 이름있는 파이프 생성

2.ConnectNamedPipe 함수 호출을 통해 파이프 연결 요청을 기다리는 상태로 변경

3.클라이언트에서 CreateFile함수를 통해 파이프와 연결

 

CreateNamedPipe 함수

HANDLE CreateNamedPipe(

      LPCTSTR   lpName,

      DWORD  dwOpenMode,

      DWORD  dwPipeMode,

      DWORD   nMaxInstances,

      DWORD   nOutBufferSize,

      DWORD   nInBufferSize,

      DWORD nDefaultTimeOut,

      LPSECURITY_ATTRIBUTES   lpSecurityAttributes

);

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

2.dwOpenMode : 파일 모드 지정과 유사

      _PIPE_ACCESS_DUPLEX : 읽기, 쓰기 모두 가능

      _PIPE_ACCESS_INBOUND : 읽기만 가능

      _PIPE_ACCESS_OUTBOUND : 쓰기만 가능

 

3.dwPipeMode : 데이터 전송 타입, 데이터 수신 타입, 블로킹 모드 세가지를 설정한다.

4.nMaxInstance : 생성할 수 있는 파이프의 최대 개수를 지정한다.

                      PIPE_UNLIMITED_INSTANCES가 전달되면 생성 가능한 최대 개수만큼 생성된다.

5.nOutBufferSize : 이름 있는 파이프의 출력 버퍼 사이즈를 설정한다. 0입력시 Windows 디폴트 값 설정

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

6.nDefaultTimeOut : WaitNamedPipe함수에 적용할 기본 만료시간을 밀리세컨드 단위로 설정한다.

7.lpSecurityAttributes : 보안 속성 지정

 

dwPipeMode 설정

데이터 전송 방식 : PIPE_TYPE_BYTE(바이트), PIPE_TYPE_MESSAGE(메세지) 데이터 전송시 바이너리 형태로 전송할 것인지, 메시지 방식으로 전송할 것인지를 결정한다. PIPE_TYPE_MESSAGE 전달시 텍스트 모드로 전송한다.

 

데이터 수신 방식 : PIPE_READMODE_BYTE, PIPE_READMODE_MESSAGE 바이너리 방식으로 읽을 것인지, 메시지 방식으로 읽을 것인지를 결정한다.

 

함수 리턴 방식 : PIPE_WAIT(블로킹), PIPE_NOWAIT(넌-블로킹) 무조건 PIPE_WAIT전달

 

각 모드들은 or연산으로 조합할 수 있다.

 

NamedPipe 사용시 유용한 함수들

connectNamedPipe 함수

BOOL ConnectNamedPipe(

        HANDLE hNamedPipe,

        LPOVERLAPPED lpOverlapped

);

1.hNamedPipe : CreateNamedPipe에서 생성된 파이프 핸들을 전달한다.

2.lpOverlapped : 중첩 I/O를 위한 전달인자이다.

 

WaitNamedPipe함수

BOOL WaitNamedPipe(

    LPCTSTR lpNamedPipeName,

    DWORD nTimeOut

);

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

2.nTimeOut : 타임-아웃 시간 설정

 

SetNamedPipeHandleState 함수

BOOL SetNamedPipeHandleSTate(

     HANDLE hNamedPipe,

     LPDWORD lpMode,

     LPDWORD lpMaxCollectionCount,

     LPDOWRD lpCollectDataTimeout

);

1.hNamedPipe : 파이프와의 연결 속성 변경을 위한 핸들 지정

2.일기모드와 함수 리턴방식에 대한 값을 OR(|)연산하여 전달

3.lpMaxCollectionCount : 서버로 데이터를 보내기에 앞서서 버퍼링할 수 있는 최대바이트 크기 지정

4.lpCollectionDataTimeout : 서버로 데이터를 보내기에 앞서서 버퍼링허용 최대시간 지정

 

728x90
728x90

프로세스 별로 핸들정보를 저장하고 있는 핸들 테이블이 존재한다.

각각의 프로세스가 자신만의 핸들 테이블을 하나씩 구성하고 관리한다.

 

프로세스가 CreateProcess 함수나 CreateMailslot과 같은 함수 호출을 통해서 리소스 생성을 요구한 결과로 핸들 정보를 얻게 될 경우, 프로세스 자신에게 속해있는 핸들 테이블에 해당 정보가 등록된다.

 

 

핸들 테이블은 프로세스 별로 독립적이다.

 

 

핸들의 상속

CreateProcess 함수를 호출하면 새로운 자식 프로세스가 생성되고, 자식프로세스를 위한 핸들 테이블도 더불어 생성된다. CreateProcess 함수 호출시 전달되는 인자에 따라 부모프로세스의 핸들테이블에 등록되어 있는 핸들 정보들을 자식 프로세스에 상속시킬 수 있다.

 

핸들의 상속에 대한 이해

 

자식프로세스 핸들 테이블 상속

실제 핸들테이블에는 해당 핸들의 상속여부를 결정짓기 위한 컬럼이 존재한다. 또한 핸들테이블의 레코드가 상속될때 상속 여부에 대한 정보도 변경없이 그대로 상속된다. 따라서 자식 프로세스가 또 다른 자식 프로세스를 생성할 경우에도 이 핸들에 대한 정보도 계속해서 상속된다.

 

CreateProcess 함수중 bInheritHandle 전달인자는 자식 프로세스에게 핸들 테이블에 등록되어 있는 핸들 정보를 상속해 줄것인지, 말것인지를 결정하는 요소이다. TRUE를 인자로 전달할 경우 부모 프로세스의 핸들 테이블 정보는 자식 프로세스로 상속된다.

 

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

프로세스의 핸들 테이블에 해당 핸들에 대한 정보가 갱신(추가)되었을 때 프로세스가 핸들을 얻게 되었다고 한다.

 

자식프로세스 생성 후 ->

 

자식 프로세스 핸들테이블 상속 후 Usage Count

핸들 테이블에 등록되어 있는 수만큼 Usage Count가 정해진다.

 

Mailslot 예제 핸들테이블

 

MailReceiver의 핸들 테이블의 127, 0x1200 번지에 있는 커널 오브젝트는 CreateMailSlot 에 의해 생성된 것이다.

MailSender의 핸들 테이블의 핸들 127은 0x1700번지에 있는 커널 오브젝트는 MailSlot에 데이터를 쓰기위한 CreateFile에 의해 생성된 것이다. 핸들 테이블은 프로세스 마다 독립적인 요소이기 때문에 핸들값이 같아도 가리키는 커널 오브젝트는 같을수도 있고, 다를 수도 있다.

 

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

GetCurrentProcess 함수를 통해서 얻은 핸들은 가짜 핸들(Pseudo 핸들) 이다.

 

GetCurrentProcess함수를 통해 얻은 핸들은 핸들 테이블에 등록되어 있지 않은 핸들이고, 실행중인 프로세스를 참조하기 위한 용도로 정의해놓은, 약속된 상수가 반환되는 것이기 때문이다. 그렇기 때문에 자식 프로세스에게 상속되지 않고, CloseHandle함수가 호출되어도 아무일도 발생하지 않는다.

 

실행중인 프로세스의 진짜 핸들을 얻기 위해서는 DuplicateHandle 이라는 함수가 필요하다.

DuplicateHandle()

BOOL DuplicateHandle(

       HANDLE hSourceProcessHandle,

       HANDLE hSourceHandle,

       HANDLE hTargetProcessHandle,

       LPHANDLE lpTargetHandle,

       DWORD dwDesiredAccess,

       BOOL bInheritHandle,

       DWORD dwOptions

);

1.hSourceProcessHandle : 복제할 핸들을 소유하는 프로세스 지정

2.hSourceHandle : 복제할 핸들 지정

3.hTargetProcessHandle : 복제된 핸들을 소유할 프로세스 지정

4.lpTargetHandle : 복제된 핸들값을 저장할 변수의 주소를 지정

5.dwDesiredAccess : 복제된 핸들의 접근권한 지정

6.bInheritHandle : 복제된 핸들 상속 여부

7.dwOptions : DUPLICATE_SAME_ACCESS를 전달하면 원본 핸들과 동일한 접근권한, DUPLICATE_CLOSE_SOURCE가 전달되면 원본 핸들 종료

 

ex)

DulpcateHandle(A핸들, 256, B핸들, &val, ...);

DuplicateHandle(

      프로세스 A 핸들,                     //프로세스 A에 존재하는

      256,                                    //핸들 256의 정보를

      프로세스 B 핸들,                     //프로세스 B의 핸들 테이블에 등록한다.

      &val,                                  //등록된 핸들의 값은 변수 val에 저장한다.

      ...

);

프로세스를 생성할 때 핸들 테이블을 상속시키면 핸들 값 그대로 상속되지만 Duplicate Handle함수를 사용하면 새로운 핸들 값을 지정하여 테이블에 등록한다.

 

 

DuplicateHandle 사용예시

DuplicateHandle(

       프로세스 A 핸들,

       256

       프로세스 A 핸들,

       &val,

       ...

);

위 사용은 프로세스 A핸들 테이블의 256에 해당하는 핸들을 A프로세스 핸들테이블에 저장하라는 것이다.

DuplicateHandle함수에 의해 핸들이 복사되고 나면, Usage Count 도 증가한다. 복사된 핸들에 대해서도 CloseHandle 함수를 통해 핸들을 반환해야한다.

 

DuplicateHandle(

      GetCurrentProcess(), GetCurrentProcess(),

      GetCurrentProcess, &hProcess, ...

);

위와 같이 호출하면 진짜 핸들이 생성되어 핸들 테이블에 등록된다.

 

위와같이 DuplicateHandle함수로 자기자신의 핸들을 테이블에 등록할 때 beInheritHandle인자를 TRUE로 주어 상속을 하게 설정하면 등록된 부모프로세스의 핸들을 자식에게 상속시켜 줄 수 있다. 이후 부모프로세스의 핸들을 통해 자식 프로세스가 부모프로세스의 정보를 참조하거나 상태를 알 수 있다.

728x90

'Programming > Windows System Programming' 카테고리의 다른 글

스케줄링 알고리즘과 우선순위  (0) 2020.07.13
파이프 방식의 IPC  (0) 2020.07.12
프로세스 환경변수  (0) 2020.07.11
Signaled vs Non-Signaled  (0) 2020.07.11
프로세스간 통신(IPC) 메일 슬롯  (0) 2020.07.10
728x90

환경변수 : 프로세스 별로 별도의 메모리 공간에 저장하고 관리하는 문자열 데이터

환경변수 구조 : Key = Value

 

[Key,value]의 형태를 가진다. 자식프로세스 생성시, 자식 프로세스의 환경변수를 등록할 수 있고, 부모 프로세스의 환경변수를 상속시킬수도 있다.

 

환경변수등록 함수 SetEnvironmentVariable

BOOL SetEnvironmentVariable(

       LPCTSTR     lpName,

       LPCTSTR     lpValue,

);

1.lpName : Key에 해당하는 값을 지정한다. 이후 Key를 통해 value값을 참조한다.

2.lpValue : value에 해당하는 값을 지정한다.

 

환경변수 참조 함수 GetEnvironmentVariable

DWORD GetEnvironmentVariable(

     LPCTSTR lpName,

     LPTSTR   lpBuffer,

     DWORD  nSize

);

1.lpName : key를 전달한다. key에 해당하는 value를 얻는다.

2.lpBuffer : value를 저장하기 위한 메모리 주소

3.nSize : lpBuffer가 가리키는 메모리 크기

728x90
728x90

커널 오브젝트의 두가지 상태

커널 오브젝트는 특정 상황에 따라 두가지 상태를 지닌다. Signaled 상태(신호를 받은 상태)와 Non-signaled 상태(신호를 받지 않은 상태)이다.

 

커널 오브젝트의 상태 정보는 커널오브젝트를 구성하는 멤버 변수중 커널 오브젝트의 상태정보를 담당하는 변수에 저장된다. Non-Signaled 상태라면 FALSE, Signaled 상태라면 TRUE 값을 지니게 된다.

 

프로세스 커널 오브젝트 상태

1.프로세스 커널 오브젝트는 오브젝트가 생성될 때 만들어진다.

2.처음 커널 오브젝트가 생성되면 커널오브젝트의 상태는 Non-Signaled 상태이다.

3.프로세스가 종료되면 프로세스 커널오브젝트의 상태는 Signaled 가 된다.

프로세스가 실행중일 때에는 프로세스 커널 오브젝트의 상태가 Non-Signaled 이고 프로세스가 종료되면 운영체제에 의해 Signaled 상태가 된다.

 

 

Signaled 상태에서  Non-Signaled 상태로의 전환은 일어날 수 없다.

종료된 프로세스가 다시 시행되면 Signaled 에서 Non-Signaled상태가 되겠지만 종료된 프로세스는 다시 실행을 재개하지 못한다. 즉 프로세스 커널 오브젝트의 상태는 일단 Signaled가 되면 절대로 다시 Non-Signaled 상태로 변경되지 않는다.

 

커널 오브젝트의 상태를 확인하는 용도의 함수

핸들을 인자로 전달해서 커널 오브젝트의 상태를 확인하는 함수 WaitForSingleObject

DWORD WaitForSingleObject(

      HANDLE hHandle,

      DWORD dwMilliseconds

);

 

1.hHandle : 상태 확인을 원하는 커널 오브젝트의 핸들을 인자로 전달한다.

2.dwMilliseconds : hHandle이 가리키는 커널오브젝트가 Signaled 상태가 되었을 때 반환한다. 이 함수는 커널 오브젝트가 Signaled 상태가 될때까지 기다리는 함수이다. dwMilliseconds는 커널 오브젝트가 Signaled상태가 될때까지 기다릴 수 있는 최대 시간을 밀리 세컨드(Milliseconds)단위로 지정하는 용도로 사용되는 인자다. 상수 INFINITE를 인자로 전달하면, 커널 오브젝트가 Signaled 상태가 될때까지 반환하지 않고 무한정 기다린다.

 

WaitForSingleObject함수의 반환 결과

      vlaue                                      의미

WAIT_OBJECT_O                     커널 오브젝트가 Signaled 상태가 되었을 때 반환되는 값

WAIT_TIMEOUT                      커널 오브젝트가 Signaled 상태가 되지않고, dwMilliseconds인자를 통해서 설정된                                                  시간이 다 된 경우 반환되는 값

WAIT_ABANDONED                 오류 발생에 의해서 반환되는 경우에 반환되는 값

 

두번째 커널 오브젝트의 상태를 확인하는 함수

WaitForMultipleObject 함수

DWORD WaitForMultipleObjects(

           DWORD nCount,

           const HANDLE * lpHandles,

           BOOL bWaitAll,

           DWORD dwMilliseconds

);

1.nCount :  배열에 저장되어 있는 핸들 개수를 전달한다.

2.lpHandles : 핸들을 저장하고 있는 배열의 주소 정보를 전달한다. 이 주소를 시작으로 nCount개의 핸들이 대상이된다.

3.bWaitAll : TRUE 전달시 관찰대상 모두 Signaled 상태가 되기를 기다리고, FALSE 전달시 하나라도 Signaled 상태가                      되면 반환할 것인지를 전달한다.

4.dwMilliseconds : 타임 아웃을 설정하는 용도로 사용된다.

 

커널 오브젝트에 존재하는 종료코드(Exit Code)

프로세스가 종료되면서 전달하는 값을 종료코드(Exit Code)라 하고, 이 종료코드는 종료되는 프로세스의 커널 오브젝트에 저장된다. 커널 오브젝트에 저장되는 종료코드는 부모 프로세스가 GetExitCodeProcess함수 호출을 통해 자식 프로세스의 종료코드를 얻을 수 있다.

 

WaitForSingleObject 함수의 유용성

자식프로세스의 종료코드를 얻기 위해서는 자식프로세스들이 종료된 상태여야 한다. 부모 프로세스 입장에서 자식프로세스가 종료될때까지 기다렸다가 GetExitCodeProcess함수를 호출해야 한다. 부모프로세스가 자식프로세스 종료때까지 기다린느 것은 WaitForSingleObject 함수를 사용하면 충분히 가능하다.

 

CreateProcess 함수를 통해 얻게되는 핸들을 이용하여 WaitForSingleObject를 호출하여 INFINITE를 인자로 주면 부모프로세스는 자식프로세스가 종료될때 까지 블로킹(Blocking)상태에 놓인다. 이후 자식프로스세가 종료되면 자식 프로세스의 커널 오브젝트는 Signaled 상태가 되고 부모 프로세스는 블로킹 상태를 빠져 나와서 나머지 부분을 실행한다.

 

728x90
728x90

IPC : Inter-Process Communication 즉 프로세스 사이의 통신이다.

 

통신하고자 하는 프로세스가 만날 수 있는 여건

공유하는 메모리 영역이 허락되면 프로세스간 통신은 아주 쉬워진다. 그러나 프로세스들은 서로 만나서 데이터를 주고 받는것이 불가능하다. 각 프로세스는 자신에게 할당된 메모리 이외에는 접근이 불가능하기 때문이다.

 

 

운영체제는 프로세스가 자신에게 할당된 메모리 공간 이외의 영역에 접근하는것을 허용하지 않는다. 프로세스가 할당된 메모리 공간 이외의 다른 프로세스 영역에 접근할 수 있다면 여러 프로세스를 실행시켰을때 문제가 발생할 것이다. 이렇게 제한을 하는 이유는 안전성을 높이기 위해서 이다.

 

메일 슬롯 방식의 IPC

Windows에서는 다양한 IPC 기법을 제공한다. 그중 메일 슬롯 기반의 IPC도 있다.

 

메일슬롯(Mail Slot)원리

메일슬롯은 편지를 넣을 수 있는 가느다란 우체통의 입구를 의미한다. 메일 슬롯의 기본 원리는 "데이터를 주고받기 위해 프로세스가 우체통을 마련하는 것"이다.

 

데이터를 보내는 프로세스를 Sender라하고 데이터를 받는 프로세스를 Receiver라고 한다. Receiver는 밖에 메일슬롯이라고 하는 우체통을 하나 걸어둔다. Sender는 Receiver의 주소를 통해서 Receiver의 메일슬롯에 데이터를 날린다. 그러면 Receiver는 메일슬롯을 통해 데이터를 얻는다.

 

Receiver 프로세스는 CreateMailSlot 함수를 통해 우체통을 생성해야 한다.

CreateMailSlot함수

HANDLE CreateMailslot(

          LPCTSTR lpName,

          DWORD nMaxMessageSize,

          DWORD lReadTimeout,

          LPSECURITY_ATTRIBUTES lpSecurityAttributes

);

위 함수는 Mailslot의 핸들값을 반환한다.

 

첫번째 인자는 생성하는 메일 슬롯의 이름을 결정하는데 사용된다. 즉 주소(경로)를 지정하는 것이다.

ex)\\computername\mailslot\[path]name

 

두번째 인자는 메일 슬롯의 버퍼크기를 지정하는데 사용된다.

만약에 0이 전달될 경우 시스템이 허용하는 최대크기로 지정된다.

 

세번째인자인 lReadTimeout은 메일슬롯으로부터 읽어들일 데이터가 있다면 이 데이터들을 읽어들일때까지 ReadFile함수를 사용한다. 메일 슬롯이 비어있다면 데이터가 채워질 때까지 ReadFile함수는 반환하지 않고 블로킹 상태에 놓이게 된다.

lReadTimeout은 최대 블로킹시간을 밀리세컨드 단위로 지정하는데 사용된다.

 

lpSecurityAttributes : 핸들을 상속하기 위한 용도

 

메일슬롯을 통한 데이터 통신을 하기 위해서 Sender는 Receiver가 만들어 놓은 메일슬롯의 이름(경로)을 알아야 한다.

Sender는 CreateFile함수와 메일 슬롯 이름을 통해 데이터를 보내기 위한 데이터 스트림을 개방하고, 해당 스트림에 WriteFile함수를 통해 데이터를 전송한다.

 

Receiver는 ReadFile함수를 통해서 Mailslot에 전송된 데이터를 읽어들인다.

ReadFile 함수

BOOL ReadFile(

   HANDLE hFile,

   LPVOID lpBuffer,

   DWORD nNumberOfBytesToRead,

   LPDWORD lpNumberOfBytesRead,

   LPOVERLAPPED lpOverlapped

);

hFile : 메일슬롯의 핸들을 인자로 넣으면, 메일슬롯에 존재하는 데이터를 읽어들인다.

lpBuffer : 읽어들인 데이터를 저장할 버퍼를 지정하는 용도로 쓰인다.

nNumberOfBytesToread : 함수 호출이 완료된 이후에, 읽어들인 데이터 크기를 바이트 단위로 얻기위한 변수를 지정한다.

lpOverlapped : 일반적으로 NULL을 전달한다. 중첩된(overlapped) I/O 에서 사용됨

 

WriteFile 함수

BOOL WriteFile(

   HANDLE hFile.

   LPCVOID lpBuffer,

   DWORD nNumberOfBytesToWrite,

   LPDWORD lpNumberOfBytesWritten,

   LPOVERLAPPED lpOverlapped

);

hFile : 데이터를 읽어들일 파일을 설정한다.(데이터를 받을 파일)

lpBuffer : 전송할 데이터가 저장되어 있는 버퍼

nNumberOfBytesToWrite : 전송할 데이터 크기를 지정한다.

lpNumberOfBytesWritten : 함수호출 완료 후 전송된 실제 데이터의 크기를 바이트 단위로 얻기위한 변수의 주소 지정

 

Mailslot의 특징

Mailslot의 경우 Sender에서 Receiver로만 메시지를 전송하는 단방향 통신만 가능하다. 메일 슬롯은 한쪽 방향, 메일슬롯이 설치되어 있는 방향으로만 메시지를 전달할 수 있기 때문에 채팅 프로그램을 구현하기 위해서는 두개의 메일 슬롯을 생성해야만 한다.

또한 메일 슬롯은 브로드캐스팅(Broad casting)방식의 통신을 지원한다. 즉, 하나의 Sender는 한번의 메시지 전송으로  여러 Receiver에게 동일한 메시지를 동시에 전송하는 것이 가능하다.

 

메일 슬롯은 생성과 동시에 Usage Count가 1이다. 메일슬롯을 참조하는 프로세스는 메일슬롯을 생성한 프로세스 하나 뿐이기 때문이다. 메일 슬롯 뿐만아니라, 프로세스와 쓰레드를 제외한 다른 모든 커널 오브젝트는 생성과 동시에 Usage Count가 1이된다.

 

 

 

 

 

 

 

 

728x90
728x90

운영체제가 커널 오브젝트 소멸 시점을 결정하기 위해서는 일단 프로세스가 종료되어야 한다.

 

자식 프로세스의 종료코드는 자식 프로세스의 커널 오브젝트에 저장된다. 자식 프로세스가 종료될때 커널 오브젝트를 소멸시키면 문제가 발생할 수 있다.

 

GetExitCodeProcess 함수

GetExitCodeProcess 함수는 첫번째 인자로 전달된 핸들이 가리키는 프로세스가 반환하는 종료코드(Exit Code, 종료 상태를 알리는 값)을 얻기 위한 함수이다. 전달된 핸들의 프로세스가 종료되지 않고 실행중이라면, STILL_ACTIVE를 반환한다.

 

return에 의해 반환되는 값이나 exit 함수 호출시 전달되는 인자는 종료상황을 알리기 위해 사용된다. 일반적으로 종료코드 -1이나 0은 비정상적 종료를 알리고자 하는 경우에 많이 사용한다.

 

자식 프로세스의 종료코드는 자식 프로세스의 커널 오브젝트에 저장된다. 자식 프로세스가 종료될때 커널 오브젝트도 동시에 소멸된다면 부모 프로세스는 자식 프로세스의 종료코드를 얻을 수 없게 된다. 때문에 프로세스가 종료되었다고 해서 커널 오브젝트까지 동시에 소멸시키지는 않는다.

 

커널 오브젝트는 해당 커널 오브젝트를 참조하는 대상이 하나도 없을때 소멸시킨다.(windows 에서)

 

커널 오브젝트를 참조하는 프로세스가 하나라도 있을 시에는 커널 오브젝트는 소멸되지 않는다. Windows는 커널 오브젝트 소멸 시기를 결정하기 위해 UsageCount를 관리한다.

 

UsageCount는 커널 오브젝트에 접근가능한 대상의 수를 나타낸다.

프로세스는 생성과 동시에 커널오브젝트의 UsageCount가 1이된다. 그리고 자식 프로세스의 경우 UsageCount가 2가 된다. 왜냐하면 프로세스 본인과 부모 프로세스가 PROCESS_INFORMATION 구조체를 통해서 커널 오브젝트에 접근이 가능하기 때문이다.

 

프로세스는 GetCurrentProcess 함수 호출을 통해서 언제든 자신의 커널 오브젝트 참조를 위한 핸들을 얻을 수 있다. 그렇기 때문에 프로세스 생성시 Usage Count는 무조건 1 이상이다.

 

자식 프로세스가 종료 된 후 자식 커널 오브젝트

자식 프로세스가 종료되면 커널오브젝트에 접근하는 대상이 하나 줄기 때문에 Usage Count 값도 1 줄어든다.

 

CloseHandle 함수

CloseHandle 함수는 핸들을 반환하면서 커널 오브젝트의 Usage Count를 하나 감소시키는 기능을 지닌다.

프로세스의 경우 프로세스가 종료되는 시점에서도  UsageCount가 하나 감소한다.

 

CloseHandle 함수와 프로세스 종료는 별개이다. 프로세스 종료 요청시에 사용되는 함수는 TerminateProcess 이다. 이것은 강제 종료 요청함수이다.

 

바탕화면에 있는 아이콘을 더블 클릭해서 프로세스를 생성할 경우, 프로세스의 UsageCount는 2이다. 바탕화면도 일종의 프로세스이고 더블 클릭이라는 이벤트를 통해서 바탕화면 프로세스에게 프로세스 생성을 요청하는 것이기 때문이다.

728x90
728x90

커널 오브젝트의 종속 관계

커널 오브젝트는 Windows 운영체제에 종속적이다.

커널 오브젝트는 프로세스에 종속적인 것이 아니라, 운영체제에 종속적인 관계로 커널 오브젝트의 소멸 시점은 운영체제에 의해서 결정된다. 커널 오브젝트는 프로세스에 종속적인 것이 아니라 운영체제에 종속적인 관계로 여러 프로세스에 의해 접근 가능하다.

 

커널 오브젝트는 운영체제에 종속적이지만 핸들(핸들 테이블)은 프로세스에 종속적이다.

 

프로세스가 생성된 후 정보를 저장하는 PROCESS_INFORMATION pi; 에서 pi.hProcess를 통해 프로세스의 핸들을 받아온다.

 

PROCESS_INFORMATION 정의

typedef struct _PROCESS_INFORMATION

{

    HANDLE hProcess;                  //프로세스 핸들

    HANDLE hThread;                   //쓰레드 핸들

    DWORD dwProcessId;             //프로세스의 ID

    DWORD dwThreadId;              //쓰레드 ID

}PROCESS_INFORMATION;

 

위 정보들을 통해 자식 프로세스의 핸들을 얻을 수 있다.

핸들을 얻는 방법은 커널 오브젝트의 종류와 상황에 따라서 다양하다. 커널 오브젝트는 프로세스에 종속적인 것이 아니라 운영체제에 종속적인 관계로 여러 프로세스에 의해서 접근이(공유가) 가능하다.

 

dwProcessId는 새로 생성되는 프로세스 ID 정보로 채워지게 된다. 프로세스 핸들은 프로세스 커널 오브젝트를 가리키기 위한 것이고, 프로세스 ID는 커널 오브젝트가 아니라 프로세스 자체를 구분짓기 위한 것이다. hThread와 dwThreadId도 각각 핸들과 쓰레드 Id를 가리키는 것이다.

 

커널 오브젝트와 Usage Count

커널 오브젝트를 생성한 주체가 커널 오브젝트를 소멸시킬 권한을 가지고 있다. CreateProcess 함수 호출이 커널 오브젝트의 생성 원인이기는 하나 프로세스 생성에 대한 요청이며, 이 과정에서 운영체제가 프로세스를 관리하고자 커널 오브젝트를 생성한다. 즉 커널 오브젝트 생성 주체는 운영체제이다.

 

커널 오브젝트를 생성한 것은 프로세스가 아니고 프로세스에 종속적이지도 않기 때문에 프로세스가 소멸된다고 해서 커널 오브젝트가 소멸된다고 말할 수 없다.

 

CloseHandle 함수

BOOL Close Handle(

     HANDLE hObject;

);

위 함수는 핸들을 닫는다. 핸들을 반환한다고도 표현한다.

 

ex)

A프로세스는 실행과정에서 B프로세스를 생성한다. 그리고 나서 B프로세스의 핸들을 이용해서 CloseHandle 함수를 호출한다. 이는 B프로세스에 대해서 더이상 내가 관여할 바 아니니, B프로세스 핸들을 반환하라 라는 의미이다.

CloseHandle 함수가 호출되는 시점에서 B프로세스가 종료된다면, CloseHandle 함수는 프로세스 종료 및 커널 오브젝트 반환 기능이 있다고 결론 내릴 수 있다.

 

커널 오브젝트 소멸 예시

위 그림은 CloseHandle함수에 의해서 B프로세스 및 해당 커널 오브젝트가 소멸된다는 가정하에 진행되는 그림이다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

728x90

+ Recent posts