缓存(Cache)缓存
CPU的读/写(以及取指令)单元正常状况下甚至都不能直接访问内存——这是物理结构决定的;CPU都没有管脚直接连到内存。相反,CPU和一级缓存(L1 Cache)通信,而一级缓存才能和内存通信。大约二十年前,一级缓存能够直接和内存传输数据。现在,更多级别的缓存加入到设计中,一级缓存已经不能直接和内存通信了,它和二级缓存通信——而二级缓存才能和内存通信。或者还可能有三级缓存。oop
缓存是分“行”(cache line)的,一个行对应一块存储空间,大小是3二、64或128字节。每一个缓存行知道本身对应什么范围的物理内存地址(缓存行对应的标志位与内存虚拟地址的子集对应),根据每组的行数不一样和组数的不一样分为: 1, 直接映射高速缓存(每组只有一个缓存行) 2,组相联高速缓存(每组有多个缓存行) 3, 全相联高速缓存(只有一个缓存组) 。不一样形式的高速缓存对应了不一样的内存地址映射关系(有效位 - 标志位 - 行内偏移位)优化
当CPU看到一条读内存的指令时,它会把内存地址传递给一级数据缓存(或可戏称为L1D$)。一级数据缓存会检查它是否有这个内存地址对应的缓存行。若是没有,它会把整个缓存段从内存(或者从更低一级的缓存,若是有的话)中加载进来。是的,一次加载整个缓存段,这是基于这样一个假设:内存访问倾向于本地化(localized),若是咱们当前须要某个地址的数据,那么极可能咱们立刻要访问它的邻近地址。一旦缓存段被加载到缓存中,读指令就能够正常进行读取。spa
若是咱们只处理读操做,那么事情会很简单,由于全部级别的缓存都遵照如下规律,我称之为:.net
基本定律:在任意时刻,任意级别缓存中的缓存段的内容,等同于它对应的内存中的内容。设计
一旦咱们容许写操做,事情就变得复杂一点了。这里有两种基本的写模式:直写(write-through)和回写(write-back)。直写更简单一点:咱们透过本级缓存,直接把数据写到下一级缓存(或直接到内存)中,若是对应的段被缓存了,咱们同时更新缓存中的内容(甚至直接丢弃),就这么简单。这也遵照前面的定律:缓存中的段永远和它对应的内存内容匹配。blog
回写模式就有点复杂了。缓存不会当即把写操做传递到下一级,而是仅修改本级缓存中的数据,而且把对应的缓存段标记为“脏”段。脏段会触发回写,也就是把里面的内容写到对应的内存或下一级缓存中。回写后,脏段又变“干净”了。当一个脏段被丢弃的时候,老是先要进行一次回写。回写所遵循的规律有点不一样。事务
回写定律:当全部的脏段被回写后,任意级别缓存中的缓存段的内容,等同于它对应的内存中的内容。ip
换句话说,回写模式的定律中,咱们去掉了“在任意时刻”这个修饰语,代之以弱化一点的条件:要么缓存段的内容和内存一致(若是缓存段是干净的话),要么缓存段中的内容最终要回写到内存中(对于脏缓存段来讲)。内存
直接模式更简单,可是回写模式有它的优点:它能过滤掉对同一地址的反复写操做,而且,若是大多数缓存段都在回写模式下工做,那么系统常常能够一会儿写一大片内存,而不是分红小块来写,前者的效率更高。
有些(大多数是比较老的)CPU只使用直写模式,有些只使用回写模式,还有一些,一级缓存使用直写而二级缓存使用回写。这样作虽然在一级和二级缓存之间产生了没必要要的数据流量,但二级缓存和更低级缓存或内存之间依然保留了回写的优点。
一致性协议(Coherency protocols)
只要系统只有一个CPU核在工做,一切都没问题。若是有多个核,每一个核又都有本身的缓存,那么咱们就遇到问题了:若是某个CPU缓存段中对应的内存内容被另一个CPU偷偷改了,会发生什么?
好吧,答案很简单:什么也不会发生。这很糟糕。由于若是一个CPU缓存了某块内存,那么在其余CPU修改这块内存的时候,咱们但愿获得通知。咱们拥有多组缓存的时候,真的须要它们保持同步。或者说,系统的内存在各个CPU之间没法作到与生俱来的同步,咱们其实是须要一个你们都能遵照的方法来达到同步的目的。
注意,这个问题的根源是咱们拥有多组缓存,而不是多个CPU核。咱们也能够这样解决问题,让多个CPU核共用一组缓存:也就是说只有一块一级缓存,全部处理器都必须共用它。在每个指令周期,只有一个幸运的CPU能经过一级缓存作内存操做,运行它的指令。
这自己没问题。惟一的问题就是太慢了,由于这下处理器的时间都花在排队等待使用一级缓存了(而且处理器会作大量的这种操做,至少每一个读写指令都要作一次)。指出这一点是由于它代表了问题不是由多核引发的,而是由多缓存引发的。咱们知道了只有一组缓存也能工做,只是太慢了,接下来最好就是能作到:使用多组缓存,但使它们的行为看起来就像只有一组缓存那样。缓存一致性协议就是为了作到这一点而设计的。就像名称所暗示的那样,这类协议就是要使多组缓存的内容保持一致。
缓存一致性协议有多种,可是你平常处理的大多数计算机设备使用的都属于“窥探(snooping)”协议,这也是这里要讲的。
“窥探”背后的基本思想是,全部内存传输都发生在一条共享的总线上,而全部的处理器都能看到这条总线:缓存自己是独立的,可是内存是共享资源,全部的内存访问都要通过仲裁(arbitrate):同一个指令周期中,只有一个缓存能够读写内存。窥探协议的思想是,缓存不只仅在作内存传输的时候才和总线打交道,而是不停地在窥探总线上发生的数据交换,跟踪其余缓存在作什么。因此当一个缓存表明它所属的处理器去读写内存时,其余处理器都会获得通知,它们以此来使本身的缓存保持同步。只要某个处理器一写内存,其余处理器立刻就知道这块内存在它们本身的缓存中对应的段已经失效。
在直写模式下,这是很直接的,由于写操做一旦发生,它的效果立刻会被“公布”出去。可是若是混着回写模式,就有问题了。由于有可能在写指令执行事后好久,数据才会被真正回写到物理内存中——在这段时间内,其余处理器的缓存也可能会傻乎乎地去写同一块内存地址,致使冲突。在回写模型中,简单把内存写操做的信息广播给其余处理器是不够的,咱们须要作的是,在修改本地缓存以前,就要告知其余处理器。咱们一般叫作MESI协议(译者注:MESI是Modified、Exclusive、Shared、Invalid的首字母缩写,表明四种缓存状态,下面的译文中可能会以单个字母指代相应的状态)。
MESI以及衍生协议
本节叫作“MESI以及衍生协议”,是由于MESI衍生了一系列紧密相关的一致性协议。咱们先从原生的MESI协议开始:MESI是四种缓存段状态的首字母缩写,任何多核系统中的缓存段都处于这四种状态之一。
CPU中每一个缓存行(caceh line)使用4种状态进行标记(使用额外的两位(bit)表示):
M: 被修改(Modified)
该缓存行只被缓存在该CPU的缓存中,而且是被修改过的(dirty), 即与主存中的数据不一致,该缓存行中的内存须要在将来的某个时间点(容许其它CPU读取请主存中相应内存以前)写回(write back)主存。当被写回主存以后,该缓存行的状态会变成独享(exclusive)状态。
E: 独享的(Exclusive)
该缓存行只被缓存在该CPU的缓存中,它是未被修改过的(clean),与主存中数据一致。该状态能够在任什么时候刻当有其它CPU读取该内存时变成共享状态(shared)。一样地,当CPU修改该缓存行中内容时,该状态能够变成Modified状态。
S: 共享的(Shared)
该状态意味着该缓存行可能被多个CPU缓存,而且各个缓存中的数据与主存数据一致(clean),当有一个CPU修改该缓存行中,其它CPU中该缓存行能够被做废(变成无效状态(Invalid))。
I: 无效的(Invalid)
该缓存是无效的(可能有其它CPU修改了该缓存行)。
在一个典型系统中,可能会有几个缓存(在多核系统中,每一个核心都会有本身的缓存)共享主存总线,每一个相应的CPU会发出读写请求,而缓存的目的是为了减小CPU读写共享主存的次数。一个缓存除在Invalid状态外均可以知足 CPU的读请求,一个Invalid的缓存行必须从主存中读取(变成S或者 E状态)来知足该CPU的读请求。
一个写请求只有在该缓存行是M或者E状态时才能被执行,若是缓存行处于S状态,必须先将其它缓存中该缓存行变成Invalid状态(也既是不容许不一样CPU同时修改同一缓存行,即便修改该缓存行中不一样位置的数据也不容许)。该操做常常做用广播的方式来完成,例如:Request For Ownership (RFO)
缓存能够随时将一个非M状态的缓存行做废,或者变成Invalid状态,而一个M状态的缓存行必须先被写回主存。
一个处于M状态的缓存行必须时刻监听全部试图读该缓存行相对就主存的操做,这种操做必须在缓存将该缓存行写回主存并将状态变成E状态以前被延迟执行。
一个处于S状态的缓存行也必须监听其它缓存使该缓存行无效或者独享该缓存行的请求,并将该缓存行变成无效(Invalid)。
一个处于E状态的缓存行也必须监听其它缓存读主存中该缓存行的操做,一旦有这种操做,该缓存行须要变成S状态。
对于M和E状态而言老是精确的,他们在和该缓存行的真正状态是一致的。而S状态多是非一致的,若是一个缓存将处于S状态的缓存行做废了,而另外一个缓存实际上可能已经独享了该缓存行,可是该缓存却不会将该缓存行升迁为E状态,这是由于其它缓存不会广播他们做废掉该缓存行的通知,一样因为缓存并无保存该缓存行的copy的数量,所以(即便有这种通知)也没有办法肯定本身是否已经独享了该缓存行。
从上面的意义看来E状态是一种投机性的优化:若是一个CPU想修改一个处于S状态的缓存行,总线事务须要将全部该缓存行的copy变成invalid状态,而修改E状态的缓存不须要使用总线事务。
MESI定律:在全部的脏缓存段(M状态)被回写后,任意缓存级别的全部缓存段中的内容,和它们对应的内存中的内容一致。此外,在任意时刻,当某个位置的内存被一个处理器加载入独占缓存段时(E状态),那它就不会再出如今其余任何处理器的缓存中。
参考: