CPU1会根据内存地址0xxx在高速缓存找到对应的缓存条目,并读取缓存条目的Tag和Flag值。如果此时缓存条目的Flag 是M、E、S三种状态的任何一种,那么就直接从缓存行中读取地址0xxx对应的数据,不会向总线中发送任何消息。
高速缓存内不存在有效数据时:
1、如CPU2 高速缓存内找到的缓存条目状态为I时,则说明此时CPU2的高速缓存中不包含数据s的有效数据副本。
2、CPU2向总线发送Read消息来读取地址0xxx对应的数据s.
3、CPU1(或主内存)嗅探到Read消息,则需要回复Read Response提供相应的数据。
4、CPU2接收到Read Response消息时,会将其中携带的数据s存入相应的缓存行并将对应的缓存条目状态更新为S。
从宏观的角度看,就是上面的流程了,我们再继续深入下,看看在缓存条目为I的时候到底是怎么进行消息处理的
说完了读取数据,我们就在说下CPU1是怎么写入一个地址为0xxx的数据s的
MESI协议解决了缓存一致性的问题,但其中有一个问题,那就是需要在等待其他处理器全部回复后才能进行下一步操作,这种等待明显是不能接受的,下面就继续来看看大神们是怎么解决处理器等待的问题的。
三、写缓冲和无效化队列因为MESI自身有个问题,就是在写内存操作的时候必须等待其他所有处理器将自身高速缓存内的相应数据副本都删除后,并接收到这些处理器回复的Invalidate Acknowledge/Read Response消息后才能将数据写入高速缓存。
为了避免这种等待造成的写操作延迟,硬件设计引入了写缓冲器和无效化队列。
写缓冲器(Store Buffer)在每个处理器内都有自己独立的写缓冲器,写缓冲器内部包含很多条目(Entry),写缓冲器比高速缓存还要小点。
那么,在引入了写缓冲器后,处理器在执行写入数据的时候会做什么处理呢?还会直接发送消息到BUS吗?
我们来看几个场景:
(注意x86处理器是不管相应的缓存条目是什么状态,都会直接将每一个写操作结果存入写缓冲器)
1、如果此时缓存条目状态是E或者M:
代表此时处理器已经获取到数据所有权,那么就会将数据直接写入相应的缓存行内,而不会向总线发送消息。
2、如果此时缓存条目状态是S
此时处理器会将写操作的数据存入写缓冲器的条目中,并发送Invalidate消息。
如果此时相应缓存条目的状态是I ,那就称之为写操作遇到了写未命中(Write Miss),此时就会将数据先写入写缓冲器的条目中,然后在发送Read Invalidate来通知其他处理器我要进行数据更新了。
处理器的写操作其实在将数据写入缓冲器时就完成了,处理器并不需要等待其他处理器返回Invalidate Acknowledge/Read Response消息
当处理器接收到其他处理器回复的针对于同一个缓存条目的Invalidate Acknowledge消息时,就会将写缓冲内对应的数据写入相应的缓存行中
通过上面的场景描述我们可以看出,写缓冲器帮助处理器实现了异步写数据的能力,使得处理器处理指令的能力大大提升。
无效化队列(Invalidate Queue)其实在处理器接到Invalidate类型的消息时,并不会删除消息中指定地址对应的数据副本(也就是说不会去马上修改缓存条目的状态为I),而是将消息存入无效化队列之后就回复Invalidate Acknowledge消息了,主要原因还是为了减少处理器等待的时间。
所以不管是写缓冲器还是无效化队列,其实都是为了减少处理器的等待时间,采用了空间换时间的方式来实现命令的异步处理。
总之就是,写缓冲器解决了写数据时要等待其他处理器响应得问题,无效化队列帮助解决了删除数据等待的问题。
但既然是异步的,那必然又会带来新的问题 -- 内存重排序和可见性问题。
所以,我们继续接着聊。
存储转发(Store Fowarding)通过上面内容我们知道了有了写缓冲器后,处理器在写数据时直接写入缓冲器就直接返回了。
那么问题就来了,当我们写完一个数据又要马上进行读取可咋办呢?话不多说,咱们还是举个例子来说,如图: