监听协议

基于监听协议的多核缓存一致性:从MSI到MESI的状态机设计

Posted by CloudingYu on May 25, 2026

一、Cache 一致性问题回顾

1.1 核心问题

多处理器系统中每个 CPU 有自己的 cache,当多个 CPU 持有同一地址的副本时,一个 CPU 修改了数据,其他 CPU cache 中的副本就会变成过时数据。如何保证所有 CPU 看到的 memory 视角一致?

在多核系统中,每个核心独立使用自己的 cache,但共享主存。当某个核心修改了共享数据后,其他核心 cache 中的副本就变成了脏数据——类似于共享存储系统中,一个节点修改了数据而其他节点并未感知的情形。

1.2 两种基本思路

在 cache 一致性问题上,有两种基本思路:

方案 描述 代价
目录协议(Directory-based) 建立一个集中目录,记录每个数据块在哪些 cache 中有副本,谁访问了谁修改了 需要大量内存存放目录信息
监听协议(Snooping-based) 让硬件来监听总线上的消息,不需要软件维护目录 总线成为瓶颈

评估任一方案时都应首先考虑代价。目录协议会消耗相当大的内存空间来存放目录,同时还需要在 cache 中额外存储目录信息——这与页表和快表(TLB)的关系类似。任何提出”加一个目录”的方案,都意味着需要额外的存储开销和访问开销。

1.3 硬件设计的现实约束

在硬件行业中,”能不改就不改”是一个重要的工程原则:

  • 流片成本极高:一次流片失败可能意味着上亿美元的损失
  • 验证成本极高:硬件无法像软件那样随意 patch,出错后无法通过补丁修复
  • 参考设计的价值:实际硬件设计大量依赖成熟参考设计,每一次修改都需要严格的验证

二、监听协议(Snooping Protocol)

2.1 基本思想

监听协议的核心思想是:每个 cache 自己监听总线上的事务,根据总线上的消息判断自己手里的数据是否还有效、是否需要更新。这将对一致性维护的负担从软件转移到了硬件上——各 CPU 自行监听总线变化并做出响应,软件工程师无需直接管理缓存一致性。

2.2 总线作为广播媒介

在基于总线的多处理器系统中,总线是所有事务的广播媒介:

  • 每个处理器访问 memory 时会产生总线事务(transaction)
  • 每个 cache 的 snoop 控制器监听总线上的所有事务
  • 总线控制器负责串行化(serialization)写操作——多个处理器同时发起写请求时,由总线控制器进行仲裁,确保写操作按序执行

在 cache 一致性协议的设计中,cache 控制器不仅需要服务本地 CPU 的读写请求,还必须持续监听总线上的其他事务——包括其他 CPU 的读写操作、数据失效通知和 cache 间数据传输请求。

2.3 监听协议中的两种策略

在监听协议框架下,当一个处理器写数据时,有两种通知方式:

  写更新(Write-update) 写失效(Write-invalidate)
行为 写了以后马上广播,让所有持有者更新 写第一个字节时就广播”作废”,让所有人标记失效
优点 别人读的时候直接 hit,延迟低 总线事务少,省电,对连续多次写友好
缺点 连续写时广播太多,总线拥挤 别人需要读的时候要多做一次 transfer
适用场景 一写多读(如 GPU、神经网络推理) 通用 CPU,模式复杂多变

两种策略各有取舍:写更新策略在广播时产生更多总线流量但读延迟低;写失效策略减少了总线事务但增加了读缺失时的传输开销。在计算机体系结构中,每种方案都有其代价——需要根据实际需求来取舍。

2.4 为什么写失效是主流

在通用 CPU 上,写失效协议更常见:

  1. 总线事务少:对同一块的多个字连续写时,写更新需要对每个字都广播;写失效只需要广播一次
  2. 省电:总线每次传输都消耗能量,在功耗墙面前很重要
  3. 通用 CPU 负载复杂:操作系统上各种任务混合,数据没有明显特征,写失效更平衡

写更新策略在特定领域仍然活跃。在 GPU 内部、专用 AI 芯片中,数据流以流水线方式从前往后写,写更新就很合适。选择哪种策略应根据实际应用的数据访问模式决定。

2.5 总线事务(Bus Transaction)

监听协议中总线上的基本事务包括:

事务 含义 说明
Bus Read 总线上有处理器读缺失,需要去 memory 拿数据 最基本的读请求
Bus Read Exclusive 读进来并即将修改(read with intent to modify) 告诉别人”我要改了,你们作废”
Bus Write Back / Flash 脏数据块被替换掉,需要写回 memory 写回策略才需要
Cache-to-cache Transfer 数据直接在 cache 之间传输 比从 memory 读更快

在支持 cache-to-cache transfer 的系统中,当一份数据已在其他 cache 中时,可以直接从该 cache 获取而不是访问主存,这比从 memory 到 cache 的传输要快得多。

2.6 Cache 控制器的角色

在多处理器一致性协议中,cache 控制器职责大增:

  1. 服务 CPU:处理来自本处理器的 load/store 请求
  2. 监听总线:监听总线上别人的读写和失效通知
  3. 发起总线事务:自己也要在总线上申请、发失效消息、做 transfer

在使用写回策略时,主存中的数据不一定是最新的——最新版本的数据可能存在于某个 cache 中而非主存,因此 cache 控制器在响应读请求时需要判断数据的最新副本在何处。


三、MSI 协议

3.1 三个状态

MSI 协议(Modified, Shared, Invalid)是监听协议中最基础的缓存一致性协议,为每个 cache block 定义三个状态:

状态 缩写 含义
Modified(已修改) M 只有当前 cache 持有,已被修改,主存中是旧数据,当前 cache 是唯一的”新鲜版本”
Shared(共享) S 当前 cache 有一份,可能其他 cache 也有,与主存一致
Invalid(无效) I 这个数据块没有进入 cache,或被别人失效了

M 态的核心含义是:当前 cache 拥有唯一的最新副本。其他处理器需要该数据时,必须从当前 cache 获取,而不能从主存读取。

3.2 MSI 状态机

MSI 协议的核心是有限状态机(FSM),每个 cache block 根据三类事件发生状态迁移:

三类事件

  1. Processor 操作:本处理器发出的 read / write(hit 或 miss)
  2. Bus 操作:总线上观察到的 bus read / bus read exclusive / bus write back
  3. Block replacement:cache block 被替换掉

状态迁移要点

  • M 态:只有自己有,读写都是 hit,不产生总线事务。如果总线上别人要读(bus read),则把自己数据写回 memory 并 transfer 过去,变为 S。如果被替换掉,写回 memory 变为 I。
  • S 态:读是 hit,不产生总线事务。写则需要先发 bus read exclusive(通知别人失效),然后进入 M 态。如果总线上有别人写(bus read exclusive),自己的数据失效进入 I。S 态允许多个 cache 共享同一份数据——一个 cache 发起读请求时,其他持有 S 态副本的 cache 不做排斥。
  • I 态:读则 miss,发 bus read 取数据,进入 S(如果有别人持有)或进入 M(如果没有别人持有且需要写)。写则 miss,发 bus read exclusive 取数据并修改,进入 M。

3.3 MSI 协议详解:典型场景

以下通过一个具体例子(P1 和 P2,数据 A1)展示完整的 MSI 协议运作流程:

场景:memory 中 A1 = 10,两个处理器 P1 和 P2

  1. P1 写 10 到 A1(write miss):
    • 总线上发 bus read exclusive
    • P1 cache 将 A1 读入,状态变为 M(独占、已修改)
    • 此时 P1 cache 中 A1 = 10,memory 中仍是旧值
  2. P1 再读 A1(read hit):
    • M 态下直接 hit,不产生总线事务
    • 状态仍为 M
  3. P2 读 A1(read miss):
    • 总线上发 bus read
    • P1 监听到后:将数据写回 memory,从 M 变为 S
    • P2 cache 读入 A1 = 10,状态为 S
    • 此时 P1 和 P2 都是 S(共享一致)
  4. P2 写 20 到 A1(write hit on S):
    • P2 是 S 态,写需要先通知别人失效
    • 总线上发 bus read exclusive(通知 P1 作废)
    • P1 的 A1 变为 I(失效)
    • P2 变为 M,A1 = 20
    • memory 中 A1 仍为 10(等到 P2 cache block 被替换时才写回)
  5. P2 再写 A2(A2 与 A1 在同一 cache block 中):
    • 因为 A1 和 A2 在同一个 cache line 中
    • P2 是 M 态,写 A2 也是 hit,不产生总线事务
    • P2 cache 中 A2 = 40(原来是 25)

核心规则:M 态下读写都不产生总线事务。一旦有处理器发起 bus read,M 态持有者需要写回 memory、传递数据给请求者,然后降级为 S。一旦有处理器发起 bus read exclusive,S 态持有者直接作废,M 态持有者传输数据并写回 memory 后作废。


四、MSI 协议的问题:虚假共享(False Sharing)

4.1 问题场景

MSI 协议有一个严重的问题:失效粒度过大。因为 cache coherence 是以 cache line(整个 block) 为单位的,失效操作是对整个 block 做的,而不是对单个字。

例如:假设 A1 和 A2 在同一个 cache block 中。P1 和 P2 都持有这个 block(都是 S 态)。现在 P1 修改了 A2,P2 那边的 A1 还能用吗?答案是不能——因为失效是按 block 来的,P1 一改 A2,P2 整个 block 都被 invalidate,P2 的 A1 虽然没被改也失效了。

4.2 无谓开销

这种场景带来了不必要的总线传输开销:

  1. P1 和 P2 共享 block,A1 = 5,A2 = 5(S 态)
  2. P1 改 A2 为 6,发 bus read exclusive
  3. P2 整个 block 被 invalidate(A1 和 A2 都不能用了)
  4. P2 要读 A1(本来还是 5),发现 I 了,必须做 transfer
  5. P1 把整个 block transfer 给 P2,P1 变成 S
  6. 后续又有处理器修改,如此反复

4.3 动机

这个例子揭示了 MSI 协议的一个不足:即使数据没被修改,只要同一个 block 中另一个数据被写了,整个 block 都会被作废,导致不必要的总线传输。这引出了对 MSI 的改进——MESI 协议。


五、MESI 协议

5.1 新增 Exclusive 状态

MESI 协议(Modified, Exclusive, Shared, Invalid)在 MSI 的基础上增加了 E(Exclusive,独占) 状态。

状态 含义
Modified 只有当前 cache 持有,已被修改,与主存不一致
Exclusive 只有当前 cache 持有,但还没修改,与主存一致
Shared 当前 cache 有一份,可能其他 cache 也有,与主存一致
Invalid 数据无效

E 状态的关键洞察是:当从主存读进一个数据时,如果只有当前 cache 持有、且数据与主存一致,那它就是 E 态而不是 S 态。此时如果处理器要写这个数据,直接从 E 变为 M,不需要在总线上发消息通知别人作废——因为根本没有别人持有这个数据。

5.2 E 态的价值

E 态的引入解决了 MSI 的一个痛点:

  • MSI 的问题:处理器从主存读数据时,不知道别人有没有,只能进入 S 态。等到要写的时候,即使是唯一持有者,也要发 bus read exclusive 通知”作废”——但实际上根本没有人有。
  • MESI 的改进:读数据时,如果确保没有别人持有,进入 E 态。从 E 态写数据时,直接变为 M,不需要产生总线事务,省掉一次总线操作。

5.3 Bus Read Exclusive 的作用

在 MESI 协议中,bus read exclusive 是一个关键的总线事务。它的含义是”我要读进来并且要修改”。

  • 如果当前是 M 态,观察到 bus read exclusive → 说明别人要写这个数据 → 自己先把最新数据 transfer 过去、写回 memory,然后变 I
  • 如果当前是 S 态,观察到 bus read exclusive → 说明别人要写 → 自己的副本直接变 I
  • 如果当前是 E 态,观察到 bus read exclusive → 把数据 transfer 过去,变 I

bus read exclusive 与普通 bus read 的关键区别在于多了一个 “X”(exclusive),它向总线上的其他 cache 传达的信号不是”共享”,而是”我要独占并修改”,因此其他 cache 的响应是作废而非保持共享。

5.4 何时给 Memory 写回

MESI 协议中 memory 的更新时机:协议选择在 flash(cache block 被替换)操作时更新 memory

需要注意到实际上所谓的”memory”可能本身就是一层 L3 cache——在层次化存储中,对上级 cache 而言的”主存”,对下级而言仍是 cache。因此数据从 memory 来还是从另一个 cache transfer 过来,逻辑上是一致的——S 态保证了 cache 数据和 memory 一致。

5.5 状态机核心要点

设计 MESI 状态机的基本方法:

  1. 先画出最简单的系统:两个处理器 + 各自的 cache + 总线 + memory
  2. 列出三类事件:处理器读、处理器写、总线上的事务
  3. 逐一分析每个状态遇到读怎么办、遇到写怎么办、遇到总线事务怎么办

对于读请求:如果是 read hit,无论当前是什么状态,都不会改变状态也不会产生总线操作。如果是 read miss,则根据当前状态按协议规则走——发 bus read 或 bus read exclusive 获取数据并转换到相应状态。


六、总结

6.1 核心框架

整个 cache 一致性的核心框架由三个要素构成:两个 CPU 带各自 cache,用总线连起来,底下是 memory。在这个框架中,核心问题是:数据在 cache 之间怎么传送?数据在 cache 和 memory 之间怎么传送?一个 cache 的数据修改后如何使其他 cache 的副本作废?作废时要不要写回 memory?

6.2 与之前知识的联系

本节内容与此前知识的联系:

  • write through vs write back:写时马上更新 memory 还是等替换时再更新 → 一致性协议中对应”何时更新 memory”
  • write update vs write invalidate:写时通知大家更新还是通知大家作废 → 一致性协议的两种策略
  • cache 的 tag/valid/dirty 位:一致性协议用更多状态位(M/S/I/E)来管理

设计中没有绝对的最优方案:如果应用特点是”一写多读”,写更新更合适;如果各种访问模式都有,写失效更省总线带宽。选择应基于实际负载的数据访问模式。