본문 바로가기
Programming/7. Device Driver

고급 기법 & 실무 적용 - 20화 KMDF 드라이버 개발 실습

by S.W 2026. 6. 10.
Windows 커널 & 드라이버 시리즈 — 20화

KMDF 드라이버 개발 실습 — I/O 큐와 요청 처리 완전 정복

📅 2026 ⏱ 읽기 약 16분 🏷 KMDF / WDFQUEUE / WDFREQUEST / DeviceIoControl
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 #실습