4. IOCTL同步处理:同步IOCTL实现、EvtIoDeviceControl回调、完成例程、状态返回
在KMDF驱动中,IOCTL(I/O控制码)是应用程序与驱动进行控制交互的核心通道。同步IOCTL处理模式要求驱动在EvtIoDeviceControl回调函数中完成所有操作,并在返回前将结果提交给I/O管理器。本章将深入剖析同步IOCTL的完整生命周期,包括回调注册、参数解析、完成例程协作以及状态码的正确返回。
4.1 同步IOCTL处理模型
同步IOCTL的核心特征是:调用线程阻塞等待,直到驱动回调返回。KMDF框架通过序列化机制确保每个请求在独立的回调上下文中被处理,无需驱动开发者手动管理并发锁。
4.2 EvtIoDeviceControl回调注册与实现
驱动必须通过WDF_IO_QUEUE_CONFIG结构体配置队列,并指定EvtIoDeviceControl回调函数。KMDF框架在接收到IOCTL请求时,自动调用此回调。
4.2.1 队列配置与回调注册
// 队列配置示例
NTSTATUS
QueueInitialize(
WDFDEVICE Device
)
{
WDF_IO_QUEUE_CONFIG queueConfig;
WDFQUEUE queue;
NTSTATUS status;
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(
&queueConfig,
WdfIoQueueDispatchSequential // 顺序分发,保证同步
);
// 注册IOCTL处理回调
queueConfig.EvtIoDeviceControl = EvtIoDeviceControl;
status = WdfIoQueueCreate(
Device,
&queueConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&queue
);
return status;
}
4.2.2 回调函数签名与参数解析
EvtIoDeviceControl回调接收四个关键参数,驱动通过它们获取请求的全部上下文:
| 参数 | 类型 | 说明 |
|---|---|---|
| Queue | WDFQUEUE | 接收请求的I/O队列句柄 |
| Request | WDFREQUEST | IOCTL请求对象,包含控制码和缓冲区 |
| OutputBufferLength | size_t | 输出缓冲区大小(字节) |
| InputBufferLength | size_t | 输入缓冲区大小(字节) |
| IoControlCode | ULONG | IOCTL控制码,标识具体操作 |
// EvtIoDeviceControl回调实现框架
VOID
EvtIoDeviceControl(
WDFQUEUE Queue,
WDFREQUEST Request,
size_t OutputBufferLength,
size_t InputBufferLength,
ULONG IoControlCode
)
{
NTSTATUS status = STATUS_SUCCESS;
PDEVICE_CONTEXT devContext = GetDeviceContext(Queue);
// 步骤1:根据控制码分发
switch (IoControlCode)
{
case IOCTL_GET_STATUS:
status = ProcessGetStatus(Request, OutputBufferLength);
break;
case IOCTL_SET_CONFIG:
status = ProcessSetConfig(Request, InputBufferLength);
break;
default:
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
// 步骤2:完成请求(同步模式)
WdfRequestComplete(Request, status);
}
4.3 完成例程与状态返回机制
完成例程(Completion Routine)是KMDF提供的请求完成通知机制。在同步IOCTL中,虽然驱动直接调用WdfRequestComplete,但框架内部仍会触发完成例程链,用于执行清理、统计或后续处理。
4.3.1 完成例程的注册与触发时机
完成例程通过WdfRequestSetCompletionRoutine注册,在请求完成时被框架调用。同步IOCTL中,完成例程在WdfRequestComplete返回之前被同步调用。
4.3.2 状态码返回规则
同步IOCTL的状态码通过WdfRequestComplete的第二个参数传递。状态码必须遵循以下规则:
- STATUS_SUCCESS:操作成功完成,输出缓冲区包含有效数据。
- STATUS_BUFFER_TOO_SMALL:输出缓冲区不足以容纳结果数据。
- STATUS_INVALID_PARAMETER:输入参数格式错误或超出范围。
- STATUS_INVALID_DEVICE_REQUEST:不支持的IOCTL控制码。
- STATUS_DEVICE_NOT_READY:设备未就绪,无法处理请求。
4.4 完整同步IOCTL实现示例
以下示例展示了一个完整的同步IOCTL处理流程,包含输入验证、缓冲区访问和状态返回:
// 定义IOCTL控制码
#define IOCTL_GET_DEVICE_TEMP \
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_SET_THRESHOLD \
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_IN_DIRECT, FILE_ANY_ACCESS)
// 同步IOCTL处理回调
VOID
EvtIoDeviceControl(
WDFQUEUE Queue,
WDFREQUEST Request,
size_t OutputBufferLength,
size_t InputBufferLength,
ULONG IoControlCode
)
{
NTSTATUS status = STATUS_SUCCESS;
PDEVICE_CONTEXT devCtx = GetDeviceContext(Queue);
size_t bytesReturned = 0;
switch (IoControlCode)
{
case IOCTL_GET_DEVICE_TEMP:
{
// 验证输出缓冲区大小
if (OutputBufferLength < sizeof(ULONG))
{
status = STATUS_BUFFER_TOO_SMALL;
bytesReturned = sizeof(ULONG);
break;
}
// 获取输出缓冲区(METHOD_BUFFERED)
PVOID outputBuffer = WdfRequestGetOutputBuffer(Request);
if (outputBuffer == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
// 模拟读取设备温度
ULONG temperature = ReadDeviceTemperature(devCtx);
RtlCopyMemory(outputBuffer, &temperature, sizeof(ULONG));
bytesReturned = sizeof(ULONG);
status = STATUS_SUCCESS;
break;
}
case IOCTL_SET_THRESHOLD:
{
// 验证输入缓冲区大小
if (InputBufferLength < sizeof(ULONG))
{
status = STATUS_INVALID_PARAMETER;
break;
}
// 获取输入缓冲区(METHOD_IN_DIRECT)
WDFMEMORY inputMem;
status = WdfRequestRetrieveInputMemory(Request, &inputMem);
if (!NT_SUCCESS(status))
{
break;
}
PULONG threshold = (PULONG)WdfMemoryGetBuffer(inputMem, NULL);
if (threshold == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
// 验证阈值范围
if (*threshold > MAX_THRESHOLD)
{
status = STATUS_INVALID_PARAMETER;
break;
}
// 设置设备阈值
status = SetDeviceThreshold(devCtx, *threshold);
break;
}
default:
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
// 完成请求:设置状态和返回字节数
WdfRequestCompleteWithInformation(Request, status, bytesReturned);
}
4.5 同步IOCTL与中断处理的协同点
在同步IOCTL中,如果驱动需要等待中断事件(如等待硬件完成某个操作),必须使用同步等待机制,而不能在EvtIoDeviceControl中直接阻塞。KMDF提供了WdfWaitLock和WdfInterruptAcquireLock等同步原语,用于在IOCTL回调中安全等待中断处理完成。
| 协同场景 | 实现方式 | 注意事项 |
|---|---|---|
| IOCTL等待中断完成 | 使用WdfInterruptSynchronize在DPC中设置事件,IOCTL回调等待事件 | 避免在IRQL > PASSIVE_LEVEL时等待 |
| IOCTL读取中断状态 | 在EvtIoDeviceControl中直接读取设备寄存器 | 确保寄存器访问受中断锁保护 |
| IOCTL触发中断操作 | 写入设备寄存器触发中断,等待ISR/DPC完成 | 使用KEVENT进行同步等待 |
4.6 调试与验证要点
同步IOCTL的调试重点在于验证请求完成路径和状态码正确性:
- 检查所有代码路径:确保每个case分支和错误处理分支都调用了WdfRequestComplete。
- 验证缓冲区访问:使用WdfRequestRetrieveInputMemory/WdfRequestGetOutputBuffer前,先检查缓冲区大小。
- 监控返回状态:在应用层检查DeviceIoControl的返回值,确认驱动返回的状态码符合预期。
- 使用WDF验证器:启用KMDF验证器(WdfVerifier),自动检测未完成的请求和无效的缓冲区访问。
// 应用层测试代码(C++示例)
DWORD bytesReturned;
BOOL result = DeviceIoControl(
hDevice,
IOCTL_GET_DEVICE_TEMP,
NULL, 0, // 无输入缓冲区
&temperature, sizeof(temperature),
&bytesReturned,
NULL // 同步模式,不使用OVERLAPPED
);
if (!result)
{
DWORD error = GetLastError();
// 根据错误码判断驱动返回的状态
printf("IOCTL failed with error: %u\n", error);
}
else
{
printf("Temperature: %u\n", temperature);
}