3. DMA 基础概念:工作原理、类型、描述符与传输方向

DMA(Direct Memory Access,直接存储器访问)是硬件子系统在不依赖 CPU 逐字节搬运的情况下,直接在外设与系统内存之间传输数据的机制。在 UMDF 驱动中,理解 DMA 的底层原理是正确配置 DMA 适配器、编写传输回调和处理 PnP 资源重新平衡的前提。本节从四个维度建立 DMA 基础概念体系。

3.1 DMA 工作原理

DMA 传输的核心思想是:将数据搬运任务从 CPU 卸载给专用的 DMA 控制器(DMAC)或支持总线主控的外设。一次典型的 DMA 传输包含以下阶段:

  1. 请求阶段:外设(如网卡、磁盘控制器)通过硬件信号线向 DMA 控制器发出传输请求(DREQ)。
  2. 仲裁阶段:DMA 控制器检查总线优先级,获得总线控制权后,向 CPU 发出 HOLD 信号,CPU 释放总线(或进入周期窃取模式)。
  3. 传输阶段:DMA 控制器在源地址与目标地址之间直接搬运数据,每次传输后递增地址、递减计数器。
  4. 完成阶段:当计数器归零或外设发出终止信号时,DMA 控制器释放总线,并向 CPU 发送中断(或通过 UMDF 完成回调通知驱动)。
关键指标:DMA 传输效率取决于总线宽度(32/64 位)、传输模式(单次/突发)、描述符链深度和缓存一致性策略。UMDF 驱动通过 WDF DMA 对象封装这些硬件细节。
DMA 传输工作原理流程图 外设请求 DREQ DMA 控制器仲裁 CPU 释放总线 DMA 数据搬运(源→目标) 传输完成 → 中断/回调 CPU 在传输期间可执行 其他指令(并行性)

3.2 系统 DMA 与总线主控 DMA

Windows 支持两种 DMA 模型,它们在架构、性能和使用场景上有本质区别。UMDF 驱动在创建 DMA 适配器时必须明确选择模型。

对比维度 系统 DMA(System DMA) 总线主控 DMA(Bus-Master DMA)
控制器位置 主板芯片组内置 DMA 控制器(如 8237) 外设自身集成 DMA 引擎
总线控制权 由系统 DMAC 持有,外设被动 外设主动申请并持有总线
传输性能 较低(受限于 DMAC 速度,通常 < 20 MB/s) 高(直接利用 PCIe 带宽,可达数 GB/s)
描述符管理 驱动填充系统 DMAC 寄存器 驱动构建描述符链表,外设自行读取
UMDF 支持 仅适用于传统 ISA/LPC 设备 主流 PCIe/PCI 设备首选
资源分配 PnP 管理器分配 DMA 通道号 PnP 管理器分配总线主控资源(BAR 空间)
UMDF 实践建议:对于现代 PCIe 设备,始终使用总线主控 DMA。系统 DMA 在 Windows 10/11 上已被标记为遗留模式,新驱动不应依赖。
系统 DMA vs 总线主控 DMA 架构对比 系统 DMA CPU 系统 DMAC 内存 外设 数据路径:外设 → DMAC → 内存 CPU 参与配置,不参与搬运 总线主控 DMA CPU 外设 DMA 引擎 内存 描述符链表 数据路径:外设引擎 → 内存(直连) 外设自主读取描述符,无需 CPU 逐次干预

3.3 DMA 描述符

DMA 描述符(Descriptor)是驱动与 DMA 引擎之间的契约数据结构,它告诉 DMA 控制器“从哪里搬、搬到哪里、搬多少、搬完后做什么”。在总线主控 DMA 中,描述符通常以链表(Scatter/Gather List)形式组织。

3.3.1 描述符核心字段

字段 说明 典型宽度
源地址 数据读取的起始物理地址(外设 FIFO 或内存缓冲区) 32/64 位
目标地址 数据写入的起始物理地址 32/64 位
传输长度 本次描述符搬运的字节数(通常为 1~4 MB) 16/32 位
控制标志 中断使能、链结束、方向、突发大小等 16/32 位
下一描述符指针 指向链表中下一个描述符的物理地址(NULL 表示链尾) 32/64 位

3.3.2 描述符链工作模式

UMDF 驱动通过 WDF 框架构建散聚列表(SCATTER_GATHER_LIST),框架自动将虚拟地址转换为物理地址并填充描述符。典型流程如下:

// 伪代码:UMDF 驱动构建描述符链
WDF_DMA_TRANSFER_CONFIG  config;
WDF_DMA_TRANSFER         dmaTransfer;
SCATTER_GATHER_LIST      sgList;

WDF_DMA_TRANSFER_CONFIG_INIT(&config, EvtDmaTransferComplete);
WdfDmaTransactionCreate(Adapter, &config, &dmaTransfer);

// 框架自动将 MDL 链转换为物理地址描述符
WdfDmaTransactionInitialize(
    dmaTransfer,
    EvtProgramDma,          // 回调:将描述符写入外设
    WdfDmaDirectionReadFromDevice,
    pMdl,
    Length
);

// 在 EvtProgramDma 中,驱动将 sgList 写入外设描述符 FIFO
WdfDmaTransactionGetTransferInfo(dmaTransfer, &sgList, NULL);
for (i = 0; i < sgList.NumberOfElements; i++) {
    WriteToDeviceFifo(sgList.Elements[i].Address.PhysicalAddress,
                      sgList.Elements[i].Length);
}
调试陷阱:描述符中的地址必须是物理地址(PA),而非虚拟地址(VA)。UMDF 驱动运行在用户态,但 WDF DMA 框架通过内核态 DMA 库自动完成转换。如果驱动手动构造描述符,必须使用 MmGetPhysicalAddress 或 WDF 提供的辅助函数。
DMA 描述符链表结构(Scatter/Gather List) 描述符 1 源地址: 0x1000_0000 目标地址: 0x2000_A000 长度: 0x1000 (4 KB) 控制: IntEn | Chain 下一指针: → Desc2 描述符 2 源地址: 0x1000_1000 目标地址: 0x2000_B000 长度: 0x0800 (2 KB) 控制: IntEn | Chain 下一指针: → Desc3 描述符 3(链尾) 源地址: 0x1000_1800 目标地址: 0x2000_C000 长度: 0x0800 (2 KB) 控制: IntEn | Last 下一指针: NULL 外设 DMA 引擎自动遍历链表,每完成一个描述符就加载下一个,直到链尾触发中断

3.4 DMA 传输方向

DMA 传输方向由外设与内存之间的数据流决定。UMDF 驱动通过 WDF_DMA_DIRECTION 枚举指定方向,框架据此配置缓存一致性(Cache Coherency)和总线事务类型。

方向枚举值 数据流 典型场景 缓存操作
WdfDmaDirectionReadFromDevice 外设 → 内存 网卡接收数据、磁盘读取 DMA 写入内存后,CPU 缓存行失效(Invalidate)
WdfDmaDirectionWriteToDevice 内存 → 外设 网卡发送数据、磁盘写入 DMA 读取内存前,CPU 缓存行刷回(Flush)

3.4.1 方向与缓存一致性

在 x64 系统上,CPU 缓存与 DMA 引擎之间可能存在数据不一致问题。UMDF 框架通过以下机制保证一致性:

  • 读方向(外设→内存):DMA 控制器将数据直接写入内存,CPU 在访问同一地址前必须执行缓存行失效指令(如 CLFLUSH 或 INVD)。WDF 框架在传输完成回调中自动插入内存屏障。
  • 写方向(内存→外设):CPU 可能已将数据写入缓存但尚未写回内存。DMA 控制器读取内存前,必须将相关缓存行刷回到系统内存。WDF 框架在初始化传输时调用 KeFlushIoBuffers。
调试检查点:如果 UMDF 驱动在 DMA 完成后读到陈旧数据,首先检查方向枚举是否正确。常见错误是读/写方向颠倒,导致框架执行了错误的缓存维护操作。
DMA 传输方向与缓存一致性操作 读方向:外设 → 内存 外设 DMA 引擎 内存 CPU 缓存 DMA 写入内存后 → CPU 缓存行失效(Invalidate) 写方向:内存 → 外设 内存 DMA 引擎 CPU 缓存 外设 DMA 读取内存前 → CPU 缓存行刷回(Flush)

3.5 本节核心概念速查表

概念 一句话定义 UMDF 相关 API/对象
DMA 传输 外设与内存之间不经 CPU 的数据搬运 WdfDmaTransaction
系统 DMA 使用主板 DMAC,外设被动 WdfDmaEnabler + 系统 DMA 通道
总线主控 DMA 外设自带 DMA 引擎,主动控制总线 WdfDmaEnabler + 散聚列表
DMA 描述符 描述一次传输的源、目标、长度、控制信息的结构 SCATTER_GATHER_LIST
读方向 外设 → 内存,需缓存失效 WdfDmaDirectionReadFromDevice
写方向 内存 → 外设,需缓存刷回 WdfDmaDirectionWriteToDevice