介绍

现代计算机体系结构中往往使用多级缓存加快访存速度,往往使用多级缓存,其中1,2级缓存为每个核独享,而3级缓存往往是所有核共享。如果不同核同时对同一地址进行写,那么针对同一地址便有了不同的值,这种问题称为缓存一致性问题

监听协议

监听协议的特点为:

  1. 实现了一种广播机制来通知更改,通常使用总线
  2. 当CPU涉及全局操作时,需要在获取总线控制权之后,才可以进行更改。例如写入内存
  3. 所有处理器一直监听总线,并检测总线上的地址是否在本地中存在,如果有,则进行操作
  4. 写操作的串行化: 写操作必须在获得总线后才可进行

一个简单的监听协议中每个数据块有三种状态:

  1. 无效: 总线中的缓存在本地中不存在
  2. 共享: 当前缓存可能在其他处理器中有副本。只要发出读请求,就会进入共享状态,无论其他处理器中是否存在
  3. 独占: 当前缓存只在该处理器中存在

当接收到来自处理器的请求时,会发生如下转换:

当读写命中时:

当读写未命中时:

当总线中的请求命中时:

总的来说,当写入一块缓存时,这片缓存将会切换到独占状态,并且清除其他处理器中的缓存。当读取一块缓存时,将从其他处理器和内存中取得,并且将状态修改为共享

请求 状态 类型 解释
读命中 处理器 M/S 命中 读本地缓存的数据
读缺失 处理器 I 缺失 将读缺失放到总线上
读缺失 处理器 M 替换 写回块,并且将读缺失放到总线上
写命中 处理器 M 命中 本地缓存写入数据
写命中 处理器 S 一致性 将Invalidate请求放到总线上,写入缓存并切换成独占状态
写缺失 处理器 I 缺失 将写缺失放到总线上
写缺失 处理器 S 替换 将写缺失放到总线上
写缺失 处理器 M 替换 写回块,将写缺失放到总线上
RdMiss 总线 S 无操作 不需要任何操作
RdMiss 总线 M 一致性 将状态切换为S,并且写回块
Invalidate 总线 S 一致性 使该块无效
WtMiss 总线 S 一致性 使该块无效
WtMiss 总线 M 一致性 写回该块,并且使该块无效

该协议有MSI三种状态,因此称为MSI协议。下面是这个协议的一些扩展

  • MESI协议: 添加了独占(exclusive)状态,它表示当前块只在一个缓存中并且没有被修改,它是从S状态划分出去的。这样当写入这个块时就可以不用向总线发出Invalidate请求
  • MOESI协议: 添加了拥有(owned)状态,表示其他处理器有该块并且在存储器中已过时。在MSI协议中,M状态遇到RdMiss将切换成S状态,并且写回块。而现在只需要将状态切换成O,而不需要写回块,当他真正被替换出去时才需要写回。

目录协议

目录是一种集中式的数据结构,它包含所有块在所有处理器中的信息,因此现在只需要查目录就可以获得这些缓存的位置信息。

在目录协议中,同样存在MSI三种状态。但是现在它的操作对象只有三种:

  • 本地节点: 发出请求的节点
  • 宿主节点(目录): 包含所访问的存储单元及其目录项的结点
  • 远程节点: 包含所需存储块,可能和宿主节点是一个节点,也可能不是

本地节点发送给宿主节点的信息:

  • RdMiss: 向宿主节点请求块,并要求把本节点加入共享集
  • WtMiss: 请求宿主节点提供数据,并且使本节点称为独占者
  • Invalidate: 本节点写命中,作废远程副本
  • MdSharer: 要求宿主节点删除被替换块的信息,如果删除后变成空集,将状态切换为I
  • WtBack2: 将修改过的被替换块写回,然后再进行MdSharer操作

宿主节点发给远程节点的信息:

  • Invalidate: 作废远程节点中的副本
  • Fetch: 从远程节点中获得副本,并且将数据送到宿主节点,把远程节点状态改成共享
  • Fetch&Inv: 获得数据并作废远程副本

宿主发给本地:

  • DReply: 将数据发送给本地

远程发给宿宿主

  • WtBack: 把远程的数据给宿主,该消息时远程节点对于Fetch的响应

ACE协议

ace协议是axi4协议的扩展,给硬件一致性提供了方案。

状态


如图为ACE协议定义的cache行状态,

  • UniqueDirty: M
  • SharedClean: S
  • Invalid: I
  • UniqueClean: E
  • SharedDirty: O

Shared表示可能有多个缓存共享该缓存行,Dirty表示当前远程节点是脏的。当然这些状态不都是必须的,可以根据自己的需要自由组合。

事务

首先本地节点读写会发送一些消息给目录,目录根据节点状态将消息转发给对应节点,这些消息在ACE协议中称为事务。

本地节点发出的事务:

事务 状态转换 通道 说明
ReadShared I->E, E->S, M->O R 读一个cache行,不影响其他节点的数据
通常是Load Miss时使用
ReadUnique 状态转换图 R 读cache行并独占它。写回式cache写miss时使用,
CleanUnique S->E,O->M R 清除其他节点中的缓存行,写命中时使用
WriteBack O->I,M->I W 写回并清除所有节点中的副本,cache写回时使用
WriteClean M->E,O->S W 只写回副本,
WriteEvict E->I W 驱逐当前节点
MakeUnique I->M,S->M,O->M R 直接驱逐所有副本,状态转为M。整个缓存行都需要写回时执行。和ReadUnique的区别是不需要发送读请求

RRESP是上级cache返回的信号,例如L1Cache发送读请求,L2Cache会发送该节点的状态。当然如果上级节点是memroy,那么shared和dirty都为0.

写操作的过程可以表示为:

驱逐操作可以表示为:

当本地节点发送请求之后,部分请求需要转发给远程节点进行下一步操作。

本地事务 远程事务
ReadShared ReadShared
ReadUnique ReadUnique
CleanUnique CleanInvalid
WirteBack 无需转发
WriteClean 无需转发
WriteEvict 无需转发
MakeUnique MakeInvalid

信号定义



snoop通道信号(用于master之间交流)


ack信号:

信号 说明
RACK Master 读事务完成通知
WACK Master 写事务完成通知

DOMAIN

  • Non-shareable: 只包含一个Master
  • Inner Shareable: 包含多个Master
  • Outer Shareable: 包含Inner Shareable和其他Master
  • System: 包含所有节点

domain用来指示当前位于哪个域。域这个概念视为了屏障设立的,屏障只确保同一个域内节点的内存序。

BAR

为了内存屏障而设立

SNOOP

事务的类型标识符,具体如下表

RESP

  • RRESP[2]: dirty位。它表示读过来的数据是否是脏的,例如L2Cache当前缓存行是脏的,并且L1Cache不存在该数据,那么L1Cache读取时就需要更改状态为UD
  • RRESP[3]: shared位

  • CRRESP[0]: 数据传输位,例如ReadShared等读操作便可能导致数据传输

  • CRRESP[1]: 错误位,例如ECC错误
  • CRRESP[2]: dirty位
  • CRRESP[3]: shared位
  • CRRESP[4]: unique位

线程同步

线程同步是基于锁来实现的,而实现锁的关键功能是硬件需要提供一种机制,使得每次只有一个核执行成功,也就是硬件原语。一般情况下,这些原语通常不是给用户使用的,而是给精心设计的底层库使用。

一种典型的操作是原子交换。它将寄存器中的一个值和内存中的一个值进行交换。我们可以假定要实现一个简单的锁,0表示这个锁可以占用,1表示已经被占用。处理器设置锁的方式是将寄存器中的1和存储器中的值进行交换,如果获得的值是1,表示其他处理器已经占用,反之则可以将1写入内存中占用锁。

实现上述方式会增加一致性实现的复杂性,因为硬件不允许在该操作之间插入任何操作,并且不能死锁。

替代方法是使用一对指令,第二条指令返回一个值,这个值表示这对指令是否如原子指令一样执行。MIPS和RISC-V都采用了这种方式。

MIPS实现了ll和sc两条指令。其中ll读取内存中的一个值并且将llbit置1,并且硬件还会记录这一次读的地址,如果中间对该地址进行操作那么llbit将会置0。sc将某个值写入内存,但是它在写入前会进行检查,如果llbit为1才会写入。

在拥有原子操作之后,可以利用这些操作实现自旋锁 - 处理器不断尝试获得锁,直到成功为止。

具体操作为:

  • 处理器不断的读取和检测,直到这个锁已经解锁为止
  • 所有处理器都是用交换指令,直到看到1为止,而胜利者将会看到0.