4. 缓存一致性基础
在讨论 DMA 性能瓶颈之前,必须先理解 CPU 侧的缓存体系。DMA 之所以复杂,根本原因在于:外设直接读写内存,而 CPU 可能把同一块内存的副本缓存在自己的私有缓存里。如果两者不同步,就会出现“CPU 看到旧数据”或“外设读到旧数据”的问题。本节从缓存层次、缓存行、写策略到 MESI 协议,建立后续分析 DMA 一致性的基础。
一、CPU 缓存层次:为什么需要多级缓存
CPU 执行速度远高于 DRAM 访问速度,二者之间存在数量级的差距。为了弥补这一鸿沟,现代处理器采用多级缓存结构,通常为 L1、L2、L3 三级:
- L1 缓存:离核心最近,容量最小(通常 32KB~64KB),访问延迟约 4~5 个周期,分为指令缓存(I-Cache)和数据缓存(D-Cache)。
- L2 缓存:容量较大(256KB~1MB),延迟约 10~20 个周期,通常为每个核心私有。
- L3 缓存:容量最大(数 MB 到数十 MB),多核共享,延迟约 30~50 个周期,是核间数据共享的关键层级。
越靠近核心,速度越快、容量越小、成本越高。数据以“缓存行”为单位在各级之间搬运,而不是按字节单独搬运。理解这一点,是理解一致性协议的前提。
二、缓存行:一致性的最小单位
缓存行(Cache Line)是缓存与内存之间传输数据的最小单位,x86 和 ARM 上通常为 64 字节。即使 CPU 只读取 1 个字节,也会把包含该字节的整行(64 字节)加载进缓存。
缓存行带来两个重要后果:
- 空间局部性收益:连续访问相邻数据时命中率高,性能好。
- 伪共享(False Sharing):两个核心分别修改同一缓存行中的不同变量,虽然逻辑上无冲突,但硬件仍会因整行失效而反复同步,导致性能骤降。
三、写回与写直达:两种写策略
当 CPU 修改缓存中的数据时,何时把新值写回内存,取决于写策略:
| 策略 | 行为 | 优点 | 缺点 |
|---|---|---|---|
| 写直达(Write-Through) | 每次写缓存的同时立即写内存 | 内存始终最新,一致性简单 | 写内存频繁,带宽压力大 |
| 写回(Write-Back) | 只写缓存并置脏位,淘汰时才写内存 | 减少内存写次数,性能高 | 内存可能滞后,需一致性协议维护 |
现代 CPU 普遍采用写回策略,因此缓存行存在“干净”和“脏”两种状态。脏行意味着内存中的副本已过期,这正是 DMA 读取时可能拿到旧数据的根源。
四、MESI 协议:四状态一致性模型
多核系统中,每个核心都有自己的缓存,同一内存地址可能在多个缓存中留有副本。MESI 协议用四个状态描述缓存行的归属与有效性:
- M(Modified,已修改):本行被修改且与内存不一致,仅本缓存持有,是唯一有效副本。
- E(Exclusive,独占):本行与内存一致,且仅本缓存持有。
- S(Shared,共享):本行与内存一致,多个缓存可同时持有。
- I(Invalid,无效):本行数据无效,不可使用。
当某核心要写一个处于 S 状态的行时,必须先向其他缓存发送失效请求,使它们转为 I,自己再转为 M。这个过程称为“缓存一致性流量”,会占用总线带宽。DMA 与 CPU 争抢内存带宽时,这类流量会进一步加剧瓶颈。
五、一致性模型与 DMA 的关联
MESI 保证的是“CPU 核间”的一致性,它并不覆盖 DMA 引擎。DMA 通常不经过 CPU 缓存,直接读写 DRAM,因此会出现两类问题:
- DMA 读(设备→内存):设备写入新数据到 DRAM,但 CPU 缓存中可能仍是旧行。若该行状态为 M 或 S,CPU 会读到旧值。需要在 DMA 传输前
invalidate对应缓存行。 - DMA 写(内存→设备):CPU 修改了缓存中的脏行,尚未写回 DRAM,设备却直接从 DRAM 读取,拿到旧数据。需要在 DMA 传输前
flush/clean对应缓存行。
六、案例与复盘:一次网卡收包异常
某嵌入式平台使用非一致性 DMA,网卡收包后 CPU 读取缓冲区,发现数据前 64 字节正确、后续为旧值。排查发现:驱动只对缓冲区首地址做了一次 invalidate,但缓冲区跨越多个缓存行,且未按 64 字节对齐,导致部分行未被失效。
/* 错误做法:只失效首地址 */
dma_cache_invalidate(rx_buf, sizeof(rx_buf));
/* 正确做法:按缓存行对齐并覆盖整个范围 */
#define CACHE_LINE 64
uintptr_t start = (uintptr_t)rx_buf & ~(CACHE_LINE - 1);
uintptr_t end = ((uintptr_t)rx_buf + len + CACHE_LINE - 1) & ~(CACHE_LINE - 1);
dma_cache_invalidate((void *)start, end - start);
复盘要点:
- DMA 缓冲区必须按缓存行对齐,长度向上取整到缓存行边界。
- 方向决定操作:设备写内存用 invalidate,CPU 写内存供设备读用 flush。
- 一致性架构(如 x86 常见)由硬件维护,但非一致性架构必须软件显式处理。
- 伪共享与缓存行争用会放大 DMA 与 CPU 的带宽冲突,需结合 perf 观察 cache-misses 指标。