Windows 커널 & 드라이버 시리즈 — 20화
KMDF 드라이버 개발 실습 — I/O 큐와 요청 처리 완전 정복
19화에서 WDF의 개념을 잡았으니, 이번 화에서는 실제로 KMDF 드라이버를 처음부터 만들어볼게요. WDM으로 9~13화에서 만들었던 것과 같은 기능 — 디바이스 열기/닫기, DeviceIoControl 처리 — 을 KMDF로 구현하면서 두 방식의 차이를 직접 느껴보시죠.
WDFQUEUE — IRP 큐의 현대적 버전
WDM에서는 IRP를 직접 받아서 직접 처리했어요. KMDF에서는 WDFQUEUE가 중간에서 IRP를 받아 관리합니다. 큐가 없으면 드라이버는 아무 요청도 받을 수 없어요.
큐의 디스패치 방식이 세 가지 있어요:
| 방식 | 특징 | 사용 시점 |
|---|---|---|
| WdfIoQueueDispatchSequential | 한 번에 하나씩만 콜백 호출 | 동기화가 필요한 간단한 드라이버 |
| WdfIoQueueDispatchParallel | 여러 요청을 동시에 콜백 호출 | 고성능, 병렬 처리 가능한 드라이버 |
| WdfIoQueueDispatchManual | 자동 호출 없음, 드라이버가 직접 꺼냄 | 비동기, 폴링 방식 |
완전한 KMDF 드라이버 예제
DeviceIoControl을 처리하는 KMDF 드라이버 전체 코드예요. WDM 버전(13화)과 비교해보세요:
#include <ntddk.h> #include <wdf.h> #define MYKMDF_POOL_TAG 'fdmK' #define IOCTL_MYKMDF_HELLO CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) // 디바이스별 컨텍스트 (WDM의 DeviceExtension에 해당) typedef struct _MY_DEVICE_CONTEXT { ULONG RequestCount; WDFSPINLOCK CounterLock; } MY_DEVICE_CONTEXT, *PMY_DEVICE_CONTEXT; // 컨텍스트 접근 헬퍼 선언 WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(MY_DEVICE_CONTEXT, GetDeviceContext) // 콜백 함수 선언 EVT_WDF_DRIVER_DEVICE_ADD MyEvtDeviceAdd; EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL MyEvtIoDeviceControl; // DriverEntry NTSTATUS DriverEntry( _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) { WDF_DRIVER_CONFIG config; WDF_DRIVER_CONFIG_INIT(&config, MyEvtDeviceAdd); return WdfDriverCreate(DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, WDF_NO_HANDLE); } // DeviceAdd 콜백 NTSTATUS MyEvtDeviceAdd( _In_ WDFDRIVER Driver, _In_ PWDFDEVICE_INIT DeviceInit) { UNREFERENCED_PARAMETER(Driver); NTSTATUS status; WDFDEVICE device; // 디바이스 이름 설정 DECLARE_CONST_UNICODE_STRING(devName, L"\\Device\\MyKmdfDevice"); DECLARE_CONST_UNICODE_STRING(symLink, L"\\DosDevices\\MyKmdfDevice"); WdfDeviceInitAssignName(DeviceInit, &devName); // 컨텍스트 속성 설정 WDF_OBJECT_ATTRIBUTES devAttr; WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&devAttr, MY_DEVICE_CONTEXT); status = WdfDeviceCreate(&DeviceInit, &devAttr, &device); if (!NT_SUCCESS(status)) return status; // 심볼릭 링크 status = WdfDeviceCreateSymbolicLink(device, &symLink); if (!NT_SUCCESS(status)) return status; // 컨텍스트 초기화 PMY_DEVICE_CONTEXT ctx = GetDeviceContext(device); ctx->RequestCount = 0; WDF_OBJECT_ATTRIBUTES lockAttr; WDF_OBJECT_ATTRIBUTES_INIT(&lockAttr); lockAttr.ParentObject = device; // 디바이스 삭제 시 락도 자동 삭제! WdfSpinLockCreate(&lockAttr, &ctx->CounterLock); // I/O 큐 생성 (Sequential — 간단한 예제) WDF_IO_QUEUE_CONFIG queueConfig; WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE( &queueConfig, WdfIoQueueDispatchSequential); queueConfig.EvtIoDeviceControl = MyEvtIoDeviceControl; WDFQUEUE queue; status = WdfIoQueueCreate(device, &queueConfig, WDF_NO_OBJECT_ATTRIBUTES, &queue); return status; } // DeviceIoControl 콜백 — WDM의 DispatchIoControl에 해당 VOID MyEvtIoDeviceControl( _In_ WDFQUEUE Queue, _In_ WDFREQUEST Request, _In_ size_t OutputBufferLength, _In_ size_t InputBufferLength, _In_ ULONG IoControlCode) { UNREFERENCED_PARAMETER(InputBufferLength); NTSTATUS status = STATUS_SUCCESS; size_t bytesWritten = 0; WDFDEVICE device = WdfIoQueueGetDevice(Queue); PMY_DEVICE_CONTEXT ctx = GetDeviceContext(device); switch (IoControlCode) { case IOCTL_MYKMDF_HELLO: { if (OutputBufferLength < sizeof(ULONG)) { status = STATUS_BUFFER_TOO_SMALL; break; } // 출력 버퍼 가져오기 (WDF가 자동으로 크기 검증) PVOID outBuf; status = WdfRequestRetrieveOutputBuffer( Request, sizeof(ULONG), &outBuf, NULL); if (!NT_SUCCESS(status)) break; // 카운터 증가 후 반환 WdfSpinLockAcquire(ctx->CounterLock); ctx->RequestCount++; *(PULONG)outBuf = ctx->RequestCount; WdfSpinLockRelease(ctx->CounterLock); bytesWritten = sizeof(ULONG); break; } default: status = STATUS_INVALID_DEVICE_REQUEST; break; } // 요청 완료 — WDM의 IoCompleteRequest에 해당 WdfRequestCompleteWithInformation(Request, status, bytesWritten); }WDM vs KMDF 코드 비교
WDM 13화 코드와 비교하면 KMDF에서 사라진 것들이 보여요:
- IoCreateDevice / IoCreateSymbolicLink 직접 호출 없음 → WdfDeviceCreate, WdfDeviceCreateSymbolicLink
- MajorFunction 배열 직접 채우기 없음 → WDF_IO_QUEUE_CONFIG에 콜백 등록
- DriverUnload 등록 없음 → WDF가 알아서 정리
- IoGetCurrentIrpStackLocation 없음 → 콜백 파라미터로 직접 받음
- IoCompleteRequest 없음 → WdfRequestCompleteWithInformation
- DeviceExtension 수동 관리 없음 → WDF_DECLARE_CONTEXT_TYPE으로 안전하게
WdfRequestRetrieveOutputBuffer의 장점
WDM에서는 Irp->AssociatedIrp.SystemBuffer를 직접 접근하고, 크기 검증을 수동으로 했어요. KMDF의 WdfRequestRetrieveOutputBuffer는 최소 크기를 파라미터로 받아서 자동으로 검증해주고, 크기가 부족하면 STATUS_BUFFER_TOO_SMALL을 반환해요. 보안 버그가 줄어드는 이유가 여기에 있습니다.
💡 컨텍스트 타입은 하나의 오브젝트에 여러 개 붙일 수 있어요
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME으로 정의한 컨텍스트는 같은 WDFOBJECT에 여러 개 연결할 수 있어요. 예를 들어 WDFDEVICE에 디바이스 컨텍스트와 설정 컨텍스트를 따로 붙일 수 있어요. 인터페이스 분리 설계에 유용합니다.
⚠️ WDFQUEUE의 기본 디바이스는 Default Queue여야 해요
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE로 만든 큐가 하나는 있어야 I/O Manager가 보내는 IRP를 처리할 수 있어요. 기본 큐 없이 특수 목적 큐만 있으면 처리되지 않는 IRP가 생겨요.
✅ 20화 요약
- WDFQUEUE가 IRP를 받아서 관리합니다. Sequential/Parallel/Manual 세 가지 디스패치 방식이 있어요.
- 디바이스별 상태는 WDF_DECLARE_CONTEXT_TYPE으로 정의한 컨텍스트 구조체에 저장합니다.
- WdfRequestRetrieveOutputBuffer/InputBuffer가 버퍼 크기 검증을 자동으로 해줍니다.
- 요청 완료는 WdfRequestCompleteWithInformation으로 합니다.
- 오브젝트의 ParentObject를 설정하면 부모 삭제 시 자식이 자동으로 정리됩니다.
다음 화
21화 — 필터 드라이버 아키텍처: 기존 디바이스 스택에 끼어들기 →
#KMDF #WDFQUEUE #WDFREQUEST #WdfDeviceCreate #실습
'Programming > 7. Device Driver' 카테고리의 다른 글
| 고급 기법 & 실무 적용 - 21화 필터 드라이버 아키텍처 (0) | 2026.06.11 |
|---|---|
| 고급 기법 & 실무 적용 - 19화 WDF 프레임워크 소개 (0) | 2026.06.09 |
| 드라이버 개발 핵심 - 18화 인터럽트, ISR, DPC (0) | 2026.06.08 |
| 드라이버 개발 핵심 - 17화 MDL (0) | 2026.06.05 |
| 드라이버 개발 핵심 - 16화 커널 메모리 풀 관리 (0) | 2026.06.04 |