DMA 突发长度、地址对齐与 Cache 一致性:non-cacheable/coherent 缓冲设计
更新时间:2026-09-19。前置:AXI4 burst 与边界、DMA 描述符环、MESI 一致性。
本文要回答的问题
- 为什么 DMA 缓冲区要对齐(4/8/32/64 字节、cache line)?不对齐会怎样?
- AXI burst 为什么不能跨 4KB,拆包由谁负责?
- CPU 改完数据启动 DMA,设备却收到旧值;或 DMA 写的内存 CPU 读到旧值——根因是什么?
- 软件 clean/invalidate、non-cacheable 映射、coherent 总线三种方案怎么选?
一、突发与对齐
AXI 一次 burst:起始地址 ARADDR/AWADDR + 长度 ARLEN(拍数-1)+ 大小 ARSIZE(每拍字节)。
- 对齐突发:起始地址是
ARSIZE的整数倍,节拍整齐,单条 burst 走完。 - 非对齐:AXI 用字节选通支持,但控制器/桥常被迫拆成「头一拍窄 + 中间整 + 尾一拍窄」,效率下降;很多简单 DMA IP 干脆要求对齐,非对齐直接报错或地址截断。
- 4KB 边界:AXI 规定单次 burst 不得跨 4KB(防止穿过对端页/从机解码边界),描述符跨越时 DMA 必须拆成多条 burst。
- Cache line 对齐:为配合整行填充/回写、避免「false sharing 式」拆行,DMA 缓冲最好按 cache line(常 32/64B)对齐且长度取整。
bash
未对齐 16B 传输,地址 0x..04(32B 行):
拍0: bytes 04..07 (窄) 拍1..: 08.. ... 尾拍窄
→ 多出拍数、可能跨行,吞吐和一致性都更差二、Cache 一致性问题(核心)
CPU 经 Cache 访问内存,DMA 旁路 Cache 直接读写 DRAM,二者对「最新数据在哪」认知不同:
bash
① 发送(CPU→设备):
CPU 写数据 → 只到 D-Cache(脏行),DRAM 还是旧的
DMA 从 DRAM 取 → 读到旧数据 ✗
正解:启动 DMA 前 clean(回写脏行到 DRAM)
② 接收(设备→CPU):
DMA 写 DRAM(新数据),但 CPU 的 Cache 里还有旧的有效行
CPU 读 → 命中旧 Cache 行 ✗
正解:DMA 完成后 invalidate(丢弃旧行,强制从 DRAM 重载)
(invalidate 前若该行为脏会丢数据,接收缓冲应独立且不与其他变量共行)顺序很重要:发送前 clean;接收后 invalidate;既收又发的环则在相应阶段分别操作。
三、三种工程方案
| 方案 | 做法 | 优缺点 |
|---|---|---|
| 软件维护 | DMA 缓冲在 normal cacheable 区,驱动手动 clean/invalidate | 硬件简单(无一致性互连);易漏、维护成本高,嵌入式常见 |
| Non-cacheable / strongly-ordered 映射 | 把 DMA 缓冲(或描述符环)映射成不可缓存(MMU 页表属性) | 永远一致、省心;CPU 访问慢、轮询描述符性能差 |
| 硬件 coherent 互连 | DMA 作为 IO-coherent master,经 ACE/CHI 或 ACI 端口,总线自动 snoop | 零软件维护、性能好;需一致性互连和 IP 支持,见 CHI/ACE |
AXI 侧通过 AxCache/AxProt 位声明:coherent master 会在 ARCACHE 标 read allocate/可被 snoop;非 coherent DMA 标 bufferable/non-cacheable 并由软件负责。
四、对齐的软件保证
- 链接段/内存池按 cache line 对齐:
__attribute__((aligned(64))),或 DMA 专用 heap。 - 长度向上取整到 line,避免和相邻变量共享一条 cache line(invalidate 误伤)。
- 网卡/USB 常用 bounce buffer:用户给的任意地址先拷到一个对齐、可控的 DMA 缓冲,再交给硬件;牺牲一次拷贝换正确性。
- 描述符环(见 Descriptor Ring)本身常放 non-cacheable 或做批量 clean,避免每描述符刷 cache。
五、常见的坑
- 发送前忘记 clean:设备偶发收到旧数据(命中脏行时复现),最经典 bug。
- 接收后忘记 invalidate:CPU 读到旧帧,加打印「手动访问过内存」又莫名好了(污染了排查)。
- invalidate 误伤共行变量:缓冲未按 line 对齐,邻居变量被丢弃回旧值。
- 非对齐地址触发 DMA 报错/截断:简单 DMA 忽略低位地址,写到错误偏移。
- 跨 4KB 未拆 burst:从机报 SLVERR 或地址回卷,见 AXI 错误响应。
- 长度非 line 整数倍:尾部 partial line 的 clean/invalidate 处理不当丢/脏数据。
- 在 coherent 系统上仍乱刷 cache:对 coherent 内存做 clean/invalidate 既浪费又可能与 snoop 冲突。
- D-Cache 关/开状态与驱动假设不符:早期引导关 cache 时正常,开 MMU/cache 后 DMA 出错。
- 描述符与数据缓冲共用 cache 行:环指针更新被数据缓冲的 invalidate 清掉。
- 字节序/大小端:DMA 与 CPU 端序不一致时描述符字段需 byte swap(与对齐无关但常一起出现)。
- 对齐 bounce 忘记同步长度/偏移:拷贝边界算错导致越界或短包。
相关与延伸
突发编码见 AXI4 burst/WRAP;一致性协议见 MESI、CHI/ACE;描述符组织见 DMA 描述符环。
总结
DMA 对齐与一致性是两根红线:对齐上,缓冲区应按 ARSIZE 并最好按 cache line 对齐、长度取整、burst 不跨 4KB,非对齐会被拆包降效或被简单 IP 截断;一致性上,CPU 经 cache、DMA 直连 DRAM,必须「发送前 clean 回写、接收后 invalidate 丢弃旧行」,或把缓冲映射成 non-cacheable,或使用 ACE/CHI 的 IO-coherent master 让总线自动 snoop——忘 clean、忘 invalidate、缓冲与变量共 cache line 是三个必发的坑。