본문 바로가기
개발/Windows

DeviceIoControl에서 Overlapped I/O 사용하기

by lucidmaj7 2020. 2. 18.
728x90
반응형

DeviceIoControl에서 Overlapped I/O 사용하기

커널드라이버에서 발생한 빈번한 대량의 이벤트를 유저모드에서 하나하나 모니터링 하기위해서 이벤트를 이용하여 동기화, polling으로 이벤트를 계속 수신하거나 하는 방법
등 다양한 방법이 쓰일 수 있다.

문제의 시나리오

비교적 간단한 polling으로 이벤트를 계속 수신한다고 생각해보자. 커널드라이버에서는 발생한 이벤트를 큐에 큐잉하고 유저모드 애플리케이션에서는 IOCTL을 발생시켜 큐의 내용을 꺼내온다. 이런식의 로직에서 유저모드 애플리케이션은 While과 같은 반복문을 수행하며 DeviceIoControl를 호출하여 드라이버와 계속 통신할 것이다.

while(true)
{
    BOOL ret =    DeviceIoControl(hDevice, IOCTL_RECV, &data, sizeof(DATA), 0,0,&dwReturn,NULL);
    if(ret)
    {
    // 처리..
    }
}

DeviceIoControl Blocking 발생

앞서 사용한 로직에서는 커널드라이버에서 이벤트가 발생할때 까지 DeviceIoControl에서 Blocking이 발생한다. DeviceIoControl에서 Blocking이 발생한다는 것은 유저모드 애플리케이션에서 DeviceIoControl이 리턴되지 않고 있다는 것이다. 여기서 문제가 발생하는데 커널드라이버는 다양한 IOCTL을 받을 수 있게 구현되어 있지만 이벤트를 수신하는 IOCTL에 대한 요청이 끝나지 않아 다른 DeviceIoControl의 호출은 처리할 수 없게 된다. 그 이유는 File Object의 특성상 한번에 하나의 Operation 처리 할 수 있기 때문이라고 한다.

( https://community.osr.com/discussion/288276/can-a-deviceiocontrol-block )

Without FILE_FLAG_OVERLAPPED in the CreateFile call, regardless of how the driver is written, the operating system will only allow one outstanding I/O operation at a time on that file handle. It doesn't matter how many threads you have. All other calls will block until the first completes.`

해결책은 FILE_FLAG_OVERLAPPED Overlapped I/O

CreateFile의 원형을 보면 다음과 같다.

HANDLE CreateFileA(
  LPCSTR                lpFileName,
  DWORD                 dwDesiredAccess,
  DWORD                 dwShareMode,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  DWORD                 dwCreationDisposition,
  DWORD                 dwFlagsAndAttributes,
  HANDLE                hTemplateFile
);

https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
Windows System 프로그래밍을 배우면서 파일 처리 부분을 배울 때 Overlapped I/O에 관한 이야기가 나왔다. 언제 쓰일지 모르던 Overlapped I/O는 여기에 적용 할 수 있다. CreateFiledwFlagsAndAttributesFILE_FLAG_OVERLAPPED플래그를 설정하면 File I/O시 비동기 Overlapped I/O 로 처리 할 수 있다. 디바이스는 파일 Object와 대응되는데 역시 디바이스 I/O를 Overlapped I/O로 처리 할 수 있다.

Overlapped I/O 적용

아래와 같이 FILE_FLAG_OVERLAPPED플래그를 이용하여 디바이스를 오픈한다.

HANDLE    m_hDevice = CreateFile(_T("\\\\.\\mydev"),
        GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, INVALID_HANDLE_VALUE);

아래는 이벤트를 수신하기 위한 쓰레드의 Loop의 일부이다.


//.....
HANDLE event;

OVERLAPPED overlapped;
event = CreateEvent(NULL, FALSE, FALSE, NULL);
memset(&overlapped, 0, sizeof(overlapped));
overlapped.hEvent = event;
while (pDlg->m_hDevice)
{


    DeviceIoControl(pDlg->m_hDevice, IOCTL_RECV, &ioctl, sizeof(MY_IOCTL), 0, 0, &dwReturn, &overlapped);

    DWORD iolen0;

    if (GetLastError() != ERROR_IO_PENDING ||
        !GetOverlappedResult(pDlg->m_hDevice, &overlapped, &iolen0, TRUE))
    {
        OutputDebugString(_T("DeviceIoControl fail."));
        continue;
    }
    dwReturn = iolen0;
        //처리...

    }

위와 같이 OVERLAPPED구조체에 이벤트를 설정하여 DeviceIoControl에게 전달 하면 DeviceIoControl은 호출 즉시 리턴된다. 요청한 IOCTL이 다 처리 되기전에 리턴되는 것이다. 때문에 GetOverlappedResult 함수를 이용하여 I/O가 끝날때까지 Wait하게 된다. 드라이버에서는 이벤트를 처리하고 Complete를 하면 I/O가 완료된다.
이렇게 이벤트 수신을 처리하는 중에도 다른 쓰레드에서 IOCTL을 처리 할 수 있다.

728x90
반응형

댓글