3. 派遣函数基础:IRP与派遣函数概念、MajorFunction与MinorFunction、派遣函数注册机制

3.1 IRP 与派遣函数概念

在 Windows 内核中,I/O 请求包(IRP, I/O Request Packet)是驱动与操作系统之间传递 I/O 操作的核心数据结构。当用户模式应用程序调用 ReadFile、WriteFile、DeviceIoControl 等 API 时,I/O 管理器会创建一个对应的 IRP,并将其发送到目标驱动对象。

派遣函数(Dispatch Function) 是驱动中处理特定类型 IRP 的回调例程。每个派遣函数对应一个 MajorFunction 代码,驱动通过注册这些函数来声明自己能够处理哪些 I/O 操作。

核心要点: IRP 是“请求”,派遣函数是“处理者”。驱动不处理未注册的 MajorFunction,I/O 管理器会返回默认状态(通常为 STATUS_INVALID_DEVICE_REQUEST)。

下图展示了 IRP 从用户态到驱动派遣函数的完整流转路径:

IRP 流转路径:用户态 → I/O 管理器 → 驱动派遣函数 用户态应用 ReadFile / WriteFile I/O 管理器 创建 IRP / 分发 驱动对象 MajorFunction 表 系统调用 IRP 发送 驱动派遣函数表(MajorFunction 数组) IRP_MJ_CREATE IRP_MJ_READ IRP_MJ_WRITE IRP_MJ_DEVICE_CONTROL … 其他 索引

3.2 MajorFunction 与 MinorFunction

IRP 的主功能码 MajorFunction 标识 I/O 操作的大类,而 MinorFunction 则在大类下进一步细分操作。MinorFunction 通常用于 SCSI、键盘、鼠标、IR 等类驱动或总线驱动,用于区分同一大类下的不同子操作。

MajorFunction 常量 数值 说明 常见 MinorFunction 示例
IRP_MJ_CREATE 0x00 打开设备(CreateFile) 无
IRP_MJ_CLOSE 0x02 关闭设备(CloseHandle) 无
IRP_MJ_READ 0x03 从设备读取数据 无
IRP_MJ_WRITE 0x04 向设备写入数据 无
IRP_MJ_DEVICE_CONTROL 0x0E 设备 I/O 控制(DeviceIoControl) 无
IRP_MJ_INTERNAL_DEVICE_CONTROL 0x0F 内部设备 I/O 控制(驱动间通信) SCSI、HID 等类驱动定义
IRP_MJ_PNP 0x1B 即插即用事件 IRP_MN_START_DEVICE、IRP_MN_STOP_DEVICE、IRP_MN_REMOVE_DEVICE
IRP_MJ_POWER 0x1C 电源管理事件 IRP_MN_SET_POWER、IRP_MN_QUERY_POWER
IRP_MJ_SYSTEM_CONTROL 0x1F WMI 系统控制 IRP_MN_QUERY_ALL_DATA、IRP_MN_CHANGE_SINGLE_INSTANCE
调试提示: 在 WinDbg 中,使用 !irp <IRP 地址> 可以查看 IRP 的 MajorFunction 和 MinorFunction。对于 PnP 和 Power IRP,MinorFunction 是判断具体事件类型的关键字段。

下图展示了 MajorFunction 与 MinorFunction 在 IRP 中的层级关系:

IRP 功能码层级:MajorFunction → MinorFunction IRP 结构(简化) MajorFunction 例如:IRP_MJ_PNP (0x1B) MinorFunction 例如:IRP_MN_START_DEVICE (0x00) 细分 完整事件:IRP_MJ_PNP + IRP_MN_START_DEVICE → 启动设备

3.3 派遣函数注册机制

在 KMDF 框架中,派遣函数的注册通过 WdfDeviceCreate 之后的 WdfDeviceInitAssignWdmIrpPreprocessCallback 或更常见的 WdfDeviceIoControl、WdfDeviceRead 等框架封装方法完成。但底层机制仍然基于 WDM 的 MajorFunction 表。

KMDF 驱动注册派遣函数的典型步骤如下:

  1. DriverEntry 入口: 调用 WdfDriverCreate 创建 WDF 驱动对象。
  2. EvtDriverDeviceAdd 回调: 在此回调中创建 WDF 设备对象。
  3. 配置 WDFDEVICE_INIT: 通过 WdfDeviceInitAssignWdmIrpPreprocessCallback 注册需要预处理的自定义 MajorFunction。
  4. 创建设备: 调用 WdfDeviceCreate。
  5. 注册 I/O 回调: 使用 WdfDeviceIoQueueCreate 创建队列,并通过 WdfIoQueueAssignForwardProgressPolicy 或直接设置 WDF_IO_QUEUE_CONFIG 的 EvtIoRead、EvtIoWrite、EvtIoDeviceControl 等成员。
常见错误: 在 KMDF 中,不要直接修改 DEVICE_OBJECT->MajorFunction 数组。KMDF 框架会接管该数组,直接修改会导致不可预测的行为。应始终使用框架提供的注册接口。

以下代码片段展示了 KMDF 驱动中注册派遣函数的典型模式:

// DriverEntry 示例
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    WDF_DRIVER_CONFIG config;
    WDFDRIVER driver;

    WDF_DRIVER_CONFIG_INIT(&config, EvtDriverDeviceAdd);
    return WdfDriverCreate(DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, &driver);
}

// EvtDriverDeviceAdd 回调
NTSTATUS EvtDriverDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT DeviceInit)
{
    WDF_IO_QUEUE_CONFIG queueConfig;
    WDFQUEUE queue;
    WDFDEVICE device;

    // 1. 创建 WDF 设备
    NTSTATUS status = WdfDeviceCreate(&DeviceInit, WDF_NO_OBJECT_ATTRIBUTES, &device);
    if (!NT_SUCCESS(status)) return status;

    // 2. 配置默认 I/O 队列
    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchSequential);
    queueConfig.EvtIoRead = EvtIoRead;
    queueConfig.EvtIoWrite = EvtIoWrite;
    queueConfig.EvtIoDeviceControl = EvtIoDeviceControl;

    // 3. 创建队列并关联设备
    status = WdfIoQueueCreate(device, &queueConfig, WDF_NO_OBJECT_ATTRIBUTES, &queue);
    return status;
}

// 派遣函数实现
VOID EvtIoRead(WDFQUEUE Queue, WDFREQUEST Request, size_t Length)
{
    // 处理读请求
    WdfRequestCompleteWithInformation(Request, STATUS_SUCCESS, Length);
}

VOID EvtIoWrite(WDFQUEUE Queue, WDFREQUEST Request, size_t Length)
{
    // 处理写请求
    WdfRequestCompleteWithInformation(Request, STATUS_SUCCESS, Length);
}

VOID EvtIoDeviceControl(WDFQUEUE Queue, WDFREQUEST Request, size_t OutputBufferLength, size_t InputBufferLength, ULONG IoControlCode)
{
    // 处理设备 I/O 控制
    WdfRequestComplete(Request, STATUS_SUCCESS);
}

下图展示了 KMDF 框架下派遣函数注册的完整架构:

KMDF 派遣函数注册架构 DriverEntry WdfDriverCreate EvtDriverDeviceAdd 创建设备与队列 WDFDEVICE_INIT 预处理回调注册 WDF 设备对象 WdfDeviceCreate I/O 队列 WdfIoQueueCreate EvtIoRead 读派遣 EvtIoWrite 写派遣 EvtIoDeviceControl 控制派遣 KMDF 框架自动将队列中的 I/O 请求分发到对应的 EvtIoXxx 回调 驱动开发者无需直接操作 IRP 或 MajorFunction 表

3.4 派遣函数注册的底层原理

KMDF 框架在 WdfDeviceCreate 内部会执行以下操作:

  • 调用 IoCreateDevice 创建 WDM DEVICE_OBJECT。
  • 将驱动提供的 EvtIoRead、EvtIoWrite 等回调与框架内部的 WDM 派遣函数 绑定。
  • 框架内部的派遣函数(如 kmdf!FxPkgIo::DispatchRead)会解析 IRP,然后调用对应的 EvtIoXxx 回调。

下表对比了 WDM 与 KMDF 在派遣函数注册上的差异:

特性 WDM 驱动 KMDF 驱动
注册方式 直接赋值 DriverObject->MajorFunction[IRP_MJ_READ] = MyRead; 通过 WDF_IO_QUEUE_CONFIG 设置 EvtIoRead 等回调
IRP 处理 手动处理 IRP 栈位置、完成状态 框架自动管理 IRP 生命周期
灵活性 完全控制,但易出错 受框架约束,但更安全
调试复杂度 需要跟踪 IRP 手动流转 框架封装后,调试需理解框架内部派遣
推荐场景 需要极致性能或特殊 IRP 处理 大多数通用驱动开发
调试技巧: 使用 WinDbg 的 !wdfdevice 0x<WDFDEVICE 句柄> 可以查看 KMDF 设备对象的详细信息,包括关联的 I/O 队列和已注册的 EvtIo 回调地址。使用 !wdfqueue 0x<WDFQUEUE 句柄> 可以查看队列的派遣函数配置。