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 처리 할 수 있기 때문이라고 한다.
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는 여기에 적용 할 수 있다. CreateFile
의 dwFlagsAndAttributes
에 FILE_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을 처리 할 수 있다.
'개발 > Windows' 카테고리의 다른 글
Hyper-V 2세대 VM에서 Com Port 활성화 하기 / Windbg (0) | 2020.03.27 |
---|---|
UINT32[4] IPv6를 스트링으로 변환하기 / MFC, C++ (0) | 2020.02.19 |
UINT32 정수형 IP주소 스트링 변환하기 / MFC / C++ (0) | 2020.02.13 |
MFC AfxBeginThread 사용시 주의: m_bAutoDelete (0) | 2020.01.30 |
Windows 커널모드 메모리 할당과 페이지 단편화 -ExAllocatePoolWithTag (0) | 2020.01.14 |
댓글