Cache 的一致性问题
前面四节讲的全是”读”。一旦允许写,Cache 里就出现了主存内容的副本,而副本一被修改,两份数据就不一致了。
一致性问题有三个来源,教材按这个顺序展开:
- CPU 自己写 Cache → Cache 与主存不一致 → 用写策略解决
- 多个处理器各有自己的 Cache → 各 Cache 之间不一致 → 用一致性协议解决
- I/O 设备(DMA)绕过 Cache 直接读写主存 → 主存与 Cache 不一致 → 用不可缓存区或强制刷新解决
写策略是必考的,后两个只考概念。
机制
写命中:写直达与写回
写直达法(全写法,Write-through):同时写 Cache 和主存。
- 任何时刻主存都是最新的,一致性天然保持
- 不需要脏位
- 每次写都要访问主存,写开销大
- 缓解办法:加写缓冲(Write Buffer)——CPU 把数据写进缓冲区就继续执行,由缓冲区异步写入主存。写缓冲满时仍会阻塞。
写回法(Write-back):只写 Cache,并置脏位;直到该行被替换时才写回主存。
- 大幅减少访存次数,对写密集的程序优势明显
- 需要脏位(见 3.5.2)
- 替换时若脏位为 1,必须先写回再装入新块——这使缺失代价变成两次主存访问
- 主存中的数据可能是过时的,这正是多核和 DMA 一致性问题的根源
写不命中:写分配与非写分配
写分配(Write-allocate):先把该块调入 Cache,再在 Cache 中写。
- 押注”写过的地方马上还会再访问”(时间局部性)
- 一次写不命中要付一次块调入的代价
非写分配(No-write-allocate):直接写主存,不调入 Cache。
- 不占用 Cache 空间,适合”写一次就不再用”的数据
四种组合,两种常见搭配
| 写命中 | 写不命中 | 是否常见 | 理由 |
|---|---|---|---|
| 写回法 | 写分配 | 常见 | 两者都押注局部性;既然要反复写,就该把块留在 Cache 里 |
| 写直达法 | 非写分配 | 常见 | 两者都不押注局部性;反正每次都要写主存,调入块没有收益 |
| 写回法 | 非写分配 | 少见 | 自相矛盾 |
| 写直达法 | 写分配 | 少见 | 调入了块,写时却还要同时写主存,收益有限 |
“写回配写分配、写直达配非写分配”这两条搭配是考点,理由是两者押注的是不是同一件事。
flowchart TD W["CPU 执行写操作"] --> H{"写命中?"} H -->|"命中"| WP{"写策略"} WP -->|"写直达"| WT["同时写 Cache 和主存<br/>(可经写缓冲)"] WP -->|"写回"| WB["只写 Cache,置脏位<br/>替换时若脏则写回"] H -->|"不命中"| MP{"写不命中策略"} MP -->|"写分配"| WA["先调入块,再按写命中处理"] MP -->|"非写分配"| NWA["直接写主存,不调入"] classDef a fill:#d5f5e3,stroke:#27ae60 classDef b fill:#fdebd0,stroke:#e67e22 class WB,WA a class WT,NWA b
多处理器的 Cache 一致性
多个核各有私有 Cache 时,同一个主存块可能在多个 Cache 里都有副本。一个核改了自己的副本,其他核的副本就过期了。
两类解决思路:
监听(嗅探)协议 Snooping:所有 Cache 控制器监听总线上的事务,发现别人写了自己也缓存着的块时作出反应。
- 写直达式(写更新):把新值广播出去,其他副本一起更新
- 写回式(写无效):把其他副本置为无效,下次访问时重新取。这是主流,MESI 协议属于此类
- 依赖广播,适合总线互连的小规模系统
目录协议 Directory:用一个集中的目录记录每个主存块被哪些 Cache 缓存着,写时只通知相关的那几个。
- 不需要广播,适合大规模多处理器
- 目录本身有存储和查询开销
I/O 引起的一致性
DMA 直接在主存与设备之间搬数据,不经过 Cache(见 第 7 章),于是:
- DMA 写主存:Cache 里的副本过期
- DMA 读主存:若 Cache 用写回法且该块是脏的,主存里的数据是旧的,DMA 读到错误数据
三种对策:把 DMA 缓冲区设为不可缓存(non-cacheable);让 DMA 也经过 Cache;由软件在 DMA 前后显式刷新/无效化 Cache。
这正是操作系统的驱动程序要做的事之一,见 OS 5.2.5 设备驱动程序接口。
边界
“Cache 一致性”和”存储一致性模型”不是一回事。 一致性(Coherence)保证的是对同一个地址,所有核看到的写顺序一致;存储一致性模型(Consistency)规定的是不同地址之间的读写能否被重排。408 只考前者。
写直达法不需要脏位,写回法必须有。 一道题问”采用写回法的 Cache 每行需要哪些控制位”,脏位不能漏。
写回法下主存不一定是最新的。 这句话本身是写回法的定义,不是缺点描述;它的缺点是替换时可能要多一次写回,且多核/DMA 一致性更复杂。
写缓冲不改变写直达的语义。 它只是把”写主存”变成异步的,一致性仍然由写直达保证;写缓冲满时 CPU 照样阻塞。
写分配 / 非写分配只在”写不命中”时才有区别。 写命中时两者行为完全相同,由写命中策略决定。
Cache 一致性问题在单核单进程、无 DMA 的模型里不存在。 教材先讲写策略再讲多核,顺序不能颠倒:写策略解决的是 Cache 与主存的不一致,多核协议解决的是 Cache 之间的不一致。
对照速查
| 写直达(全写法) | 写回法 | |
|---|---|---|
| 写命中时 | 同时写 Cache 和主存 | 只写 Cache,置脏位 |
| 脏位 | 不需要 | 需要 |
| 主存是否最新 | 总是最新 | 可能过时 |
| 访存次数 | 多 | 少 |
| 替换开销 | 直接覆盖 | 脏行需先写回 |
| 常搭配 | 非写分配 | 写分配 |
| 一致性维护 | 简单 | 复杂 |
| 一致性来源 | 解决手段 |
|---|---|
| CPU 写 Cache | 写策略(写直达 / 写回) |
| 多核私有 Cache | 监听协议 / 目录协议 |
| DMA 绕过 Cache | 不可缓存区 / 软件刷新 |
考点
- 写命中:写直达 vs 写回;写不命中:写分配 vs 非写分配,两组正交
- 常见搭配是”写回 + 写分配”和”写直达 + 非写分配”,因为两者押注的是同一件事
- 只有写回法需要脏位
- 写缓冲缓解写直达的开销,但不改变其语义
- 写回法下替换脏行要多一次主存写
- 监听协议靠广播(写无效为主流),目录协议靠集中记录
- DMA 绕过 Cache 会造成不一致,靠不可缓存区或软件刷新解决
- Cache 一致性 ≠ 存储一致性模型,408 只考前者
链接
- 🏠 返回总览:计算机组成原理第 3 章:存储系统总览
- ⬅️ 上一节:3.5.4 Cache 中主存块的替换算法
- ➡️ 下一节:3.6.1 虚拟存储器的基本概念
- 🔗 3.5.2(脏位属于标记项)
- 🔗 第 7 章 输入/输出系统(DMA 方式)
- 🌐 跨科:OS 5.2.5 设备驱动程序接口
- 📖 名词库:第 3 章名词库