2. 硬件基础:CPU缓存层次结构(L1/L2/L3)与缓存行(Cache Line)的工作原理
在深入内存屏障与DMA描述符同步之前,必须理解CPU缓存层次结构。现代多核处理器通过引入多级缓存来弥合CPU核心与主存(DRAM)之间巨大的速度差距。缓存一致性协议(如MESI)与缓存行(Cache Line)是内存屏障生效的物理基础。
2.1 缓存层次结构概览
典型的现代CPU(如x86_64、ARM64)采用三级缓存架构:L1(最快、最小、每个核心私有)、L2(中等速度、每个核心私有或共享)、L3(较慢、所有核心共享)。访问延迟差异巨大,理解这一层次是性能优化的起点。
- L1缓存访问延迟:约 0.5-1 ns(3-4个CPU周期)
- L2缓存访问延迟:约 3-5 ns(10-15个周期)
- L3缓存访问延迟:约 10-15 ns(30-50个周期)
- 主存(DRAM)访问延迟:约 50-100 ns(200-400个周期)
2.2 缓存行(Cache Line)—— 数据传输的最小单元
CPU缓存与主存之间的数据交换并非以字节为单位,而是以缓存行(Cache Line)为单位。在x86_64和ARM64架构中,缓存行大小通常为64字节。这意味着,即使CPU只读取一个4字节的整数,也会将包含该整数的整个64字节块加载到缓存中。
2.3 缓存行状态与MESI协议
为了维护多核缓存的一致性,现代CPU实现了MESI协议(或其变体MESIF/MOESI)。每个缓存行被标记为以下四种状态之一:
| 状态 | 缩写 | 含义 | 该核心是否可写 | 其他核心是否持有副本 |
|---|---|---|---|---|
| Modified | M | 已修改,数据与主存不一致,且仅本核心持有 | 是(直接写) | 否 |
| Exclusive | E | 独占,数据与主存一致,且仅本核心持有 | 是(无需通知总线) | 否 |
| Shared | S | 共享,数据与主存一致,多个核心持有副本 | 否(需先失效其他副本) | 是 |
| Invalid | I | 无效,缓存行不包含有效数据 | 否(需先加载) | — |
2.4 缓存行对齐与DMA描述符
在DMA描述符同步场景中,缓存行对齐至关重要。如果描述符跨越两个缓存行,CPU或DMA控制器在更新描述符时可能触发两次缓存行加载,增加复杂性并降低性能。
内核中常用的对齐宏:
/* 将变量对齐到缓存行边界(假设缓存行大小为64字节) */
#define CACHE_LINE_SIZE 64
#define ____cacheline_aligned __attribute__((__aligned__(CACHE_LINE_SIZE)))
/* 使用示例:描述符结构体对齐 */
struct dma_desc {
u32 status;
u32 length;
u64 addr;
u32 flags;
} ____cacheline_aligned;
- 将每个DMA描述符的大小设计为缓存行大小(64字节)的整数倍,或确保其不超过一个缓存行。
- 将经常被CPU修改的描述符字段(如状态)与经常被DMA修改的字段(如地址)放在不同的缓存行中,避免伪共享。
- 使用
dma_alloc_coherent()分配的一致性内存通常已经考虑了缓存行对齐。
2.5 缓存行与内存屏障的协同
当CPU写入一个DMA描述符时,数据首先被写入L1缓存(如果缓存行处于M或E状态)。此时,主存中的数据是过时的。如果DMA控制器(不经过CPU缓存)直接读取主存,它将看到旧数据。因此,在通知DMA控制器之前,CPU必须执行写内存屏障(如wmb()或dma_wmb()),强制将缓存行写回主存(或至少使DMA控制器能够看到一致的数据)。
反之,当DMA控制器完成传输并更新描述符后,CPU在读取描述符之前必须执行读内存屏障(如rmb()或dma_rmb()),使CPU缓存中对应的缓存行失效,从而从主存中重新加载最新的数据。