1.1 DMA 基础概念

直接存储器访问(Direct Memory Access, DMA)是嵌入式与高性能计算系统中一项关键的硬件机制。它允许外设与系统内存之间直接交换数据,而无需CPU逐字节或逐字地参与搬运。理解DMA的基础概念,是后续分析缓存一致性、描述符链优化以及多通道并发调优的前提。

1.1.1 DMA 定义与核心思想

DMA是一种由专用硬件控制器(DMA Controller)管理的数据传输方式。CPU只需向DMA控制器下发传输描述(源地址、目的地址、数据长度、传输方向),随后DMA控制器独立完成总线仲裁与数据搬运,传输完成后通过中断或轮询标志通知CPU。

关键指标对比: 一次 1 MB 的数据从网卡 FIFO 搬运到系统内存,CPU 逐字节搬运约需 2.3 ms(@2 GHz,单周期拷贝),而 DMA 搬运仅需约 0.12 ms(@1333 MT/s 总线),效率提升近 20 倍。

1.1.2 DMA 与 CPU 数据传输对比

下表从多个维度对比DMA传输与CPU直接(PIO)传输的差异:

对比维度 CPU 直接传输(PIO) DMA 传输
数据搬运主体 CPU 核心(load/store 指令) DMA 控制器(硬件状态机)
CPU 占用率 100%(传输期间无法执行其他任务) 仅初始化与完成中断时占用
传输速率 受限于 CPU 主频与缓存带宽 接近总线理论带宽(如 AXI、PCIe)
适用场景 小数据量(< 64 字节)、控制寄存器访问 大数据块(≥ 512 字节)、流式外设
缓存一致性 天然一致(CPU 直接访问缓存) 需软件或硬件维护一致性
延迟抖动 低(确定性高) 受总线仲裁与描述符处理影响
实战经验: 在嵌入式 Linux 中,对于小于 64 字节的 SPI/I2C 寄存器读写,使用 PIO 模式反而比 DMA 更高效,因为 DMA 的描述符准备与中断开销会超过传输本身的时间。

1.1.3 DMA 传输模式

DMA 控制器支持三种基本传输模式,不同模式在总线占用、描述符管理和实时性方面有显著差异。

1. 单次传输模式(Single Transfer Mode)

每次 DMA 请求只传输一个数据单元(1 字节、2 字节或 4 字节),然后释放总线。适用于低速率外设(如 UART 字符流)或需要最小化总线延迟的场景。

  • 优点: 总线占用时间短,对 CPU 和其他 DMA 通道干扰小。
  • 缺点: 传输效率低,每次传输都需要重新仲裁总线。
  • 典型应用: 低速传感器数据采集、调试串口。

2. 突发传输模式(Burst Transfer Mode)

DMA 控制器在获得总线控制权后,连续传输多个数据单元(突发长度可配置,如 4、8、16 次传输),然后释放总线。这是 Linux 内核中最常用的 DMA 模式。

  • 优点: 总线利用率高,传输吞吐量大。
  • 缺点: 长时间占用总线可能增加其他主设备的延迟。
  • 典型应用: 网卡 RX/TX、存储控制器、GPU 纹理传输。

3. 循环传输模式(Circular / Auto-reload Mode)

DMA 控制器在完成一次传输后,自动将地址指针重置到缓冲区起始位置,持续循环搬运数据。无需 CPU 重新配置描述符。

  • 优点: 零中断开销的连续数据流,适合音频 ADC、摄像头等周期性数据。
  • 缺点: 需要双缓冲或乒乓缓冲避免数据覆盖。
  • 典型应用: 音频 I2S 流、环形 FIFO 采集。
常见陷阱: 在循环模式下,如果软件读取缓冲区速度慢于 DMA 写入速度,会导致数据覆盖(overrun)。必须配合硬件水位中断或半满中断进行软件同步。

1.1.4 DMA 传输模式对比表

特性 单次传输 突发传输 循环传输
每次传输单元数 1 N(可配置,如 4/8/16) N(自动重复)
总线占用时间 短 中~长 持续(循环)
CPU 干预频率 每次传输后中断 每次突发后中断 仅初始化时配置
适用数据速率 低(< 1 MB/s) 中~高(1 MB/s ~ 10 GB/s) 中(周期性流)
Linux 驱动典型接口 dmaengine_slave_config + dmaengine_prep_slave_sg dmaengine_prep_dma_memcpy(突发长度由硬件决定) dmaengine_prep_dma_cyclic

1.1.5 DMA 传输模式工作流程(SVG 图)

下图展示了三种传输模式下,DMA 控制器与 CPU、总线、外设之间的交互流程:

DMA 三种传输模式工作流程对比 DMA 三种传输模式工作流程对比 单次传输模式 CPU 配置描述符 DMA 请求总线 传输 1 单元 释放总线 触发中断 突发传输模式 CPU 配置描述符 DMA 请求总线 传输 N 单元 释放总线 触发中断 循环传输模式 CPU 配置循环 DMA 请求总线 传输 N 单元 地址自动重置 循环继续(无中断) 说明: 单次模式每次传输后释放总线;突发模式连续传输 N 次后释放;循环模式自动重置地址并持续传输,无需 CPU 重新配置。

1.1.6 三种模式在 Linux DMA Engine 中的对应

Linux 内核的 DMA Engine 框架为上述三种模式提供了统一的抽象接口。驱动开发者通过 dmaengine_prep_* 系列函数选择传输模式:

/* 单次/突发传输(通过 scatter-gather 列表实现) */
struct dma_async_tx_descriptor *dmaengine_prep_slave_sg(
    struct dma_chan *chan,
    struct scatterlist *sgl,
    unsigned int sg_len,
    enum dma_transfer_direction dir,
    unsigned long flags);

/* 内存到内存的突发拷贝 */
struct dma_async_tx_descriptor *dmaengine_prep_dma_memcpy(
    struct dma_chan *chan,
    dma_addr_t dst, dma_addr_t src,
    size_t len, unsigned long flags);

/* 循环传输(音频/摄像头流) */
struct dma_async_tx_descriptor *dmaengine_prep_dma_cyclic(
    struct dma_chan *chan,
    dma_addr_t buf_addr, size_t buf_len,
    size_t period_len,
    enum dma_transfer_direction dir,
    unsigned long flags);
性能优化要点: 在突发传输中,突发长度(burst length)直接影响总线效率。对于 AXI 总线,建议将突发长度设置为 8 或 16,以充分利用总线带宽,同时避免超过总线最大突发限制(通常为 16 拍)。

1.1.7 小结