1. 缓存一致性基础
在嵌入式 Linux 系统与多核 SoC 中,CPU 缓存(Cache)是提升内存访问性能的核心硬件。然而,缓存带来的数据副本问题,使得驱动开发者必须深入理解缓存一致性协议,否则将导致数据错乱、系统崩溃等难以调试的 Bug。本节从 CPU 缓存架构出发,逐步拆解 Cache Line、MESI 协议,并阐明驱动为何必须关注缓存一致性。
1.1 CPU 缓存架构概述
现代 CPU 采用多级缓存(L1/L2/L3)来弥合处理器与主存(DRAM)之间的速度鸿沟。L1 缓存通常分为指令缓存(L1i)和数据缓存(L1d),与核心紧耦合;L2 缓存为核心私有或共享;L3 缓存为所有核心共享。缓存以 Cache Line 为基本单位与主存交换数据,典型大小为 32 字节、64 字节或 128 字节。
下图展示了一个典型四核 SoC 的缓存层次结构,包含私有 L1/L2 与共享 L3 缓存:
1.2 Cache Line 与 MESI 协议简介
Cache Line 是缓存与主存之间数据传输的最小单元。每个 Cache Line 除存储数据外,还包含标签(Tag)、有效位(Valid Bit)和一致性状态位。多核系统中,同一内存地址的数据可能同时存在于多个核心的私有缓存中,必须通过缓存一致性协议来保证所有副本的一致。
MESI 协议 是最经典的缓存一致性协议,为每个 Cache Line 定义四种状态:
| 状态 | 全称 | 含义 | 该副本是否唯一 | 数据是否与主存一致 |
|---|---|---|---|---|
| M | Modified | 该 Cache Line 已被修改,数据仅存在于当前核心的缓存中 | 是 | 否(脏数据,需写回主存) |
| E | Exclusive | 该 Cache Line 未被修改,且仅存在于当前核心的缓存中 | 是 | 是 |
| S | Shared | 该 Cache Line 未被修改,且可能存在于多个核心的缓存中 | 否 | 是 |
| I | Invalid | 该 Cache Line 无效,不包含有效数据 | — | — |
MESI 协议通过总线嗅探(Bus Snooping)或目录协议(Directory-based)来监听其他核心的读写请求,并自动切换 Cache Line 状态。例如,当核心 A 要写入一个处于 S 状态的 Cache Line 时,协议会向其他核心发送“失效”消息,将它们的副本置为 I 状态,然后将本地状态升级为 M。
下图展示了 MESI 协议的状态转换流程:
1.3 为什么驱动需要关心缓存一致性
驱动运行在操作系统内核空间,直接管理硬件设备。当设备通过 DMA 读写主存,而 CPU 同时对该内存区域进行缓存访问时,就会出现缓存一致性问题。典型场景包括:
- DMA 接收数据: 设备将数据写入主存缓冲区,CPU 读取该缓冲区时,若 L1/L2 缓存中仍保留旧副本,则 CPU 读到的是过期数据。
- DMA 发送数据: CPU 构造数据到缓冲区,然后通知设备 DMA 读取。若 CPU 写入的数据仍停留在缓存中(脏数据),设备将读到不完整或错误的数据。
- 共享内存通信: 多核之间或 CPU 与协处理器(如 DSP、GPU)共享内存时,缺乏一致性协议会导致数据错乱。
下表总结了驱动中常见的缓存一致性风险场景及后果:
| 场景 | 操作方向 | 风险描述 | 典型后果 |
|---|---|---|---|
| DMA 接收 | 设备 → 主存 → CPU | CPU 缓存中保留旧数据,未失效 | 数据丢包、校验错误、协议栈崩溃 |
| DMA 发送 | CPU → 主存 → 设备 | CPU 写入的数据仍在缓存中,未写回主存 | 设备发送错误数据、硬件超时 |
| 多核共享变量 | Core A 写 / Core B 读 | Core B 读到过期的缓存副本 | 竞态条件、死锁、数据损坏 |
| MMIO 与缓存混用 | CPU 读写设备寄存器 | 寄存器映射内存被缓存,导致写操作延迟 | 设备状态错误、中断丢失 |
Linux 内核提供了一系列 API 来显式控制缓存一致性,驱动开发者必须掌握这些接口的使用时机:
/* 缓存刷新(写回):将脏数据从缓存写回主存 */
void dma_sync_single_for_device(struct device *dev, dma_addr_t dma_handle,
size_t size, enum dma_data_direction dir);
/* 缓存失效:使缓存中的副本失效,下次读取从主存加载 */
void dma_sync_single_for_cpu(struct device *dev, dma_addr_t dma_handle,
size_t size, enum dma_data_direction dir);
/* 分配一致性内存(非缓存或硬件一致性) */
dma_addr_t dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t flag);
dma_alloc_coherent 分配的内存默认是非缓存的,或由硬件保证一致性,可避免手动同步。
下图展示了驱动在 DMA 发送流程中必须执行的缓存同步操作:
dmam_alloc_coherent 或 dma_alloc_coherent 分配缓冲区可以彻底避免手动同步,但需注意一致性内存的分配开销较大,不适合高频小数据场景。