1、DMA基础与性能瓶颈分析
各位工程师朋友,咱们今天聊聊DMA。DMA这玩意儿,说白了就是帮CPU干苦力的。我刚开始做嵌入式那会儿,总觉得DMA是个可有可无的“高级功能”,直到有一次项目里CPU占用率飙到95%,系统卡得跟幻灯片似的……嗯,从那以后我才真正重视起DMA来。
1.1 DMA工作原理
DMA的全称是Direct Memory Access,直接存储器访问。它的核心思想很简单:让外设和内存之间直接传数据,CPU只负责“发号施令”,不参与具体搬运。
我习惯把DMA的工作流程拆成三步:
- CPU配置DMA控制器:告诉它数据从哪来、到哪去、传多少、用什么触发。
- DMA接管总线:外设发出请求,DMA控制器向CPU申请总线控制权。
- 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相关的性能瓶颈:
1.5 实战排查清单
最后,我整理了一份排查清单,你遇到DMA性能问题时可以直接对照检查:
| 排查项 | 检查方法 | 常见问题 |
|---|---|---|
| DMA是否启用 | 查看外设寄存器配置 | 外设没使能DMA请求 |
| 传输宽度匹配 | 检查源/目的地址对齐 | 32位外设配了8位DMA传输 |
| 描述符链完整性 | 遍历描述符链表 | 描述符地址错误导致断链 |
| 中断频率 | 用逻辑分析仪抓中断信号 | 每帧都中断,CPU被频繁打断 |
| 总线优先级 | 查看总线矩阵配置 | 低优先级DMA通道被饿死 |
| Cache一致性 | 对比DMA前后数据 | CPU读到的是Cache里的旧数据 |
嗯,这一章的内容就到这里。DMA基础打牢了,后面咱们才能谈调优。记住一句话:DMA不是万能的,但用对了地方,效果立竿见影。