2. 缓存一致性基础:MESI/MOESI协议原理、CXL的一致性要求
说到 CXL 的监听过滤,你得先把缓存一致性这个老本行吃透。我刚开始做一致性验证那会儿,天天对着 MESI 的四个状态画来画去,后来发现光画不行,得真刀真枪在仿真波形里抓协议违规。今天咱们就把 MESI 和 MOESI 这两个基础协议掰开揉碎,再看看 CXL 对一致性到底提了哪些要求。
2.1 为什么需要缓存一致性?
你想想看,一个多核系统里,每个核心都有自己的 L1/L2 缓存。如果核 A 改了某个地址的数据,核 B 却还拿着旧数据,那程序就跑飞了。缓存一致性协议就是用来保证:所有缓存对同一地址的观察是一致的。
说白了就是三个规则:
- 写传播(Write Propagation)——核 A 改了以后,核 B 必须能看见。
- 写串行化(Write Serialization)——所有核心对同一地址的写顺序必须一致。
- 承诺(Commitment)——数据一旦被某个核心写入,最终所有人都能看到。
2.2 MESI 协议——四个状态的博弈
MESI 是最经典的写无效协议,四个状态你就当是四种身份:
| 状态 | 含义 | 本缓存是否有有效数据 | 与其他缓存是否共享 | 主存是否最新 |
|---|---|---|---|---|
| M(Modified) | 已修改 | 是 | 独有 | 否(只有本缓存最新) |
| E(Exclusive) | 独占 | 是 | 独有 | 是 |
| S(Shared) | 共享 | 是 | 多份 | 是 |
| I(Invalid) | 无效 | 否 | — | — |
状态转换的触发事件就那几类:本地读 / 本地写 / 远程读 / 远程写 / 回写。我来给你画个关键转换规则:
- M 状态下本核读 → 仍为 M(数据没变)
- M 状态下本核写 → 仍为 M
- M 状态下遇到远程读 → 必须提供数据并变为 S
- E 状态下本核写 → 变成 M(顺手把主存标记为过时)
- E 状态下远程读 → 变成 S(因为别人也读了)
- S 状态下本核写 → 需要发总线信号,其他核的 S 都失效,自己变成 M
- 任何时候收到无效请求 → 回到 I
2.3 MOESI 协议——多了一个 O 状态
MOESI 是 MESI 的增强版,多了一个 O(Owned) 状态。你在 CXL 协议栈里经常能看到它,尤其是 AMD 的芯片。
O 状态是什么?
本质上是:我修改过数据,但我允许别人共享我的缓存行。主存此时是过时的,但其他缓存在 O 或 S 状态下读到的数据都是来自我这个 O 状态缓存。
O 状态的好处:
- 避免了一次不必要的写回(Writeback)。如果是 M 状态遇到远程读,本来要写回主存再变成 S;现在直接提供数据,主存可以继续留着旧值,节省了带宽。
- 减少延迟。远程读直接从另一个核的缓存拿数据,比走内存控制器快得多。
我调过一次 MOESI 的协议违例:核 A 是 O,核 B 是 S,然后核 A 又写了一次数据。按照协议,核 A 必须发总线信号让核 B 的 S 失效,但我们的 snoop 过滤逻辑漏掉了核 B,导致核 B 读到了脏数据。嗯,查了两天才定位到。
2.4 CXL 对缓存一致性提出了什么要求?
CXL 不是重新发明一致性协议,它是在已有的 MESI/MOESI 基础上做跨设备扩展。具体来说:
- 支持多个一致性域:主机 CPU 侧有自己的缓存,CXL 设备(比如内存扩展器、加速器)也有自己的缓存。CXL 需要保证这两个域之间的数据一致性。
- 高效的 Snoop 过滤:不能每个事务都广播到所有缓存。CXL 的监听过滤单元(Snoop Filter)会记录每个缓存行在哪个域,只给相关的缓存发 snoop。
- 支持 Back-Invalidation:设备缓存的某些行可能会被主机侧偷偷作废,设备必须能响应。
- 支持 Coherent Passthrough:设备发出的请求带上一致性标签,让主机知道这个请求是否需要 snoop。
- 时序要求:CXL 的一致性事务延迟有严格的上限,毕竟它是串行链路。
你可以把 CXL 的一致性引擎想象成一个分布式的 MOESI 管理器,每个 CXL 端口都是一个“代理”,通过链路层消息(如 SnoopReq、SnoopRsp、MemReq)来同步状态。
说到这里,你可能会问:CXL 具体是怎么把 MOESI 状态映射到链路上的?这个我们下个章节会详细拆解。但先记住核心原则:每个缓存行在 CXL 域里都有一个 Home Agent 负责维护它在主机和设备之间的状态。Snoop Filter 就是通过查这个状态的“分布图”来决定哪些端口需要被通知。