3. Cache 写策略:写直达、写回、写分配与写不分配
Cache 写策略决定了处理器在写入数据时,如何同步 Cache 与主存之间的数据。不同的写策略直接影响系统性能、带宽消耗以及缓存一致性协议的复杂度。在 DMA 与多核系统中,写策略的选择更是决定数据一致性的关键因素。
3.1 写命中策略:写直达 vs 写回
当 CPU 写入的数据已经在 Cache 中命中时,有两种基本处理方式:写直达(Write-Through)和写回(Write-Back)。两者的核心区别在于是否立即将数据写入主存。
| 对比维度 | 写直达(Write-Through) | 写回(Write-Back) |
|---|---|---|
| 写入时机 | 每次写命中时,同时写入 Cache 和主存 | 仅写入 Cache,标记脏位(Dirty Bit),延迟写回主存 |
| 主存一致性 | 始终与 Cache 一致(强一致性) | 主存可能过时(弱一致性),需脏块写回后才一致 |
| 写带宽消耗 | 高,每次写操作都占用总线带宽 | 低,多次写操作合并为一次写回 |
| 实现复杂度 | 简单,无需脏位管理 | 较复杂,需脏位、写回仲裁逻辑 |
| 适用场景 | 对一致性要求高、写操作少的系统 | 高性能计算、写密集型应用 |
写直达(Write-Through)流程
写直达策略下,每次写命中时,CPU 同时更新 Cache 行和主存对应地址。Cache 行始终与主存保持一致,因此替换时无需写回操作。
写回(Write-Back)流程
写回策略下,写命中时仅更新 Cache 行,并设置脏位(Dirty Bit = 1)。主存数据变为过时状态,直到该 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 行内其他地址的读操作可以直接命中。
写不分配(No-Write-Allocate)流程
写不分配策略在写缺失时,直接绕过 Cache 将数据写入主存,不将主存块加载到 Cache 中。这种方式避免了 Cache 污染,适合流式写入或一次性写入的场景。
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 行无效。
- 对于 DMA 缓冲区,建议使用写直达策略或非 Cache 属性(如 ARM 的 Strongly-Ordered 或 Device 内存类型),避免一致性维护开销。
- 若必须使用写回策略,则在 DMA 传输前后显式调用 Cache 维护指令:DMA 读取前刷回(Clean),DMA 写入后失效(Invalidate)。
- 在 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 寄存器中的属性索引
// 在页表描述符中关联属性索引
}