OVERLAPPED 구조체 설정

 

 오버랩, 비오버랩에 관한 이야기는 이전 포스트에서 한번 언급을 했었다. 요번에는 overlapped 읽기 쓰기에 사용되는 OVERLAPPED 구조체가 무엇인지, 그리고 어떤 의미를 가지고 있는지에 대해 알아 보겠다.

 

 그런데 구조체 내부 구조와 사용 방법에 대해 알아보기에 앞서, 먼저 OVERLAPPED 구조체를 왜 쓰는 것인가에 대해 생각해 볼 필요가 있다. 이전 포스트에서 overlapped 방식의 통신에서는 read와 write가 실제 작업 종료여부와는 상관 없이 호출하자 마자 끝나버린다고 했었다.

 

 그럼 자연히 드는 한가지 의문점은 언제 끝나는지 어떻게 아냐 라는 것이다. 일단 호출해 버리면 바로 반환되어 버리기 때문에, 언제 읽기 쓰기 작업이 종료되었는지 그리고 얼만큼을 성공적으로 썻는지에 대한 정보를 알기가 어렵다.

 

이에 대한 해답으로 나온 것이 바로 OVERLAPPED 구조체로, C#으로 따지자면 비동기 메소드의 ASyncResult 객체와 쓰임새가 비슷하다고 생각해도 좋다.

 

그럼이제 OVERLAPPED 구조체에 대해 살펴보자. OVERLAPPED 구조체는 아래와 같다.

 

typedef struct _OVERLAPPED{

 ULONG_PTR internal;

 ULONG_PTR internalHigh;

 DWORD Offset;

 DWORD OffsetHigh;

 HANDLE hEvent;

}OVERLAPPED;

 

여러가지 인자가 있지만, 시리얼 통신에서 알아 두어야만 하는 인자는 hEvent 뿐이다. 이 이벤트는 overlapped 읽기 쓰기 작업이 종료됨과 동시에 set상태가 되어 이 이벤트 객체를 waitForSingleObject와 같은 함수로 기다릴 수도 있다. (이 이벤트는 bManualReset을 TRUE로 설정하여 생성해도 read, write함수에 인자로 넣는 순간 자동으로 Reset되는 특이성(?) 이 있다.)

 

참고로 시리얼 통신을 공부하면서 가장 헷갈렸던게 바로 OVERLAPPED 구조체의 Offset 인자인데 결론적으로 이야기 하자면 통신 Device에서는 전혀 몰라도 되긴 하다.

 

설명을 좀 하자면.. 먼저 이 OVERLAPPED 구조체가 쓰이는 read write 함수가 어디에 쓰이는지에 대해 알아야 한다. 바로 파일의 비동기 입출력에 쓰인다. 앞서 올린 포스트에서 CreateFile함수가 이름에 충실하게 실제 파일, 그리고 통신 Device까지도 우리가 가지고 작업 할 수 있도록 파일디스크립터 ( 핸들 )을 제공 한다고 했었다.

 

만약 내가 실제로 하드디스크에 100KB 짜리 파일을 쓴다고 생각해 보자. 동기 (NonOverlapped) 쓰기 작업이라고 하면 항상 내가 파일에 쓴만큼 파일포인터는 앞으로 전진할테고 계속해서 나는 그냥 쓰기만 하면 된다. 예를 들어 넓은 논에 모내기를 하는데 나 혼자 심게 되면 어디서 부터 심을지 생각하지 않고, 그냥 처음부터 모를 심을 때 마다 바로 이전에 마지막으로 심었던 모자리 바로 앞에다가 심으면 그만이다.

 

그러나 비동기 (Overlapped) 쓰기 작업은 다르다. 여러 쓰레드가 동시에 읽거나 쓰기 때문에 현재 파일 포인터를 기준으로 쓰거나 읽으려고 하면 차질이 생긴다. 모내기를 예로 들면 2명이서 모내기를 하려고 하는 것과 같다. "미리 나는 0부터 심어 나갈 테니까 너는 50부터 심어나가" 라고 심어나갈 초기 위치를 협의 해야 100짜리 논에 모를 심어 낼 수 있다.

 

따라서 진짜로 파일 쓰기나 읽기에서는 직접 수동으로 읽은만큼 offset을 변경해야 한다.

 

그러나 통신장치에서는 다르다. 그냥 내가 쓰기버퍼에 쓰면 바로바로 FIFO(First In First Out)으로 상대방 읽기 버퍼로 전송되어 쌓이게 된다. (QEUE)  따라서 항상 통신 장치를 Overlapped방식으로 읽기 쓰기 할 때는 이 offset이 0이어야 하며, 0이 아닌 값이 들어가 있어도 무시된다. (0으로 놔두는 것이 좋다.)

 

마지 막으로 OVERLAPPED 구조체의 hEvent를 만들어 주는 것을 보도록 하겠다.

 

 m_OverlappedRead.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

 

참고로 CreateEvent는 이벤트 핸들을 반환하며 아래와 같은 함수원형을 가진다.

 

HANDLE WINAPI CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,
                                          BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName);
 

 

보통 인자중에 처음과 마지막 인자는 null을 넣어서 사용한다. 첫 인자는 자식 프로세스에 이 이벤트가 상속되지 않음을 명시하는 거라는데, 다른 프로세스까지 이 이벤트가 영향을 미치지 않도록 하는 의미인것 같다. 보통 다중 프로세스 프로그램은 안짜고 다중 스레드만 짜기 때문에 난 그냥 무시하고 NULL을 넣어서 쓴다. 마지막 인자는 이 이벤트의 이름을 의미하는 인자로 그냥 이름 없는 이벤트를 쓰기위해 NULL을 넣는다.

 

두번째 인자 bManualReset은 좀 중요하다. 이벤트는 상태가 Set, Reset 두 가지가 있는데 WaitForSingleObject와 같은 함수로 이 이벤트를 기다리면 Set상태가 될 때까지 블록 상태가 된다. 그리고 Set 상태가 되면 블록이 해제되고 WaitForSingleObject 이후 단계의 코드로 넘어 가는데 이때 bManualReset이 TRUE인 이벤트는 자동으로 다시 Reset상태가 되지 않는다. FALSE인 이벤트는 지가 자동으로 다음 코드로 넘어갈때 다시 Reset상태로 변경된다.

 

세번째 인자는 이 이벤트를 만들 때, 초기 상태를 SET으로 만들꺼냐 말꺼냐를 묻는 인자다 TRUE이면 초기 상태가 SET이 된다.

Posted by 굿쟌
,