4. Cache 一致性基础:CPU Cache 结构与一致性协议
在 DMA 与设备共享内存的场景中,Cache 一致性是决定数据正确性的核心问题。CPU 通过多级 Cache 加速内存访问,但设备(如 DMA 控制器)直接读写物理内存,绕过了 CPU Cache。若 Cache 与内存中的数据不一致,轻则数据错误,重则系统崩溃。本节从硬件结构出发,深入剖析 Cache 的工作机制与一致性协议。
4.1 CPU Cache 层次结构(L1 / L2 / L3)
现代多核处理器通常采用三级 Cache 架构,以平衡速度、容量与成本:
| 层级 | 位置 | 典型容量 | 延迟(CPU 周期) | 共享范围 |
|---|---|---|---|---|
| L1 Cache | 每个核心私有 | 32 KB ~ 64 KB(指令 + 数据分离) | 2 ~ 4 周期 | 单核心 |
| L2 Cache | 每个核心私有 | 256 KB ~ 512 KB | 10 ~ 20 周期 | 单核心 |
| L3 Cache | 所有核心共享 | 2 MB ~ 32 MB | 30 ~ 50 周期 | 所有核心 |
关键认知:L1 速度最快但容量最小,L3 容量最大但延迟最高。DMA 缓冲区通常映射为“非缓存”或“一致性”内存,以避免与 L1/L2 私有 Cache 产生冲突。
下图展示了一个四核处理器的典型 Cache 拓扑结构:
4.2 Cache Line:数据交换的最小单元
Cache 与内存之间的数据交换并非以字节为单位,而是以 Cache Line(缓存行)为基本单元。Cache Line 的大小通常为 64 字节(x86/ARM 主流架构)。
- 空间局部性利用:当 CPU 访问一个地址时,其相邻的 64 字节数据会被一次性加载到 Cache Line 中。
- 对齐要求:DMA 缓冲区若与 Cache Line 边界未对齐,可能导致一个 Cache Line 包含部分 DMA 数据和部分 CPU 数据,引发“伪共享”(False Sharing)问题。
- 一致性粒度:Cache 一致性协议以 Cache Line 为粒度进行状态跟踪和同步。
驱动调试要点:在分配 DMA 缓冲区时,务必使用
dma_alloc_coherent() 或确保缓冲区地址按 Cache Line 大小对齐。若手动管理一致性,需调用 dma_map_single() 并指定正确的 DMA 方向。
下图展示了 Cache Line 在 CPU 与内存之间的数据搬运过程:
4.3 Cache 写策略:Write-through 与 Write-back
CPU 写入数据时,Cache 与内存的同步方式决定了性能与一致性复杂度。两种主流写策略对比如下:
| 写策略 | 写入 Cache 时 | 写入内存时 | 性能 | 一致性复杂度 | 典型应用 |
|---|---|---|---|---|---|
| Write-through | 立即写入 Cache | 同时立即写入内存 | 较低(每次写操作都访问内存) | 低(内存始终最新) | 早期处理器、对一致性要求严格的设备寄存器 |
| Write-back | 仅写入 Cache,标记为 Dirty | 延迟写入(Cache Line 被替换或显式刷回时) | 高(减少内存访问次数) | 高(需协议保证一致性) | 现代通用处理器(x86/ARM) |
DMA 陷阱:现代 Linux 系统默认使用 Write-back 策略。当 DMA 设备向内存写入数据时,若 CPU Cache 中仍持有该内存区域的“脏”Cache Line,CPU 将读到旧数据。反之,CPU 写入数据后若未刷回 Cache,DMA 设备将读到旧数据。这是驱动开发中最常见的一致性 Bug。
下图对比了两种写策略的数据流:
4.4 Cache 一致性协议:MESI
MESI 协议是维护多核 Cache 一致性的经典协议,定义了每个 Cache Line 的四种状态:
| 状态 | 全称 | 该 Cache Line 是否有效 | 数据是否与内存一致 | 其他核心中是否有副本 |
|---|---|---|---|---|
| M | Modified(已修改) | 是 | 否(数据仅在本 Cache,内存已过时) | 无 |
| E | Exclusive(独占) | 是 | 是(与内存一致) | 无 |
| S | Shared(共享) | 是 | 是(与内存一致) | 有(一个或多个核心持有副本) |
| I | Invalid(无效) | 否 | — | — |
协议核心思想:当某个核心要写入一个 Cache Line 时,必须确保其他核心中该 Cache Line 的副本全部失效(Invalid)。写入后,该 Cache Line 变为 Modified 状态,其他核心若读取则必须从修改后的核心获取最新数据(Cache-to-Cache 传输)。
下图展示了 MESI 协议的状态转换流程:
4.5 一致性协议对 DMA 的影响
MESI 协议虽然保证了多核 CPU 之间的 Cache 一致性,但 DMA 设备不在 MESI 协议的管理范围内。设备直接访问物理内存,不会发送“窥探”请求给 CPU Cache。因此,驱动开发者必须手动处理以下场景:
- CPU 写 → DMA 读:CPU 写入数据到缓冲区后,必须执行 Cache 刷回(Clean/Flush)操作,将 Dirty Cache Line 写回内存,否则 DMA 设备读到的是旧数据。
- DMA 写 → CPU 读:DMA 设备向内存写入数据后,CPU 必须使相关 Cache Line 失效(Invalidate),否则 CPU 可能读到 Cache 中的陈旧副本。
- 伪共享(False Sharing):当两个核心分别操作同一 Cache Line 中的不同变量时,MESI 协议会频繁触发状态切换,导致性能下降。DMA 缓冲区应避免与频繁访问的 CPU 数据共享同一 Cache Line。
最佳实践:在 Linux 驱动中,使用
dma_alloc_coherent() 分配的内存默认是非缓存的(或启用硬件一致性协议如 IOMMU/SMMU),无需手动维护一致性。若使用 dma_map_single() 进行流式 DMA 映射,则必须根据 DMA 方向调用 dma_sync_single_for_cpu() 和 dma_sync_single_for_device() 来管理 Cache。
下图总结了 DMA 与 Cache 一致性的关键交互路径: