[zz]Memory Barriers(内存屏障): a Hardware View for Software Hackers 阅读笔记

Memory Barriers: a Hardware View for Software Hackers(原文地址:http://www.puppetmastertrading.com/images/hwViewForSwHackers.pdf)是一篇介绍CPU缓存及内存屏障的原理,通过阅读这篇文章使我对CPU缓存工作原理和内存屏障有了新的认识,也让我对java内存模型有了新的理解,一下是本人关于这篇文章的前5章做的总结,限于本人的英语水平和相关知识的欠缺存在很多理解不正确的地方,欢迎大家指出。

1. 缓存结构

1.1 cache line结构(cache line)

这里写图片描述 
- cache line为什么设计成多行两列 
多行实际上是硬件hash结构,首先根据内存地址可以直接映射到某一cache line(比如地址0X…000–>0x0,0X…FO1–>OXF)。可以实现快速定位,否则需要逐行匹配,耗费时间

  • 为什么需要设计成两列的模式 
    如果直接使用单列的硬件hash缓存结构,由于缓存数据经常会出现冲突,剔除原有缓存,造成缓存命中率大大降低。加上一列可以在出现冲突时有备用地址保存冲突元素
  • cache line每一次缓存多大的数据 
    cache line每一次缓存一块数据,比如cache line大小是64kb,则每次缓存是64kb数据。连续64kb的数据实际上映射到一个缓存块上去,比如0X00000002和0X00000003属于同一个缓存块,cache line其中一个都会缓存另外一个。

1.2 CPU总体缓存架构

这里写图片描述 
缓存总体上由,缓存器,store buffer,invalidate queues三部分组成,其中缓存器用于存储缓存数据项store buffer用于存储cpu写入的数据项,invalidate queues用于保存接收到无效消息时,暂时存储的位置

2. 缓存协议

2.1 缓存状态MESI

缓存的主要状态有如下4种

  • M:modified状态 
    表示当前cache line的数据项被修改过(未保存至内存),这种状态下该数据是被该CPU占有的,其他任何CPU中都不会存在有效的相同数据项。
  • E:exclusive状态 
    表示该数据被本CPU拥有,在其他CPU的缓存中都不存在对应的数据,这种情况下该CPU可以直接修改该数据项,而不需要发送消息
  • S:shared状态 
    表示该数据被多个CPU所共享(存在于多个CPU缓存中),这种情况下CPU不能直接修改该数据项
  • I:invalid状态 
    表示该数据无效,即删除,CPU加载该数据时不需要重新加载,不能直接使用

2.2 缓存协议消息

缓存消息主要有如下6种

  • Read消息 
    该消息包含读的物理地址,一般用于加载数据
  • Read Respionse消息 
    该消息包含前面Read消息所请求的数据,可以由其他CPU缓存或者内存发出
  • Invalidate消息 
    该消息包含无效的物理地址,由某个CPU缓存发出,所有接收到消息的缓存需要移除对应的数据项(置无效)
  • Invalidate Acknowledge消息 
    在接收到Invalidate消息并移除对应数据后,相应的CPU缓存需要发送此消息
  • Read Invalidate消息 
    该消息包含读的物理地址,同时让其他CPU缓存移除对应数据。该消息可以接收到一个Read Response消息和一系列Invalidate Acknowledge消息
  • Writeback消息 
    该消息包含物理地址和需要被会写内存的数据,这个消息允许缓存为存放其他数据清除该数据所占的空间,否则该数据不能被移除

2.3 MESI状态转换 
这里写图片描述 
以下转换以CPU0的角度解释 
(1)CPU0自身发送消息导致的状态转换

  • a:m->e 
    发送writeback消息,将数据写入内存,同时将当前数据项的状态置为Exclusive
  • b:e->m 
    不需要发送任何消息,CPU0拥有数据项,直接将数据写入cache line
  • j:i->e 
    CPU0意识到将要保存一个数据但是不在他的缓存中,CPU0发送read invalidate消息,在接收到一个read response消息和一系列validate acknowledge消息后,改变状态为exclusive,此时CPU0可以通过b保存数据项(也有说加载数据项后,发现其他缓存中不存在该数据,这种解释应该是错的)
  • d:i-m 
    CPU0执行一个原子性读写操作,直接保存一个数据项但是不在他的缓存中,CPU0发送read invalidate消息,在接收到一个read response消息和一系列validate acknowledge消息后,改变状态为modfied
  • k:i->s 
    CPU0发送read消息,加载数据项,得到read response消息后改变状态为shared
  • e:s->m 
    CPU0执行一个原子性读写操作,直接保存一个数据,但是该数据当前以shared(只读)状态在他的缓存中,CPU0发送invalidate消息,在接收到一系列validate acknowledge消息后,改变状态为modfied
  • h:s->e 
    CPU0意识到将要保存一个数据,但是该数据当前以shared(只读)状态在他的缓存中,CPU0发送invalidate消息,在接收到一个read response消息和一系列validate acknowledge消息后,改变状态为exclusive,此时CPU0可以通过b保存数据项; 
    或者所有其他CPU发送消息writeback将数据回写至内存(i-e可否知道自己单独存储?)

(2)其他CPU发送消息给CPU0导致的状态转换

  • c:m->i 
    CPU0接收到read消息,发送read response消息同时将modfied状态的数据项发送出去,改变自身状态置shared
  • f:m-s 
    CPU0接收到read消息,发送read response消息同时将modfied状态的数据项发送出去,改变自身状态置shared
  • i:e->i 
    其他CPU执行一个原子性读写操作,发送read invalidate消息,CPU0接收到消息后发送read response消息同时将modfied状态的数据项发送出去,然后发送invalidate acknowledge消息并将该数据项置invalidate
  • g:e->s 
    其他CPU读取一个数据项,发送read消息,CPU0接收消息后发送数据项,同时将其状态置为shared
  • l:s-i 
    其他CPU想要保存一个数据项,发送invalidate消息,CPU0接收消息后将该数据项状态置为invalidate

3. store buffer和内存屏障(memory barriers)

3.1 store buffer

(1)store buffer如何工作 
CPU0在写入共享数据时,直接将数据写入store buffer中,同时发送invalidate消息,等接收到所有的invalidate acknowledge消息时再将数据存储至cache line中

(2)为什么需要store buffer 
这里写图片描述 
虽然加上缓存后可以使数据的读取更加有效率,但是对于数据的存储来说却并不是非常有效率考虑如下: 
CPU0存储数据项,发送invalidate消息,此时需要等待接收所有invalidate acknowledge消息才能将数据保存至cache line,这对存储来说是非常耗时的,而且CPU0总是会将数据存储至cache line中

(3)为什么需要让CPU可以从store buffer中加载数据 
我们看一个例子,如下: 
这里写图片描述

  • 初始状态 CPU0拥有变量b=0,CPU1拥有变量a=0
  • CPU0执行a=1,cache misssing,发送read invalidate消息(为什么不是invalidate消息?)
  • CPU0直接存储a=1至store buffer中
  • CPU0接收CPU1发送的read response消息(a=0)
  • CPU0从cache line中载入a=0
  • CPU0接收invalidate acknowledge消息,将a=1写入cache line
  • CPU0执行b=a+1,其中a=0,且CPU0拥有b(状态为exclusive),所有直接写入cache line,并将b状态置为modified,此时b=1
  • CPU0执行assert(b==2)–>false 
    上述问题可以让CPU0直接加载a时直接读取store buffer中的a

3.2 内存屏障

(1)首先看一个例子 
这里写图片描述

  • 初始状态CPU0拥有b=0,CPU1拥有a=0,CPU0执行foo,CPU1执行bar
  • CPU0执行a=1,CPU0缓存中不拥有a,所有将a=1放入store buffer,同时发送read invalidate消息
  • CPU1执行while(b==0)continue,CPU1缓存中不存在b,所以发送read消息
  • CPU0执行b=1,CPU0拥有b,所以直接写入缓存中(b状态为modfied)
  • CPU0接收到read消息,发送read response消息,同时将b状态编程shared
  • CPU1接收CPU0发送来的b=1的数据
  • CPU1结束while(b==0)的循环
  • CPU1执行assert(a==1),CPU1中a=0,所以FALSE
  • CPU1接收到read invalidate消息,发送a同时将a置invalid状态,此时已经晚了
  • CPU0接收到read response和invalidate acknowledge消息,将store buffer中的a=1存储至缓存中(a状态为modfied)

(2)上述问题的解决方案

使用内存屏障,该内存屏障主要使CPU简单的停止存储数据直到store buffer为空或者使用store buffer存储后续的数据直到先前的数据全部保存至缓存

代码如下(主要加上了内存屏障) 
这里写图片描述

  • 初始状态CPU0拥有b=0,CPU1拥有a=0,CPU0执行foo,CPU1执行bar
  • CPU0执行a=1,CPU0缓存中不拥有a,所有将a=1放入store buffer,同时发送read invalidate消息
  • CPU1执行while(b==0)continue,CPU1缓存中不存在b,所以发送read消息
  • CPU0执行smp_mp(),标记当期的store buffer中的数据项
  • CPU0执行b=1,CPU0拥有b,但是当前store buffer中有被标记的数据项(即上面的a),所有CPU0只能将b=1存储至store buffer
  • CPU0接收到read消息,发送read response消息,同时将b状态编程shared
  • CPU1接收CPU0发送来的b=0的数据
  • CPU1继续while(b==0)的循环,此时CPU1缓存中的b=0
  • CPU1接收到read invalidate消息,发送a同时将a置invalid状态
  • CPU0接收到read response和invalidate acknowledge消息,将store buffer中的a=1存储至缓存中(a状态为modfied)
  • CPU0的store buffer中只要a一个表标记,a被存储至缓存中,所以此时b也可以存储了,但是现在CPU0中b的状态是shared,所以CPU0发送invalidate消息
  • CPU1接收invalidate消息,发送acknowledge消息
  • CPU0接收acknowledge消息,将b状态置为exclusive,同时可以将store buffer中的b=1写入缓存中
  • CPU1执行while(b==0)但是此时CPU1缓存中不包含b,发送read消息
  • CPU0接收read消息,发送b=1,同时将b状态置为shared
  • CPU1接收CPU0发送来的b=1的数据
  • CPU1结束while(b==0)的循环
  • CPU1执行assert(a\==1),CPU1缓存中此时并不包含a,所以发送read消息,当其接收CPU0返回的a=1消息时,执行assert(a==1)此时正确

4. invalidate queues和内存屏障

4.1 invalidate queues

(1)invalidate queues如何工作 
CPU在接收到invalidate消息后,不用等到CPU真正将相应的缓存置为无效状态,CPU可以直接将对应数据加入invalidate queues中,同时直接发送invalidate acknowledge响应,当然在对应的数据被处理前,CPU不能再向其他CPU发送有关该数据的无效消息 
(2)为什么需要invalidate queues 
因为缓存存储器的容量有限(很小),CPU很容易将其填满(特别是加入了内存屏障的情况下),通过加入invalidate queues可以让其他CPU快速响应acknowledge消息,以将数据从store buffer保存至缓存器中

4.2 内存屏障

(1)首先看一个例子 
这个例子还是使用3.2(1)中的代码

  • 初始状态CPU0拥有b=0(独有),存有a=0(shared),CPU1存有a=0(shared),CPU0执行foo,CPU1执行bar
  • CPU0执行a=1,CPU0缓存a的状态是shared,将a=1放入store buffer,同时发送invalidate消息
  • CPU1执行while(b==0)continue,CPU1缓存中不存在b,所以发送read消息
  • CPU0执行b=1,CPU0拥有b,所以直接将其存储至缓存中
  • CPU0接收到read消息,发送read response消息,同时将b状态变成shared
  • CPU1接收invalidate消息,将其加入invalidate queues,同时直接发送invalidate acknowledge消息(此时CPU1任然拥有a)
  • CPU1接收CPU0发送来的b=1的数据
  • CPU1结束while(b==0)的循环
  • CPU1执行assert(a==1),但是此时a的值为0,因为CPU1的缓存中还存在a
  • CPU1处理invalidate queues,将a移除缓存,但是此时已经晚了
  • CPU0接收到invalidate acknowledge消息,保存a=1至缓存中

(2)上述问题的解决方案 
使用内存屏障,该内存屏障主要将invalidate queues中的数据项都标记,CPU后续的加载数据都必须等到这些被标记的数据处理完毕 
代码如下: 
这里写图片描述

  • 初始状态CPU0拥有b=0(独有),存有a=0(shared),CPU1存有a=0(shared),CPU0执行foo,CPU1执行bar
  • CPU0执行a=1,CPU0缓存a的状态是shared,将a=1放入store buffer,同时发送invalidate消息
  • CPU1执行while(b==0)continue,CPU1缓存中不存在b,所以发送read消息
  • CPU0执行b=1,CPU0拥有b,所以直接将其存储至缓存中
  • CPU0接收到read消息,发送read response消息,同时将b状态变成shared
  • CPU1接收invalidate消息,将其加入invalidate queues,同时直接发送invalidate acknowledge消息(此时CPU1任然拥有a)
  • CPU1接收CPU0发送来的b=1的数据
  • CPU1结束while(b==0)的循环
  • CPU1执行smp_mb内存屏障,将invalidate queues中的数据标记
  • CPU1执行assert(a==1),但是此时a被内存屏障标记,存在于invalidate queues中,所有不能加载a直至invalidate queues中a的消息被处理了
  • CPU1处理invalidate queues,将a移除缓存
  • CPU1此时可以加载a了,但是此时CPU1中并不包含a,所有发送read消息
  • CPU0接收到invalidate acknowledge消息,保存a=1至缓存中
  • CPU0接收read消息,将a=1发送至CPU1
  • CPU1接收CPU0返回的a=1消息,执行assert(a==1)此时正确

5. 读写内存屏障

上述例子中的内存屏障会同时处理store buffer和invalidate queues,但是在我们的代码中foo并不需要处理invalidate queues,同样的bar也无需处理store buffer,所以一些CPU架构将两者分开处理,分别是读内存屏障和写内存屏障如下:

  • 写内存屏障主要解决写入数据至缓存存储器时,保证后续的写操作不能再这个写操作之前(也就是内存指令不能重排),这样可以避免CPU0执行a=1(写入store buffer),b=1(直接写入),然后其他CPU读取CPU0数据时,造成读到了b=1的操作,而未得到a=1的导致的结果,通过内存屏障强制a=1发生在b=1之前
  • 读内存屏障主要解决invalidate queues中的数据和缓存中数据的冲突问题,保证CPU读取数据时必须先执行完invalidate queues中的任务,否则CPU0执行a=1,b=1操作时,CPU1可能读取到的a还是a=0(因为a=1发送的无效消息,但是该消息并不直接将CPU1缓存中的a置为无效),但是b=1(直接从CPU0读取
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值