2. DMA基础原理

在嵌入式与通用计算系统中,CPU 直接搬运数据是一种“昂贵”的操作:每一次读写都要占用取指、译码、执行与总线周期。DMA(Direct Memory Access,直接存储器访问)的核心思想,是让一个独立的硬件控制器代替 CPU 完成数据搬运,CPU 只负责“配置任务”和“处理完成事件”。理解 DMA 的工作模式、传输方向、地址空间、总线带宽与中断机制,是后续使用 dmaengine 接口和描述符链管理的前提。

一、核心原理:DMA 如何把 CPU 从搬运中解放出来

DMA 控制器(DMAC)内部通常包含源地址寄存器、目的地址寄存器、传输长度寄存器、控制寄存器以及状态寄存器。CPU 通过总线写入这些寄存器,DMAC 便向总线发起读写事务,把数据从源端搬到目的端。整个过程不需要 CPU 逐字节参与,CPU 可以继续执行其他任务,或在低功耗状态下等待。

从“谁发起访问”的角度看,DMA 工作模式可分为三类:

  • 周期窃取(Cycle Stealing):DMAC 与 CPU 交替使用总线,每次只搬一个数据单元,适合对实时性要求高的系统。
  • 突发传输(Burst Transfer):DMAC 连续占用总线搬完一整块数据,吞吐高,但会短暂阻塞 CPU 访问。
  • 透明传输(Transparent):DMAC 只在 CPU 不使用总线时搬运,对 CPU 几乎无影响,但速度最慢。

这三种模式本质上是“总线占用权”的分配策略,选择哪一种,取决于系统对带宽和实时性的权衡。

二、传输方向与地址空间

DMA 的传输方向由源端和目的端共同决定,常见有四类:外设到存储器(如 ADC 采样写入内存)、存储器到外设(如 UART 发送缓冲区)、存储器到存储器(如内存拷贝)、外设到外设(较少见,通常需要特殊控制器支持)。方向配置错误是实际调试中最常见的故障之一,往往表现为数据错位或外设无响应。

地址空间方面,DMA 需要区分三类地址:

  • 物理地址:DMAC 直接访问的总线地址,必须真实存在且对齐。
  • 虚拟地址:CPU 视角的地址,需经 IOMMU 或平台映射转换为物理地址后才能交给 DMA。
  • 总线地址:设备视角的地址,在多总线系统中可能与物理地址不同。
把 CPU 虚拟地址直接写入 DMA 寄存器是典型错误。若未做映射或未考虑 cache 一致性,DMA 可能写入错误位置,或 CPU 读到旧数据。使用 dma_map_single、dma_alloc_coherent 等接口正是为了解决地址转换与一致性。

三、总线带宽与中断机制

DMA 的吞吐受总线带宽限制。若外设数据率超过总线可用带宽,或 DMA 与 CPU、其他主设备争用总线,就会出现丢包、溢出或延迟抖动。评估时需关注:总线位宽、时钟频率、仲裁策略、突发长度以及是否与其他高带宽设备共享通道。

中断机制是 DMA 与 CPU 的“握手”方式。常见中断类型包括:

  • 传输完成中断:整块数据搬完,通知 CPU 处理。
  • 半传输中断:搬完一半时触发,适合双缓冲或流式处理。
  • 错误中断:总线错误、地址错误、溢出等异常。

中断过多会加重 CPU 负担,因此常配合描述符链、批量完成或轮询模式来降低中断频率。下面这张图展示了 DMA 的典型数据通路与中断回传路径:

CPU 配置 / 处理完成 外设 ADC / UART / SPI DMA 控制器 源/目的/长度/控制 通道仲裁 · 突发 存储器 SRAM / DDR 中断控制器 完成 / 半传 / 错误 寄存器配置 数据源 写入 中断信号 通知 CPU 总线带宽共享:CPU 取指、DMA 搬运、其他主设备访问需仲裁

四、案例场景:ADC 连续采样中的 DMA 配置

以 STM32 类平台为例,ADC 以 1 MHz 采样率连续转换,若用中断逐次读取,CPU 每秒要进 100 万次中断,几乎无法执行其他任务。改用 DMA 后,ADC 每次转换完成触发 DMA 请求,DMAC 把结果写入内存缓冲区,仅在搬完一整块(如 1024 个样本)后产生一次完成中断。

/* 伪代码:ADC + DMA 循环缓冲配置要点 */
dma_config.src_addr   = &ADC1->DR;      // 外设数据寄存器
dma_config.dst_addr   = adc_buffer;     // 内存缓冲区
dma_config.length     = 1024;           // 传输单元数
dma_config.src_width  = DMA_WIDTH_16BIT;
dma_config.dst_width  = DMA_WIDTH_16BIT;
dma_config.mode       = DMA_CIRCULAR;   // 循环模式
dma_config.irq        = DMA_IRQ_HALF | DMA_IRQ_FULL;
dma_start(&dma_config);
/* 半传输中断处理前半缓冲,完成中断处理后半缓冲 */

这里体现了 DMA 的三个关键价值:降低中断频率、释放 CPU 算力、保证采样节奏稳定。若把 length 设错或方向配反,常见现象是缓冲区只有首字节有效、数据整体偏移或完全无数据。

五、误区与复盘

误区现象正确做法
用虚拟地址配置 DMA数据写入错误位置或触发总线错误使用 dma_map_single / dma_alloc_coherent 获取总线地址
忽略 cache 一致性CPU 读到旧数据,DMA 写入被覆盖传输前后做 cache flush / invalidate,或使用一致性内存
方向配置错误外设无响应或数据错位明确源端与目的端,核对 DEV_TO_MEM / MEM_TO_DEV
中断过密CPU 占用率高,实时任务抖动使用半传输、描述符链或批量完成降低中断次数
忽视总线争用高负载下丢包、溢出评估带宽预算,合理设置突发长度与仲裁优先级
判断 DMA 是否真正卸载了 CPU,不能只看“有没有用 DMA”,而要看单位数据量对应的 CPU 周期数、中断次数和总线占用时间。一个配置良好的 DMA 通道,应让 CPU 从“搬运工”变成“调度者”。

总结来看,DMA 的工作模式决定总线占用策略,传输方向决定数据流向,地址空间决定访问合法性,总线带宽决定吞吐上限,中断机制决定 CPU 参与频率。把这五个维度理解清楚,后续使用 dmaengine 接口申请通道、准备描述符、提交事务和管理回调时,才能准确判断每个参数背后的硬件含义。