1、DMA基础与性能瓶颈分析

各位工程师朋友,咱们今天聊聊DMA。DMA这玩意儿,说白了就是帮CPU干苦力的。我刚开始做嵌入式那会儿,总觉得DMA是个可有可无的“高级功能”,直到有一次项目里CPU占用率飙到95%,系统卡得跟幻灯片似的……嗯,从那以后我才真正重视起DMA来。

1.1 DMA工作原理

DMA的全称是Direct Memory Access,直接存储器访问。它的核心思想很简单:让外设和内存之间直接传数据,CPU只负责“发号施令”,不参与具体搬运。

我习惯把DMA的工作流程拆成三步:

  1. CPU配置DMA控制器:告诉它数据从哪来、到哪去、传多少、用什么触发。
  2. DMA接管总线:外设发出请求,DMA控制器向CPU申请总线控制权。
  3. DMA搬运数据:数据在外设和内存之间直接流动,CPU该干嘛干嘛。

你想想看,这就像老板(CPU)派了个搬运工(DMA)去仓库搬货,老板只需要说一句“把A区的货搬到B区”,然后就可以去开会了。搬运工自己干完活再回来报告一声。

关键点:DMA传输过程中,CPU可以继续执行指令,两者并行工作。这才是性能提升的根本来源。

1.2 DMA与CPU数据搬运对比

咱们直接上对比表,这样更直观:

对比维度 CPU直接搬运 DMA搬运
CPU占用率 100%(搬运期间啥也干不了) 仅配置和中断处理时占用(通常<5%)
传输速度 受限于CPU主频和指令执行效率 接近总线理论带宽
适用场景 小数据量、不规则传输 大数据块、周期性传输
实现复杂度 简单,直接读写寄存器 需要配置DMA控制器和中断
功耗 较高(CPU全速运行) 较低(CPU可进入低功耗模式)

我在项目中遇到过这样一个案例:一个音频采集系统,用CPU直接搬运PCM数据,采样率一上到48kHz就丢数据。换成DMA后,同样的采样率,CPU占用率从85%降到了3%。说白了,这就是用对了工具。

个人经验:判断该不该用DMA,我一般看两个指标——数据块大小超过64字节,或者传输频率超过1kHz。满足任意一条,DMA就值得上。

1.3 系统吞吐量瓶颈识别方法

吞吐量上不去,问题可能出在好几个地方。我总结了一套排查思路,咱们一步步来看:

1.3.1 总线带宽瓶颈

总线带宽是硬上限。比如你的AHB总线是32位、100MHz,理论带宽就是400MB/s。但实际能用到多少?我见过不少项目,DMA配置没问题,但总线仲裁策略不对,导致多个DMA通道互相抢总线。

我曾经在一个多通道数据采集项目里,四个ADC同时用DMA传数据,结果总线利用率才30%就出现丢包。查了半天,原来是总线优先级配成了轮询模式,高吞吐量的通道没拿到优先权。

1.3.2 DMA描述符链效率

很多高端DMA控制器支持描述符链(Descriptor Chain)。这个机制好不好用,直接影响吞吐量。

// 描述符链配置示例(伪代码)
dma_desc_t desc_chain[4];

// 配置每个描述符
for (int i = 0; i < 4; i++) {
    desc_chain[i].src_addr = (uint32_t)adc_buffer[i];
    desc_chain[i].dst_addr = (uint32_t)sdram_buffer[i];
    desc_chain[i].transfer_size = 1024;
    desc_chain[i].next_desc = &desc_chain[(i + 1) % 4];  // 形成环形链
    desc_chain[i].interrupt_enable = (i == 3) ? 1 : 0;   // 只在最后一帧触发中断
}

// 启动DMA
dma_start(DMA_CHANNEL_0, &desc_chain[0]);

这里有个坑:描述符链的更新时机。如果CPU更新描述符的速度跟不上DMA消耗的速度,就会出现“断链”。我建议在中断服务程序里用双缓冲机制来更新描述符,别在中断里做复杂处理。

1.3.3 中断处理开销

DMA传输完成会触发中断。如果中断处理太慢,反而会拖累吞吐量。我见过有人在中断里做数据格式转换、协议解析……这完全违背了用DMA的初衷。

避坑指南:我曾经在一个项目中,DMA中断处理函数里调用了printf打印日志,结果系统吞吐量直接腰斩。后来改成用环形缓冲区记录日志,中断里只做标志位设置,吞吐量才恢复正常。

1.3.4 缓存一致性问题

这个坑在带Cache的处理器上特别常见。DMA直接访问内存,但CPU可能从Cache里读数据,两边数据不一致。

我习惯的做法是:

  • DMA传输前,CPU主动flush Cache(写回内存)
  • DMA传输后,CPU主动invalidate Cache(让Cache失效,重新从内存读)
  • 或者干脆把DMA缓冲区放在非Cache区域

1.4 DMA性能瓶颈识别流程图

下面这张图是我自己总结的排查流程,帮你快速定位DMA相关的性能瓶颈:

DMA性能瓶颈识别流程图 DMA性能瓶颈识别流程图 系统吞吐量不达标 CPU占用率是否过高? 是 检查是否已启用DMA 否 检查总线带宽利用率 检查DMA配置参数 检查总线仲裁策略 中断处理是否耗时? 缓存一致性处理了吗? 定位瓶颈,针对性优化 排查要点 • 先看CPU占用率 • 再看总线利用率 • 最后查中断和缓存

1.5 实战排查清单

最后,我整理了一份排查清单,你遇到DMA性能问题时可以直接对照检查:

排查项 检查方法 常见问题
DMA是否启用 查看外设寄存器配置 外设没使能DMA请求
传输宽度匹配 检查源/目的地址对齐 32位外设配了8位DMA传输
描述符链完整性 遍历描述符链表 描述符地址错误导致断链
中断频率 用逻辑分析仪抓中断信号 每帧都中断,CPU被频繁打断
总线优先级 查看总线矩阵配置 低优先级DMA通道被饿死
Cache一致性 对比DMA前后数据 CPU读到的是Cache里的旧数据

嗯,这一章的内容就到这里。DMA基础打牢了,后面咱们才能谈调优。记住一句话:DMA不是万能的,但用对了地方,效果立竿见影。