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、总线、外设之间的交互流程:
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 拍)。