一、为什么要使用cpu与内存之间要使用cache
我们知道计算机中的程序和数据都是0和1组成的,假设一台计算机的主频是2.9GHz,意思是cpu每秒钟可以处理2.9g个bit(即一个int(4个字节,32个bit)大约10纳秒),而我们从内存中读取一次数据需要1000ns,差了100倍;所以为了提高cpu的处理效率我们需要引用告诉缓存,cpu与缓存的图示如下:
其中cache1和cache2是核cpu私有的,cache3才是共有的。
那问题来了多核cpu之间如何保持缓存数据的一致性?
二、问题背景
1、首先我们假设2个CPU的系统,并且L1 Cache的cache line大小是64 Bytes。两个CPU都读取0x40地址数据,导致0x40开始的64 Bytes内容分别加载到CPU-0和CPU-1的私有的cache line。
2、CPU-0执行写操作,写入值0x01。CPU-0私有的L1 Cache更新cache line的值。然后,CPU-1读取0x40数据,CPU-1发现命中cache,然后返回0x00值,
并不是CPU-0写入的0x01。这就造成了CPU-0和CPU-1私有L1 Cache数据不一致现象。
按照正确的处理流程,我们应该需要以下方法保证多核Cache一致性:
CPU-0修改0x40的时候,除了更新CPU-0的Cache之外,还应该通知CPU-1的Cache更新0x40的数据。
CPU-0修改0x40的时候,除了更新CPU-0的Cache之外,还可以通知CPU-1的Cache将0x40地址所在cache line置成invalid。保证CPU-1读取数据时不会命中自己的Cache。不命中自己的cache之后,我们有两种选择保证读取到最新的数据。
a) 从CPU-0的私有cache中返回0x40的数据给CPU-1;
b) CPU-0发出invalid信号后,将写入0x40的数据写回主存,CPU-1从主存读取最新的数据。
Bus Snooping Protocol(总线广播的方式)
继续以上面的例子说明bus snooping的工作机制。当CPU-0修改自己私有的Cache时,硬件就会广播通知到总线上其他所有的CPU。对于每个CPU来说会有特殊的硬件监听广播事件,并检查是否有相同的数据被缓存在自己的CPU,这里是指CPU-1。如果CPU-1私有Cache已经缓存即将修改的数据,那么CPU-1的私有Cache也需要更新对应的cache line。这个过程就称作bus snooping。如下图所示,我们只考虑L1 dCache之间的一致性。
这种bus snooping方法简单,但要需要每时每刻监听总线上的一切活动。我们需要明白的一个问题是不管别的CPU私有Cache是否缓存相同的数据,都需要发出一次广播事件。这在一定程度上加重了总线负载,也增加了读写延迟。针对该问题,提出了一种状态机机制降低带宽压力。这就是MESI protocol(协议)。
MESI Protocol
MESI是现在一种使用广泛的协议,用来维护多核Cache一致性。我们可以将MESI看做是状态机。我们将每一个cache line标记状态,并且维护状态的切换。cache line的状态可以像tag,modify等类似存储。继续以上面的例子说明问题。
当CPU-0读取0x40数据,数据被缓存到CPU-0私有Cache,此时CPU-1没有缓存0x40数据,所以我们标记cache line状态为Exclusive。Exclusive代表cache line对应的数据仅在数据只在一个CPU的私有Cache中缓存,并且其在缓存中的内容与主存的内容一致。
然后CPU-1读取0x40数据,发送消息给其他CPU,发现数据被缓存到CPU-0私有Cache,数据从CPU-0 Cache返回给CPU-1。此时CPU-0和CPU-1同时缓存0x40数据,此时cache line状态从Exclusive切换到Shared状态。Shared代表cache line对应的数据在"多"个CPU私有Cache中被缓存,并且其在缓存中的内容与主存的内容一致。
继续CPU-0修改0x40地址数据,发现0x40内容所在cache line状态是Shared。CPU-0发出invalid消息传递到其他CPU,这里是CPU-1。CPU-1接收到invalid消息。将0x40所在的cache line置为Invalid状态。Invalid状态表示表明当前cache line无效。然后CPU-0收到CPU-1已经invalid的消息,修改0x40所在的cache line中数据。并更新cache line状态为Modified。Modified表明cache line对应的数据仅在一个CPU私有Cache中被缓存,并且其在缓存中的内容与主存的内容不一致,代表数据被修改。
如果CPU-0继续修改0x40数据,此时发现其对应的cache line的状态是Modified。因此CPU-0不需要向其他CPU发送消息,直接更新数据即可。
如果0x40所在的cache line需要替换,发现cache line状态是Modified。所以数据应该先写回主存。
以上是cache line状态改变的举例。我们可以知道cache line具有4中状态,分别是Modified、Exclusive、Shared和Invalid。取其首字母简称MESI。当cache line状态是Modified或者Exclusive状态时,修改其数据不需要发送消息给其他CPU,这在一定程度上减轻了带宽压力。
MESI Protocol Messages
Cache之间数据和状态同步沟通,是通过发送message同步和沟通。MESI主要涉及一下几种message。
Read: 如果CPU需要读取某个地址的数据。
Read Response: 答复一个读消息,并且返回需要读取的数据。
Invalidate: 请求其他CPU invalid地址对应的cache line。
Invalidate Acknowledge: 回复invalidate消息,表明对应的cache line已经被invalidate。
Read Invalidate: Read + Invalidate消息的组合。
Writeback: 该消息包含要回写到内存的地址和数据。
继续以上的例子,我们有5个步骤。现在加上这些message,看看消息是怎么传递的。
CPU-0发出Read消息。主存返回Read Response消息,消息包含地址0x40的数据。
CPU-1发出Read消息,CPU-0返回Read Response消息,消息包含地址0x40数据。
CPU-0发出Invalidate消息,CPU-1接到消息后,返回Invalidate Acknowledge消息。
不需要发送任何消息。
发送Writeback消息。