3. Cache 写策略:写直达、写回、写分配与写不分配

Cache 写策略决定了处理器在写入数据时,如何同步 Cache 与主存之间的数据。不同的写策略直接影响系统性能、带宽消耗以及缓存一致性协议的复杂度。在 DMA 与多核系统中,写策略的选择更是决定数据一致性的关键因素。

3.1 写命中策略:写直达 vs 写回

当 CPU 写入的数据已经在 Cache 中命中时,有两种基本处理方式:写直达(Write-Through)和写回(Write-Back)。两者的核心区别在于是否立即将数据写入主存。

对比维度 写直达(Write-Through) 写回(Write-Back)
写入时机 每次写命中时,同时写入 Cache 和主存 仅写入 Cache,标记脏位(Dirty Bit),延迟写回主存
主存一致性 始终与 Cache 一致(强一致性) 主存可能过时(弱一致性),需脏块写回后才一致
写带宽消耗 高,每次写操作都占用总线带宽 低,多次写操作合并为一次写回
实现复杂度 简单,无需脏位管理 较复杂,需脏位、写回仲裁逻辑
适用场景 对一致性要求高、写操作少的系统 高性能计算、写密集型应用
实战要点:在嵌入式实时系统中,写直达常用于外设寄存器映射区域,确保 CPU 写操作立即反映到外设;写回则用于普通数据区,以提升运算密集型任务的 Cache 命中效率。

写直达(Write-Through)流程

写直达策略下,每次写命中时,CPU 同时更新 Cache 行和主存对应地址。Cache 行始终与主存保持一致,因此替换时无需写回操作。

写直达(Write-Through)流程图 CPU 写请求 Cache命中? 更新 Cache 行 同步写入主存 写操作完成 特点:Cache 与主存始终一致;每次写操作都占用总线带宽;替换时无需写回,替换速度快。

写回(Write-Back)流程

写回策略下,写命中时仅更新 Cache 行,并设置脏位(Dirty Bit = 1)。主存数据变为过时状态,直到该 Cache 行被替换或显式刷回时,才将数据写回主存。

写回(Write-Back)流程图 CPU 写请求 Cache命中? 更新 Cache 行 脏位 = 1 写操作完成 Cache 行被替换时 脏位=1 → 写回主存 特点:主存可能过时;减少总线写操作;需脏位管理; 替换时若脏位为1,需先写回主存再加载新数据。
性能对比:写回策略在写密集型场景中可减少 50%~80% 的主存写流量。例如,在图像处理流水线中,连续对同一 Cache 行进行多次像素更新,写回仅在最后一次替换时写回主存,而写直达每次都会触发总线事务。

3.2 写缺失策略:写分配 vs 写不分配

当 CPU 写入的数据在 Cache 中缺失时,需要决定是否先将缺失地址对应的主存块加载到 Cache 中。这引出了写分配(Write-Allocate)和写不分配(No-Write-Allocate)两种策略。

对比维度 写分配(Write-Allocate) 写不分配(No-Write-Allocate)
缺失时行为 先将主存块加载到 Cache,再更新 Cache 行 直接写入主存,不加载到 Cache
与写策略的搭配 通常与写回策略搭配使用 通常与写直达策略搭配使用
空间局部性利用 好,后续读操作可直接命中 Cache 差,后续读操作仍需从主存加载
Cache 污染风险 较高,可能将不常用的块挤入 Cache 低,不会因写操作污染 Cache
典型应用 通用计算、数据密集型应用 流式写入、大块数据拷贝(如 DMA)

写分配(Write-Allocate)流程

写分配策略在写缺失时,先从主存读取整个 Cache 行到 Cache 中,然后 CPU 在 Cache 中执行写操作。这种方式利用了空间局部性,后续对该 Cache 行内其他地址的读操作可以直接命中。

写分配(Write-Allocate)流程 写缺失 从主存加载整行到 Cache (若脏块则先写回) 在 Cache 中执行写 脏位 = 1 写操作完成 适用场景:后续可能读取该 Cache 行内其他数据(空间局部性好)。 典型搭配:写分配 + 写回(Write-Back with Write-Allocate)。

写不分配(No-Write-Allocate)流程

写不分配策略在写缺失时,直接绕过 Cache 将数据写入主存,不将主存块加载到 Cache 中。这种方式避免了 Cache 污染,适合流式写入或一次性写入的场景。

写不分配(No-Write-Allocate)流程 写缺失 直接写入主存 不加载到 Cache 写操作完成 Cache 内容不变 无 Cache 污染
DMA 一致性陷阱:当 DMA 控制器直接向主存写入数据时,如果 CPU 使用写回 + 写分配策略,Cache 中可能包含过时的脏数据。此时 DMA 写入主存后,CPU 读 Cache 命中会读到旧数据,导致缓存一致性问题。解决方案包括:DMA 操作前执行 Cache 刷新指令,或使用硬件缓存一致性协议(如 MESI)。

3.3 四种策略的组合与选择

实际 Cache 控制器通常将写命中策略与写缺失策略组合使用,形成两种主流模式:

  • 写直达 + 写不分配(Write-Through with No-Write-Allocate):写缺失时直接写主存,不加载 Cache;写命中时同时更新 Cache 和主存。实现简单,适合对一致性要求高的外设寄存器区域。
  • 写回 + 写分配(Write-Back with Write-Allocate):写缺失时先加载整行到 Cache 再写;写命中时仅更新 Cache 并置脏。性能最优,适合通用计算和写密集型任务。
组合模式 写命中 写缺失 总线写流量 Cache 污染 一致性维护
写直达 + 写不分配 更新 Cache + 主存 直接写主存 高(每次写都触发) 低 简单(主存始终最新)
写回 + 写分配 仅更新 Cache 加载到 Cache 再写 低(仅替换时写回) 较高 需脏位管理 + 刷回机制

3.4 写策略对 DMA 一致性的影响

在包含 DMA 的嵌入式系统中,写策略的选择直接影响数据一致性维护的复杂度:

  • 写直达场景:CPU 写操作立即更新主存,DMA 读取主存时总能获得最新数据。但写直达会消耗大量总线带宽,可能影响 DMA 的传输效率。
  • 写回场景:CPU 写操作仅更新 Cache,主存数据可能过时。DMA 读取主存前,CPU 必须执行 Cache 刷回指令(如 ARM 的 DC CVAC),将脏数据写回主存。
  • DMA 写入场景:DMA 向主存写入新数据后,如果 CPU 的 Cache 中存有该地址的旧数据(且脏位为 0),CPU 读 Cache 命中会读到旧值。此时需要执行 Cache 失效指令(如 ARM 的 DC IVAC),使 Cache 行无效。
工程实践建议:
  1. 对于 DMA 缓冲区,建议使用写直达策略或非 Cache 属性(如 ARM 的 Strongly-Ordered 或 Device 内存类型),避免一致性维护开销。
  2. 若必须使用写回策略,则在 DMA 传输前后显式调用 Cache 维护指令:DMA 读取前刷回(Clean),DMA 写入后失效(Invalidate)。
  3. 在 Cortex-M 系列 MCU 中,可使用 MPU 将 DMA 缓冲区配置为“非 Cache 区域”,硬件自动绕过 Cache 访问。

3.5 写策略配置示例

以下示例展示了在 ARM Cortex-A 系列处理器中,通过页表项配置不同内存区域的 Cache 写策略:

/* ARMv8-A 页表属性配置示例 */
/* 内存区域:DMA 缓冲区 —— 使用写直达 + 写不分配 */
#define DMA_BUF_ATTR  (MT_DEVICE_nGnRnE | MT_WRITE_THROUGH)

/* 内存区域:普通数据区 —— 使用写回 + 写分配 */
#define DATA_ATTR      (MT_NORMAL | MT_WRITE_BACK | MT_WRITE_ALLOCATE)

/* 页表项设置函数 */
void set_memory_attributes(uint64_t base, uint64_t size, uint64_t attr) {
    // 实际实现依赖具体 MMU 初始化代码
    // 设置 MAIR_ELx 寄存器中的属性索引
    // 在页表描述符中关联属性索引
}
小结:写策略的选择是 Cache 子系统设计的核心权衡。写直达 + 写不分配提供强一致性但牺牲性能;写回 + 写分配提供高性能但增加一致性维护复杂度。在 DMA 和多核系统中,必须根据数据共享模式、实时性要求和带宽预算,为不同内存区域选择合适的写策略组合。