1. DMA 基础概念

1.1 DMA 工作原理

直接存储器访问(Direct Memory Access, DMA) 是一种允许硬件子系统(外设、存储器、加速器)直接读写系统内存,而无需 CPU 参与每个数据传输周期的技术。DMA 的核心思想是将数据搬运任务从 CPU 卸载给专用的 DMA 控制器(DMAC),从而释放 CPU 去执行更重要的计算与控制逻辑。

核心价值: 在高速外设(如网卡、NVMe SSD、GPU、摄像头)与内存之间,DMA 可将数据传输吞吐量提升 10~100 倍,同时将 CPU 占用率从接近 100% 降至接近 0%。

DMA 传输的基本工作流程包含以下 5 个步骤:

  1. CPU 配置 DMA 控制器: CPU 设置源地址、目标地址、传输长度、传输方向、触发源等参数到 DMA 控制器的寄存器中。
  2. DMA 控制器获取总线控制权: 当外设或软件触发 DMA 请求时,DMAC 向总线仲裁器申请总线控制权。
  3. DMAC 执行数据传输: 获得总线控制权后,DMAC 直接驱动地址总线和数据总线,在源和目标之间搬运数据,无需 CPU 介入。
  4. 传输完成中断: 当传输计数器归零时,DMAC 向 CPU 发送中断信号,通知传输完成。
  5. CPU 处理后续数据: CPU 在中断服务程序中处理已就绪的数据缓冲区。
DMA 工作原理流程图 DMA 传输工作流程 ① CPU 配置 DMAC ② DMAC 获取总线 ③ DMAC 传输数据 ④ 传输完成中断 ⑤ CPU 处理数据 说明:步骤 ② 中 DMAC 通过总线仲裁器获得系统总线控制权;步骤 ③ 中数据直接在源和目标之间搬运, CPU 可以同时执行其他任务,实现真正的并行处理。

1.2 DMA 与 CPU 的交互

DMA 并非完全取代 CPU,而是与 CPU 协同工作。两者在系统总线、缓存一致性、中断处理等方面存在复杂的交互关系。下表总结了 DMA 与 CPU 在关键维度上的分工与协作:

交互维度 CPU 角色 DMA 角色 关键机制
传输控制 初始化 DMA 描述符、启动传输 执行实际数据搬运 描述符环(Descriptor Ring)
总线仲裁 释放总线控制权 请求并获取总线控制权 周期窃取(Cycle Stealing)
缓存一致性 刷出/无效化缓存行 直接访问物理内存 Cache Coherency Protocol
传输完成通知 响应中断、处理数据 触发中断或更新状态位 中断 / Polling
错误处理 解析错误状态、恢复传输 置位错误标志、中止传输 DMA 错误中断
缓存一致性陷阱: 在 ARM/Linux 环境中,如果 CPU 缓存了 DMA 目标内存区域的数据,而 DMA 直接写入了物理内存,CPU 可能读到过时的缓存数据。驱动开发者必须使用 dma_map_single() / dma_unmap_single() 等 API 来保证一致性。
DMA 与 CPU 交互架构图 DMA 与 CPU 交互架构 CPU 核心 配置 / 中断 / 缓存管理 L1/L2 Cache 系统总线(AXI / AHB) DMA 控制器 源 / 目标 / 长度 / 触发 外设(UART / SPI / ETH) 系统内存(DDR) DMA 缓冲区 / 描述符环 交互要点:① CPU 通过系统总线配置 DMA 控制器的寄存器;② DMA 控制器通过总线直接读写内存和外设; ③ 传输完成后 DMA 通过中断线通知 CPU;④ CPU 在中断服务程序中处理已就绪的数据缓冲区。

1.3 DMA 传输类型

根据数据传输的源和目标设备类型,DMA 传输可分为以下三种基本类型。理解这些类型对于正确选择 DMA 通道和配置传输参数至关重要。

1.3.1 内存到内存(Memory-to-Memory, M2M)

  • 场景: 数据拷贝、内存整理、加密/解密加速、图像处理中的帧拷贝。
  • 特点: 源和目标都是系统内存;不需要外设触发,通常由软件直接启动;传输速率受内存带宽限制。
  • 典型硬件: 通用 DMAC(如 ARM PL080、DW DMAC)。

1.3.2 内存到外设(Memory-to-Device, M2D)

  • 场景: 向 UART 发送数据、向 SPI DAC 写入波形、向 LCD 控制器刷新帧缓冲。
  • 特点: 源是内存,目标是外设 FIFO/寄存器;通常由外设的“请求”信号触发;传输粒度通常为字或半字。
  • 典型硬件: 专用 DMA 通道(如 STM32 DMA、Xilinx AXI DMA)。

1.3.3 外设到内存(Device-to-Memory, D2M)

  • 场景: 从 UART 接收数据、从 ADC 采集样本、从摄像头捕获图像帧。
  • 特点: 源是外设 FIFO/寄存器,目标是内存;外设数据就绪时触发 DMA 请求;需要处理数据溢出保护。
  • 典型硬件: 与 M2D 共用同一 DMA 控制器,仅方向位不同。
传输类型 源 目标 触发方式 典型带宽 Linux 驱动场景
M2M 内存 内存 软件触发 内存带宽的 60~90% 加密加速、数据搬运
M2D 内存 外设 外设请求 外设接口速率 SPI 发送、LCD 刷新
D2M 外设 内存 外设请求 外设接口速率 UART 接收、音频采集
DMA 传输类型对比图 三种 DMA 传输类型 M2M:内存 → 内存 源内存 目标内存 软件触发 · 批量拷贝 M2D:内存 → 外设 内存 外设 外设触发 · 发送数据 D2M:外设 → 内存 外设 内存 外设触发 · 接收数据 注意:M2M 传输通常不需要外设请求线,由软件直接设置 DMA 控制器的使能位即可启动; 而 M2D 和 D2M 必须等待外设的硬件请求信号,DMA 控制器才能开始每个数据项的传输。 传输类型选择指南 • 需要快速内存拷贝(如 memcpy 加速)→ 选择 M2M 模式 • 需要向外设发送大量数据(如 LCD 刷新、音频播放)→ 选择 M2D 模式 • 需要从外设接收大量数据(如摄像头捕获、ADC 采集)→ 选择 D2M 模式 • 同一 DMA 控制器通常支持多种模式,通过配置寄存器中的方向位切换
驱动开发常见误区: 初学者容易混淆 M2M 和 M2D 的触发方式。在 Linux 内核中,struct dma_slave_config 的 direction 字段必须与硬件实际连接一致。如果外设是 SPI TX,方向应设为 DMA_MEM_TO_DEV;如果是 SPI RX,应设为 DMA_DEV_TO_MEM。错误的方向配置会导致 DMA 传输无法启动或数据错乱。