为什么需要缓存一致

目前主流电脑的 CPU 都是多核心的,多核心的有点就是在不能提升 CPU 主频后,通过增加核心来提升 CPU 吞吐量。每个核心都有自己的 L1 Cache 和 L2 Cache,只是共用 L3 Cache 和主内存。每个核心操作是独立的,每个核心的 Cache 就不是同步更新的,这样就会带来缓存一致性(Cache Coherence)的问题。

举个例子,如图:Responsive Image

有 2 个 CPU,主内存里有个变量x=0。CPU A 中有个需要将变量x1。CPU A 就将变量x加载到自己的缓存中,然后将变量x1。因为此时 CPU A 还未将缓存数据写回主内存,CPU B 再读取变量x时,变量x的值依然是0

这里的问题就是所谓的缓存一致性问题,因为 CPU A 的缓存与 CPU B 的缓存是不一致的。

如何解决缓存一致性问题

通过在总线加 LOCK 锁的方式

在锁住总线上加一个 LOCK 标识,CPU A 进行读写操作时,锁住总线,其他 CPU 此时无法进行内存读写操作,只有等解锁了才能进行操作。

该方式因为锁住了整个总线,所以效率低。

缓存一致性协议 MESI

该方式对单个缓存行的数据进行加锁,不会影响到内存其他数据的读写。

在学习 MESI 协议之前,简单了解一下总线嗅探机制(Bus Snooping)。要对自己的缓存加锁,需要通知其他 CPU,多个 CPU 核心之间的数据传播问题。最常见的一种解决方案就是总线嗅探。

这个策略,本质上就是把所有的读写请求都通过总线广播给所有的 CPU 核心,然后让各个核心去“嗅探”这些请求,再根据本地的情况进行响应。MESI 就是基于总线嗅探机制的缓存一致性协议。

MESI 协议的由来是对 Cache Line 的四个不同的标记,分别是:

状态
状态
描述
监听任务
Modified已修改该 Cache Line 有效,数据被修改了,和内存中的数据不一致,数据只存在于本 Cache 中Cache Line 必须时刻监听所有试图读该 Cache Line 相对于主存的操作,这种操作必须在缓存将该 Cache Line 写回主存并将状态改为 S 状态之前,被延迟执行
Exclusive独享,互斥该 Cache Line 有效,数据和内存中的数据一直,数据只存在于本 CacheCache Line 必须监听其他缓存读主存中该 Cache Line 的操作,一旦有这种操作,该 Cache Line 需要改为 S 状态
Shared共享的该 Cache Line 有效,数据和内存中的数据一直,数据存在于很多个 Cache 中Cache Line 必须监听其他 Cache Line 使该 Cache Line 无效或者独享该 Cache Line 的请求,并将 Cache Line 改为 I 状态
Invalid无效的该 Cache Line 无效

整个 MESI 的状态,可以用一个有限状态机来表示它的状态流转。需要注意的是,对于不同状态触发的事件操作,可能来自于当前 CPU 核心,也可能来自总线里其他 CPU 核心广播出来的信号。我把各个状态之间的流转用表格总结了一下:

当前状态
事件
行为
下个状态
MLocal Read从 Cache 中读,状态不变M
MLocal Write修改 cache 数据,状态不变M
MRemote Read这行数据被写到内存中,使其他核能使用到最新数据,状态变为 SS
MRemote Write这行数据被写入内存中,其他核可以获取到最新数据,由于其他 CPU 修改该条数据,则本地 Cache 变为 II
当前状态
事件
行为
下个状态
ELocal Read从 Cache 中读,状态不变E
ELocal Write修改数据,状态改为 MM
ERemote Read数据和其他 CPU 共享,变为 SS
ERemote Write数据被修改,本地缓存失效,变为 II
当前状态
事件
行为
下个状态
SLocal Read从 Cache 中读,状态不变S
SLocal Write修改数据,状态改为 M,其他 CPU 的 Cache Line 状态改为 IM
SRemote Read数据和其他 CPU 共享,状态不变S
SRemote Write数据被修改,本地缓存失效,变为 II
当前状态
事件
行为
下个状态
ILocal Read1. 如果其他 CPU 没有这份数据,直接从内存中加载数据,状态变为 E;
2. 如果其他 CPU 有这个数据,且 Cache Line 状态为 M,则先把 Cache Line 中的内容写回到主存。本地 Cache 再从内存中读取数据,这时两个 Cache Line 的状态都变为 S;
3. 如果其他 Cache Line 有这份数据,并且状态为 S 或者 E,则本地 Cache Line 从主存读取数据,并将这些 Cache Line 状态改为 S
E 或者 S
ILocal Write1. 先从内存中读取数据,如果其他 Cache Line 中有这份数据,且状态为 M,则现将数据更新到主存再读取,将 Cache Line 状态改为 M;
2. 如果其他 Cache Line 有这份数据,且状态为 E 或者 S,则其他 Cache Line 状态改为 I
M
IRemote Read数据和其他 CPU 共享,状态不变S
IRemote Write数据被修改,本地缓存失效,变为 II