上一节我们知道计算机为了提高运行效率,充分利用CPU资源,特意为CPU的运行提供了缓存,但由此引发一个问题,在多核CPU的情况下,由于每个核心都有自己的缓存,当两个运行在不同核心上的进程或线程都使用内存中同一份数据,如何保证数据一致,CPU有两种设计方法:1、总线加锁 2、缓存一致性协议+缓存锁
一、总线加锁
根据计算机组成结构图我们知道CPU从内存获取数据是通过总线,通过对总线加锁来控制数据一致性问题。
总线锁:当一个处理器往总线上输出LOCK#信号时,其它处理器的请求将被阻塞,此时该处理器此时独占共享内存。
LOCK#信号作用:在CPU的LOCK信号被声明之后,在此期间随同执行的指令会转换成原子指令。在多处理器环境中,LOCK信号能保证,在此信号被声明之后,处理器独占共享内存;
总线控制权的分配:总线仲裁;当只有一个核心触发总线的Lock#信号时,总线仲裁机构会把总线的控制器权完全分给这个核心;如果有多个核心同时触发了总线Lock#,总线仲裁机构会根据一定的算法进行选择,将总线的控制权交给哪个核心
缺点:总线锁把CPU和内存之间的通信锁住了,这使得在锁定期间,其他处理器也不能操作其他内存地址的数据,所以总线锁的开销比较大。早期的CPU采用该方式,Intel P6 CPU开始已经不在使用。
二、缓存锁
缓存锁:由于总线锁的为了实现原子性,他阻塞的了总线,是的其他核心都无法工作,Intel P6 CPU开始就做了一个优化;他不会声明LOCK#信号,它只会对CPU的缓存中的缓存行进行锁定,在锁定期间,其它 CPU 不能同时缓存此数据,在修改之后,通过缓存一致性协议来保证修改的原子性,这个操作被称为“缓存锁”;
cache line(缓存行):cache分成多个组,每个组分成多个行,linesize是cache的基本单位,从主存向cache迁移数据都是按照line size为单位替换的,也就是每次从主存读取一个缓冲行加入(或置换)缓存中。目前主流的CPU Cache的Cache Line大小通常是64字节
1、缓存一致性协议
目前处理器上都有一套完整的协议,来保证Cache一致性,比较经典的Cache一致性协议是MESI协议;
MESI:也称为伊利诺斯协议,是因为该协议由伊利诺斯州立大学提出,是一种广泛使用的支持写回策略的缓存一致性协议,该协议被应用在Intel奔腾系列的CPU中;
MESI的四种状态:
- M(被修改的):该缓存行只被缓存在该CPU的缓存中,并且是被修改过的(dirty),即与主存中的数据不一致,该缓存行中的内存需要在未来的某个时间点(允许其它CPU读取请主存中相应内存之前)写回(write back)主存。当被写回主存之后,该缓存行的状态会变成独享(exclusive)状态。
- E(独享的):该缓存行只被缓存在该CPU的缓存中,它是未被修改过的(clean),与主存中数据一致。该状态可以在任何时刻当有其它CPU读取该内存时变成共享状态(shared)。同样地,当前CPU修改该缓存行中内容时,该状态可以变成Modified状态。
- S(共享的):该状态意味着该缓存行可能被多个CPU缓存,并且各个缓存中的数据与主存数据一致(clean),当有一个CPU修改该缓存行中,其它CPU中该缓存行可以被作废(变成无效状态(Invalid))。
- I(无效的):该缓存是无效的(可能有其它CPU修改了该缓存行)。
MESI的交互流程:
1、假设有三个CPU A、B、C,对应三个缓存分别是cache a、b、 c。在主内存中定义了x的引用值为0。
单核读取:CPU A发出了一条指令,从主内存中读取x。从主内存通过bus读取到缓存中(远端读取Remote read),这时该Cache line修改为E状态(独享),并且自己将一直监听bus总线对该地址发送的消息,直到自己不在使用该cache line。
双核读取:CPU A发出了一条指令,从主内存中读取x;CPU A从主内存通过bus读取到 cache a中并将该cache line 设置为E状态。
CPU B发出了一条指令,从主内存中读取x;CPU B试图从主内存中读取x时,CPU A嗅探到了地址冲突。这时CPU A对相关数据做出响应。此时x 存储于cache a和cache b中,x在chche a和cache b中都被设置为S状态(共享)。
修改数据:CPU A 计算完成后发指令需要修改x=x+1;CPU A 将x设置为M状态,这时CPU B会嗅探到该变化, 则CPU B将本地cache b中的x设置为I状态(无效);
同步数据:CPU B 发出了要读取x的指令;CPU A 嗅探到将有指令操作cache line后,他将修改后的数据同步到主内存,并将cache a 修改为E(独享);CPU B同步主存的数据到cache b,并将状态设置为S,CPU A将同步状态为S
通过以上流程我们发现使用MESI的CPU除了在做内存数据传输的时候和总线交互 ,而且还会通过不停在嗅探总线上发生的数据交换,跟踪其他缓存在做什么。当一个cache line在它所属的处理器去读写内存时,其它处理器都会得到通知,以此来使自己的缓存保持同步。只要某个处理器一写内存,其它处理器马上知道这块内存在它们的缓存段中已失效。
MESI无法控制的场景:
- 每次cache的数据大于缓存行
- CPU不支持MESI协议
2、MESI总结
如果cpu A与cpu B同时发出修改指令,总线将如何选择:通过总线裁决决定哪个CPU先执行修改指令
MESI 可以通过保证数据的可见性,而使数据实现一致性,他无法保证数据的原子性和指令的有序性