计算机体系结构 23:缓存一致性与所有权转移

核心问题

第 16 到第 22 篇把单核视角里的 cache、DRAM、TLB、异常和数据布局串起来。多核一出现,同一个地址可能同时存在于多个核心的私有 cache 中。问题不再只是“这次访问命中哪一级”,而是:某个核心写了一个地址后,其他核心还能不能读到旧副本;某个 cache line 的最新值到底来自内存,还是来自另一个核心的脏副本。

缓存一致性解决的是单个地址的副本管理。它不保证所有地址上的操作都像单线程那样排成一个全局序列;跨地址顺序属于内存一致性模型,第 24 篇单独处理。本篇只回答一个可验收的问题:对一个 cache line,如何用失效、共享、独占和脏所有者转移,维持“同一时刻最多一个可写副本”和“读到的值来自最近一次有效写入”。

本文附件:

证据等级:功能执行。模型是 Python 标准库写成的单 cache line 教学状态机,复跑时会检查 SWMR 断言、失效确认、脏数据来源和最终内存值。它不是商品处理器 MESI 验证器。

边界

Sorin、Hill、Wood 的一致性教材把 cache coherence 的核心约束拆成两个不变量。第一是 SWMR,也就是 single-writer/multiple-reader:任一逻辑时间点,一个内存位置要么有一个可写副本,要么有多个只读副本。第二是 data-value invariant:读操作应该返回同一位置最近写入后允许被观察到的值。教材也明确区分 coherence 和 consistency:coherence 主要处理同一地址,consistency 决定不同地址上的操作如何排序。

本文模型只覆盖最小可检查子集:三个核心 C0/C1/C2,一个 cache line,状态取 I/S/E/M,请求按脚本顺序串行发生。模型记录每次读写后的各核心状态、各 cache 副本的值、内存值、失效确认列表和数据来源。没有 transient 状态、总线竞争、目录协议、NACK/retry、替换、DMA、I/O 设备一致性或真实互连延迟。

这个边界有意收窄。若先把 transient 和乱序消息加入模型,读者很容易把“协议工程细节”误认为“coherence 的基本判据”。本篇先固定单行串行模型,让每个状态转换都能用手算检查。

MESI 状态怎样承载所有权

四个状态的含义如下:

状态 含义 是否可直接读 是否可直接写 是否可能比内存新
I 无效副本
S 共享只读副本
E 独占干净副本
M 独占脏副本

EM 都是“可写副本”,所以模型的 SWMR 断言把它们算作 writer-capable copy。若某个核心处于 M,其他核心不能同时处于 S/E/M。若多个核心处于 S,它们都只能读;其中任意一个要写,必须先发出取得所有权的请求,把其他共享副本失效。

一次写 miss 或共享副本升级写入,在模型中用 write_getm 表示。GetM 的关键不是“写一个新值”这一行代码,而是先完成所有其他有效副本的失效确认。脚本把被失效的核心列入 invalidation_acks。每个事件写入 trace 之前都会运行:

1
2
3
4
writers = [c for c, st in self.cache.items() if st in (M, E)]
sharers = [c for c, st in self.cache.items() if st == S]
assert len(writers) <= 1
assert not (writers and sharers)

这段断言只检查 SWMR,不检查消息协议是否完整。它能抓住最基本的错误:两个核心同时持有可写副本,或者一个核心可写时另一个核心还保留共享读副本。

核心轨迹

复现实验命令:

1
python3 examples/computer-architecture/coherence/src/mesi_cases.py

本次输出:

1
coherence cases: PASS

轨迹中的主要事件可以手算复核。

初始状态是三份 I,内存值为 0。C0 第一次读 miss,没有其他副本,直接从内存取值 0,并进入 E

1
C0=E(value 0), C1=I, C2=I, memory=0

C1 随后读同一行。C0E 副本是干净独占副本,模型把 C0 降为 S,把 C1 也置为 S,两者都读 0:

1
C0=S(value 0), C1=S(value 0), C2=I, memory=0

此时可以有多个读者,但没有写者。C0 写入 7 时必须取得独占写权限。它发出 GetM,使 C1 的共享副本失效,收到 C1 的失效确认后进入 M,本地值变成 7:

1
C0=M(value 7), C1=I, C2=I, memory=0

这里内存仍然是 0,最新值只在 C0 的脏副本里。若后续 C1 读 miss,不能从内存直接读 0。模型查到 dirty owner 是 C0,把 7 从 C0 供应给 C1,同时把内存更新为 7,并让两者进入 S

1
C0=S(value 7), C1=S(value 7), C2=I, memory=7

随后 C2 写入 9,要失效 C0/C1 两个共享副本,进入 MC0 再读时,数据来源是 C2 的脏副本,最终内存和值都到 9。

这个轨迹说明两个点。第一,写权限是所有权,不只是 cache 中有一份数据。第二,脏数据的来源可能是另一个 cache,而不是内存。若模型在 C1 读 miss 时直接读内存 0,SWMR 仍可能成立,但 data-value invariant 已经被破坏。

验收结果

mesi_summary.txt 记录:

1
2
mesi_swmr_dirty_source: PASS
checks: invalidation acks, dirty source transfer, SWMR after every event

本文验收项对应为:

  • 能解释 S/E/M/I 各自代表的读写权限和内存新旧关系。
  • 能在 C0=M(value 7) 后说明 C1 读 miss 的数据来源必须是 C0,不能是旧内存。
  • 能检查每个事件后是否仍满足 SWMR。
  • 能说明该模型没有覆盖 transient 状态、消息竞态、目录扩展性和真实硬件时延。

练习

练习 1

已知 C0=M(value 5),内存仍为 0,C1/C2=I。此时 C1 读同一 cache line。读到的值、数据来源、结束状态分别是什么?

答案线索:C0 是 dirty owner。C1 不能从内存读 0,而要从 C0 取得 5。结束后常见教学转换是 C0=S(value 5), C1=S(value 5), C2=I, memory=5

练习 2

只检查 SWMR 是否足以证明读值正确?

答案线索:不够。例子是 C0=M(value 7), memory=0C1 读 miss。如果协议错误地让 C1 从内存读 0,并把 C0/C1 都改成 S,SWMR 形式上可能没有两个写者,但读值已经违反 data-value invariant。

参考资料