UMDF框架DMA传输与IOCTL通信机制实现 - 第4节
4.1 IOCTL 概念与作用
I/O 控制码(IOCTL, Input/Output Control)是 Windows 驱动模型中用于应用程序与驱动程序之间进行自定义控制操作的核心机制。与常规的 ReadFile / WriteFile 数据流不同,IOCTL 允许应用层向驱动发送带语义的控制命令,例如:配置设备参数、获取设备状态、执行硬件复位或启动一次 DMA 传输。
在 UMDF 框架中,IOCTL 请求以 WDFREQUEST 对象的形式到达驱动,驱动通过回调函数 OnDeviceIoControl 或 OnInternalDeviceIoControl 进行处理。每个 IOCTL 请求都携带一个唯一的 IOCTL 码,驱动根据该码识别操作类型并执行对应的处理逻辑。
4.2 IOCTL 码定义与结构
每个 IOCTL 码是一个 32 位无符号整数,由四个字段按位组合而成。Windows 通过这四个字段唯一标识一个控制操作,并决定缓冲区模型和访问权限。
| 位域 | 宽度(位) | 名称 | 说明 |
|---|---|---|---|
| 31–30 | 2 | TransferType | 缓冲区模型:METHOD_BUFFERED / METHOD_IN_DIRECT / METHOD_OUT_DIRECT / METHOD_NEITHER |
| 29–16 | 14 | FunctionCode | 功能码,驱动自定义操作编号(0x000–0xFFF) |
| 15–14 | 2 | RequiredAccess | 访问权限:FILE_ANY_ACCESS / FILE_READ_DATA / FILE_WRITE_DATA / 组合 |
| 13–0 | 14 | DeviceType | 设备类型,如 FILE_DEVICE_UNKNOWN、FILE_DEVICE_USB 等 |
4.3 CTL_CODE 宏详解
Windows 驱动开发工具包(WDK)提供 CTL_CODE 宏,用于在头文件中安全、规范地定义 IOCTL 码。宏定义位于 wdm.h 或 ntddk.h,原型如下:
#define CTL_CODE(DeviceType, Function, Method, Access) \
(((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
四个参数分别对应 IOCTL 码的四个字段。驱动开发者只需传入逻辑参数,宏自动完成移位和组合。以下是 UMDF 驱动中常见的定义示例:
// 设备类型:自定义设备
#define MY_DEVICE_TYPE 0x8000
// 功能码定义
#define IOCTL_MY_GET_STATUS \
CTL_CODE(MY_DEVICE_TYPE, 0x100, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_MY_SET_CONFIG \
CTL_CODE(MY_DEVICE_TYPE, 0x101, METHOD_IN_DIRECT, FILE_WRITE_DATA)
#define IOCTL_MY_START_DMA \
CTL_CODE(MY_DEVICE_TYPE, 0x102, METHOD_OUT_DIRECT, FILE_READ_DATA)
4.4 缓冲区模型详解
IOCTL 的 TransferType 字段决定了应用层与内核层之间数据缓冲区的传递方式。UMDF 驱动主要使用前三种模型,METHOD_NEITHER 在 UMDF 中不推荐使用。四种模型对比如下:
| 模型 | 宏常量 | 数据方向 | UMDF 支持 | 典型场景 |
|---|---|---|---|---|
| METHOD_BUFFERED | 0 | 双向 | ✅ 完全支持 | 小数据量控制命令(状态、配置) |
| METHOD_IN_DIRECT | 1 | 输入(应用→驱动) | ✅ 完全支持 | 大数据量写入(固件下载) |
| METHOD_OUT_DIRECT | 2 | 输出(驱动→应用) | ✅ 完全支持 | 大数据量读取(DMA 数据回传) |
| METHOD_NEITHER | 3 | 由驱动定义 | ❌ 不推荐 | 极少使用,需手动锁定内存 |
4.4.1 METHOD_BUFFERED 模型
系统在非分页池中分配一块临时缓冲区,将应用层的输入数据拷贝到该缓冲区,驱动直接读写这块临时缓冲区。输出时,系统再将临时缓冲区内容拷贝回应用层。优点是驱动无需关心用户态地址有效性,缺点是存在一次数据拷贝开销。
// UMDF 驱动中获取缓冲区的典型代码
void CMyDevice::OnDeviceIoControl(
IWDFIoRequest* pRequest,
ULONG ControlCode,
SIZE_T InputBufferSizeInBytes,
SIZE_T OutputBufferSizeInBytes
)
{
IWDFMemory* pInputMemory = nullptr;
IWDFMemory* pOutputMemory = nullptr;
// 获取输入缓冲区(已由框架拷贝到内核空间)
pRequest->GetInputMemory(&pInputMemory);
// 获取输出缓冲区
pRequest->GetOutputMemory(&pOutputMemory);
// 直接读写 pInputMemory / pOutputMemory
// ...
}
4.4.2 METHOD_IN_DIRECT 与 METHOD_OUT_DIRECT 模型
这两个模型使用 MDL(内存描述符列表) 来映射用户态缓冲区。系统将应用层提供的缓冲区锁定在物理内存中,并创建一个 MDL 描述该区域。驱动通过 MDL 直接访问用户态内存,无需额外拷贝。UMDF 框架对这两种模型提供了良好的支持,驱动通过 IWDFIoRequest2::GetInputMemory 或 GetOutputMemory 获取对应的框架内存对象。
4.5 IOCTL 码定义最佳实践
在 UMDF 驱动项目中,建议将 IOCTL 码定义集中放在一个公共头文件中,并遵循以下规范:
- 命名规范:使用
IOCTL_<设备前缀>_<操作名>格式,如IOCTL_MYDEV_GET_STATUS。 - 设备类型:自定义设备使用
FILE_DEVICE_UNKNOWN(0x00000022)或自定义值(需确保不冲突)。 - 功能码:从 0x100 开始分配,避免与系统预定义功能码重叠。
- 访问权限:只读操作使用
FILE_READ_DATA,只写操作使用FILE_WRITE_DATA,读写操作使用FILE_ANY_ACCESS。 - 缓冲区模型:根据数据量和方向选择,参考上一节的指南。
// MyDeviceIoctl.h
#pragma once
#define MYDEV_TYPE FILE_DEVICE_UNKNOWN
// 功能码定义
#define IOCTL_MYDEV_GET_VERSION \
CTL_CODE(MYDEV_TYPE, 0x100, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_MYDEV_SET_PARAM \
CTL_CODE(MYDEV_TYPE, 0x102, METHOD_BUFFERED, FILE_WRITE_DATA)
#define IOCTL_MYDEV_READ_SENSOR \
CTL_CODE(MYDEV_TYPE, 0x104, METHOD_OUT_DIRECT, FILE_READ_DATA)
#define IOCTL_MYDEV_WRITE_FIRMWARE \
CTL_CODE(MYDEV_TYPE, 0x106, METHOD_IN_DIRECT, FILE_WRITE_DATA)
#define IOCTL_MYDEV_START_DMA \
CTL_CODE(MYDEV_TYPE, 0x108, METHOD_OUT_DIRECT, FILE_READ_DATA)
4.6 本章小结
IOCTL 是 UMDF 驱动与应用层之间进行控制通信的基石。本章涵盖了以下核心知识点:
- IOCTL 概念:作为命令通道,区别于 ReadFile/WriteFile 的数据流。
- IOCTL 码结构:32 位整数包含 DeviceType、FunctionCode、RequiredAccess、TransferType 四个字段。
- CTL_CODE 宏:安全生成 IOCTL 码的工具,需注意 Function 参数必须为偶数。
- 缓冲区模型:BUFFERED 适合小数据,DIRECT 适合大数据和 DMA 场景,NEITHER 在 UMDF 中不推荐。
- 最佳实践:集中定义、规范命名、合理选择缓冲区模型。