5. 基础 - 缓存一致性问题

102 阅读7分钟

一、示例场景

第一步:Core 1 和 Core 2 读取了同一个内存块的数据,在两个 Core 中的 Cache 都缓存了一份内存块的副本,此时Cache 1、Cache 2、主存数据是一致的;

第二步:Core 1 需要写数据了,触发了写操作:

  • 在写直达策略中,新数据会直接写回内存,此时,Cache1 和内存块一致,但 Cache 2 和 Cache 1 、主存是不一致的;
  • 在写回策略中,新数据会延迟写回内存,此时 Cache 1 和内存、Cache 2 均不一致;

   如果此时Core 2 访问了 Cache 2 过时的数据,软件上就会获取到不正确的数据。 因此就需要一种机制,将多个核心的工作联合起来,共同保证多个核心下的 Cache 一致性,这就是缓存一致性机制。

二、MESI 协议简介

   MESI(Modified-Exclusive-Shared-Invalid) 是一个基于状态跟踪的协议,也是目前最经典、最常用的总线嗅探式缓存一致性协议。每个缓存行(Cache Line,缓存管理的最小单位,通常是 64 字节)在每个核心的私有缓存中都维护一个状态标记(State Tag)。协议定义了四个核心状态:

1.M (Modified - 已修改):

  • 含义: 该缓存行中的数据已被当前核心修改过,与主内存中的对应数据不一致。当前核心拥有该数据的唯一、最新副本。
  • 责任: 如果其他核心需要读取这个数据,或者该缓存行需要被替换出去,拥有 M 状态的缓存必须负责将修改后的数据写回主内存。
  • 权限: 当前核心可以自由读写该缓存行,无需与其他核心通信。
  • 类比: 你有一本书的唯一副本,并且你在书上做了笔记(修改)。你的笔记还没同步到图书馆(主存)的底本上。

2.E (Exclusive - 独占):

  • 含义: 该缓存行中的数据与主内存中的数据一致,并且只有当前核心的缓存中有该数据的副本。
  • 权限: 当前核心可以自由读取该缓存行。如果它要写入,因为它拥有独占权,可以直接将状态提升为 M (Modified),无需通知其他核心(因为其他核心没有副本)。
  • 优势: 从 E 状态开始的写操作非常高效,因为不需要广播无效化消息。
  • 类比: 你从图书馆借出了唯一的一本干净的书(与底本一致)。只有你有这本书,你可以随时阅读。如果你想做笔记(写),只需要把书标记成你的笔记版(M),不需要通知别人(因为没人有副本)。

3.S (Shared - 共享):

  • 含义: 该缓存行中的数据与主内存中的数据一致,并且至少有一个其他核心的缓存中也有该数据的副本(可能多个)。
  • 权限: 当前核心可以自由读取该缓存行。如果它要写入,它必须首先向所有其他可能持有该缓存行副本的核心发送“无效化(Invalidate)”消息。其他核心收到无效化消息后,将自己缓存中该行的状态置为 I (Invalid)。当前核心在收到所有其他核心的确认(Acknowledge)后,才能将状态改为 M (Modified) 并进行写入。
  • 类比: 你和朋友都从图书馆借了同一本书的复印本(大家都有,且与底本一致)。如果你想在书上做笔记(写),你必须通知所有借了这本书的朋友:“我的版本要改了,你们的版本作废了!”。等朋友们都确认扔掉(Invalidate)了他们的复印本后,你才能开始做笔记,并成为唯一有效的版本(M)。

4.I (Invalid - 无效):

  • 含义: 该缓存行中的数据是无效的、过时的或者该缓存行是空的。不能使用该缓存行中的数据。
  • 操作: 如果核心需要读取或写入这个内存地址,它必须从其他缓存(如果其他缓存有有效状态如 M, E, S)或主内存中重新获取有效数据。
  • 类比: 你手上的这本书要么是空白页,要么内容已经过时(因为你知道有人改了原版)。你需要重新去获取最新版本。

三、解决流程

MESI 解决上述问题场景流程:

第一步:初始状态 (一致): Core 1 和 Core 2 都读取了内存块 X 的数据。此时:

  • Core 1 的缓存行状态:S (Shared - 共享,数据与内存一致)
  • Core 2 的缓存行状态:S (Shared - 共享,数据与内存一致)

第二步:Core 1 执行对 X 的写入操作: 此时 Core 1 的缓存行当前状态是 S (Shared)。根据MESI 协议规则,一个核心要写入一个处于 S 状态的缓存行时:

  1. 广播“无效化”(Invalidate)请求: Core 1 通过 CPU 的总线(或片上互连网络)向系统中所有其他核心(主要是 Core 2)发送一条消息:“我要修改地址 X 的数据了,请把你们缓存中地址 X 的副本标记为无效!”
  2. 等待确认 (Acknowledge): Core 1 需要等待所有收到无效化请求的核心(这里是 Core 2)发回确认(Acknowledge)信号。
  3. Core 2 的动作 (总线嗅探) :Core 2 一直在“监听”总线上的消息(总线嗅探),当它收到 Core 1 发来的针对地址 X 的无效化请求。它先检查自己的缓存,发现自己确实缓存了地址 X 的数据,并且状态是 S。它将自己缓存中地址 X 的缓存行状态从 S 改为 I (Invalid - 无效),表示这个副本已经过期作废。Core 2 向总线发送确认(Acknowledge)信号给 Core 1。
  4. Core 1 获得独占权并修改:Core 1 收到 Core 2 的确认后,知道自己是系统中唯一拥有有效地址 X 数据的核心了。它将自身缓存行状态从 S 改为 M (Modified - 已修改)。现在 Core 1执行实际的写入操作,修改其缓存行中的数据。此时,内存中的数据仍然是旧的 (因为状态是M,修改尚未写回内存)。

当前状态:

  • Core 1 缓存行 (X):状态 M,数据是最新的(已修改)。
  • Core 2 缓存行 (X):状态 I,数据被标记为无效(过期)。
  • 内存数据 (X):旧的。
  • 一致性暂时破坏 (Core 2 的缓存无效),但协议已介入处理。

第三步:Core 2 再次读取 X:

  1. Core 2 需要访问地址 X 的数据,它检查自己的缓存,发现状态是 I (无效),发生 读未命中 (Read Miss)。因此Core 2 发起总线请求,要求读取地址 X 的数据。
  2. 总线嗅探与响应:Core 1 监听总线,发现 Core 2 请求的是自己处于 M 状态的地址 X,根据协议,拥有 M 状态数据的核心(Core 1)有责任提供最新数据,Core 1 执行写回(Writeback),将 M 状态缓存行中的最新数据写回主内存(或者有时直接通过总线传输给 Core 2,称为“干预”),Core 1 将自己缓存行的状态从 M 改为 S(因为现在 Core 2 也要共享了)。
  3. 状态更新:Core 2 接收到最新数据,将其缓存行状态设置为 S。

当前状态:

  • Core 1 缓存行 (X):状态 S,数据最新。
  • Core 2 缓存行 (X):状态 S,数据最新。
  • 内存数据 (X):最新。
  • 所有副本再次一致!

四、软件上的处理

    MESI 协议是由多层硬件协同实现,保证了数据的最终一致性,但执行顺序、原子性和可见性仍需开发者通过编程手段控制。

1. 强制顺序与互斥

  • 锁(Locks):通过互斥(Mutex)、信号量(Semaphore)等机制,确保临界区代码仅被一个线程执行。
  • 原子操作(Atomic Operations):利用CPU指令(如CAS、LL/SC)实现不可分割的读-修改-写(RMW)。

2. 内存屏障(Memory Barriers)

主要解决乱序执行导致的可见性问题。kernel 中相关函数如下: 在这里插入图片描述