NOTE

2.1 缓存

CPU Cache、MESI、写缓冲器、无效化队列与内存屏障的历史学习笔记。

Computer Architecture & Assembly创建于 更新于 historical

这是历史学习笔记,可能存在过时或不完整的理解。

2026 注:本文把 MESI、写缓冲器、无效化队列和内存屏障作为简化教学模型来理解。实际缓存一致性协议与内存排序规则依赖具体 CPU 架构;内存屏障也不能简单理解为“清空某个硬件队列”。

[toc]

1. 高速缓存

CPU和内存之间有一层高速缓存,用于弥补两者之间速度的差异。 在高速缓存中查找数据的时候,内存地址分为index,tag,offset三部分。 首先通过index确定bucket 然后通过tag确定entry 在通过offset确定具体的缓存行 最后根据缓存行的flag字段确定是否有效(参考2.1.1)还是失效

1.1. 高速缓存带来的问题

每个处理器都有一个高速缓存,都保存一份同一个变量的缓存数据。某个时刻处理器A更新了这个变量,其他处理器如何得知? 这就需要引入缓存一致性协议。

2. 缓存一致性

2.1. MESI协议

类似于MySql的两阶段锁:读写锁。读并发,写互斥。

他把缓存entry 分成四种状态

  • Invalid:初始状态。不包含缓存数据
  • Shared:每个处理器上的entry可能都有一份一样的缓存。缓存数据与内存数据一致。
  • Exclusive:只有一个处理器上的entry保留了一份缓存。缓存数据与内存数据一致。
  • Modified:某个处理器更新了缓存行,但并未写入内存。缓存数据与内存数据不一致。

2.1.1. 具体实现

  • Processor0需要读取数据S,状态为I。Processor1状态为M/E/S。 Processor0向总线发出Read请求 Processor1接收到请求后,查看缓存状态 如果是M,说明Processor1正在往内存中写入数据,等他写完后把状态改为S。再回复Processor0 如果是I,那么Processor0会中内存中读取数据。 如果是S,那么直接回复Processor0。
  • Processor0需要写入数据S, 如果Processor0的的状态为M/E,那么可以直接写入缓存并把状态改为M。 如果Processor0的状态为I,那么首先发送Invalid消息给Processor1使其缓存失效得到回复后,再把数据写入缓存该状态为M。
  • 总结 读的时候如果有其他处理器在写,那么等待其他处理器写入缓存再写入内存再读 写的时候如果有其他处理器已经缓存了,那么先让他们全部失效再写入缓存以及内存

2.1.2. MESI的性能问题

写操作必须等待其他所有处理器删除缓存中的内容并回复后才能写入

3. 写缓冲器和无效化队列

3.1. 写缓冲器

比高速缓存更小的存储器 引入该部件后,写操作如下: 如果entry状态为E或者M,会直接把数据写入缓存行而不发送任何消息 如果entry状态为S,会将写操作的数据写入写缓冲器中,发送Invalid消息不等待回复 如果entry状态为I,会将写操作的数据写入写缓冲器中,发送Read Invalid消息不等待回复

总结 如上所属,处理器写入的时候可以不等待Invalid消息

3.2. 无效化队列

其他处理器接收到Invalid消息后直接把该消息写入队列中就回复,后面再慢慢处理

3.3. 带来的问题

可见性和重排序

4. 重排序问题

写缓冲器如3.1说的,Processor0更新数据可能存放在写缓冲器中没有写入缓存,发送Invalid消息后,其他处理器还未收到消息,此时缓存中的数据是旧的

4.1. 分类

StoreLoad processor1先执行写S,processor0后执行读S,本来processor0应该能读到新数据的其实不能。好像Load先于Store执行一样

StoreStore processor1先执行写A,后执行写B;processor0先执行读B,在执行读A 。本来processor0应该能读到最新的A数据,其实不能。好像processor1先执行写B再执行写A一样。

LoadLoad 本来processor1先执行Load1再执行Load2的,但是由于写缓冲器的原因在processor0看来就像是先执行Load2再执行Load1一样

LoadStore

5. 可见性问题

processor1更新了数据到写缓冲器中未写入高速缓存,processor0读到的是旧的数据,这叫做可见性问题?

5.1. 解决

  • flush 写缓冲器
  • flush无效化队列 这两点都是通过编译器的特殊指令:内存屏障来实现的。 前者使用的是StoreBarrier,后者使用的是LoadBarrier。

6. 内存屏障

有几种重排序,就有集中内存屏障。 统一用XY表示,意思是左边的任何X指令都必须在右侧的Y指令之前执行。 当然左侧的X可以自由重排,右边的Y也可以自由重排。

6.1. 具体实现

LoadLoad屏障 通过清空无效化队列实现。

StoreStore屏障 通过对写缓冲器中的entry标记?

StoreLoad 可以实现其他三种屏障。通过清空无效化队列,并把写缓冲器的entry flush进高速缓存中实现。

7. 参考