陷阱3:DMA缓冲区对齐——Cache Line引发的血案
做海思驱动开发,DMA传输是个绕不开的坎。我见过太多工程师,明明DMA配置得妥妥当当,数据就是不对。查来查去,最后发现是缓冲区没对齐Cache Line。
说白了,这就是个「你以为你传了,其实没传对」的问题。今天咱们就把它彻底讲透。
问题到底出在哪?
海思芯片用的是ARM架构,CPU和DMA之间有个Cache。Cache Line通常是64字节。什么意思呢?CPU读写内存,不是按字节来的,而是按64字节一块儿读的。
你想想看,如果DMA缓冲区起始地址不是64的倍数,会怎样?
举个例子。你申请了一个缓冲区,地址是0x10000004。DMA往这个地址写了数据。CPU去读的时候,发现Cache里没有,就去内存加载。但加载的是0x10000000到0x1000003F这一整块。
问题来了——DMA只写了0x10000004开始的几个字节,但CPU加载的是整块Cache Line。这块数据里,有一部分是DMA写的新数据,有一部分是内存里的旧数据。CPU拿到的是新旧混杂的数据,你说能不乱吗?
我在项目中踩过的坑
记得有一次做海思3559的摄像头采集驱动。图像数据通过DMA从ISP传到内存,再交给CPU做算法处理。图像总是出现条纹,偶尔还有花屏。
我排查了整整两天。时钟、同步信号、DMA配置……全查了一遍。最后用printk打印缓冲区地址,发现每次分配的地址都不一样,而且大部分都不是64字节对齐的。
嗯,问题就在这儿。malloc分配的地址,没法保证对齐。海思的DMA控制器可不管你这些,它只管往你给的地址写数据。结果就是Cache Line错位,数据一塌糊涂。
怎么解决?两种方案
解决思路其实就两条:要么让缓冲区对齐,要么让Cache和DMA同步。
方案一:强制对齐分配
我个人习惯用这个方案,简单粗暴,效果直接。
/* 方法1:使用posix_memalign */
void *buf;
int ret = posix_memalign(&buf, 64, size); // 64字节对齐
if (ret != 0) {
printk("DMA buffer alignment failed\n");
return -ENOMEM;
}
/* 方法2:使用dma_alloc_coherent(海思推荐) */
dma_addr_t dma_handle;
void *cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
if (!cpu_addr) {
printk("DMA coherent allocation failed\n");
return -ENOMEM;
}
注意看第二个方法。dma_alloc_coherent是海思SDK里最常用的。它分配的内存既保证Cache Line对齐,又自动处理了Cache一致性。说白了,你只管用,底层的事它帮你搞定。
方案二:手动Cache操作
有时候你没法用dma_alloc_coherent,比如缓冲区是上层传下来的。那就得手动刷Cache。
/* DMA传输前:把CPU可能修改过的数据刷回内存 */
dma_map_single(dev, buf, size, DMA_FROM_DEVICE);
/* DMA传输后:让CPU重新从内存加载数据 */
dma_unmap_single(dev, dma_handle, size, DMA_FROM_DEVICE);
这里有个坑,我提醒一下。很多人只记得传输后unmap,忘了传输前map。结果就是CPU写了一半的数据,DMA直接拿去用了,数据还是旧的。
性能问题也不容忽视
除了数据错乱,对齐问题还会导致性能骤降。为什么?
你想想看,如果缓冲区没对齐,DMA传输时可能触发多次Cache Line的加载和回写。本来一次能搞定的事,现在要分好几次。吞吐量直接掉一半。
我在海思3516上做过测试:
| 缓冲区对齐情况 | DMA传输速率 | CPU占用率 |
|---|---|---|
| 64字节对齐 | 800 MB/s | 12% |
| 32字节对齐 | 520 MB/s | 28% |
| 未对齐(随机地址) | 310 MB/s | 45% |
看到没?没对齐的情况下,性能直接腰斩。CPU还得多干活,去处理Cache一致性问题。说白了就是两头亏。
避坑指南
我曾经吃过不少亏,总结了几条经验:
- 分配时就对齐——别指望后面再补救。dma_alloc_coherent一步到位。
- 检查地址——调试时加个打印,看看缓冲区地址是不是64的倍数。不是的话,赶紧改。
- 别混用——同一个缓冲区,别一会儿用DMA操作,一会儿用CPU操作。非要混用的话,记得map/unmap。
- 注意海思平台差异——不同海思芯片的Cache Line大小可能不同。3559是64字节,3516也是64字节,但有些老芯片可能是32字节。查手册确认。
好了,这一章就讲到这里。DMA对齐问题说难不难,说简单也不简单。关键是要理解Cache和DMA的工作机制,知道它们为什么打架,怎么劝架。下一章咱们聊聊中断处理中的竞态问题,那个坑更深,到时候再细说。