设为首页 加入收藏

TOP

内存屏障和 volatile 语义(二)
2018-10-06 21:33:06 】 浏览:203
Tags:内存 屏障 volatile 语义
; b=1; } public void print(){ while(b==0) ; assert a==1; }

smb_mb()就是内存屏障指令,英文memory barries。它的作用,是在后续的store动作之前,将sotre buffer中的内容刷新到cacheline。这个操作的效果是让本地的cacheline的操作顺序和代码的顺序一致,也就是让其他cpu观察到的该cpu的cacheline操作顺序被分为smp_mb()之前和之后。要达到这个目的有两种方式

  • 遇到smp_mb()指令时,暂停cpu执行,将当前的store_buffer全部刷新到cacheline中,完成后cpu继续执行
  • 遇到smp_mb()指令时,cpu继续执行,但是所有后续的store操作都进入到了store buffer中,直到store buffer之前的内容都被刷新到cacheline,即使此时需要store的内容的cacheline是M或者E状态,也只能先写入store buffer中。这样的策略,既可以提升cpu效率,也保证了正确性。当之前store buffer的内容被刷新到cacheline完成后,后面新增加的内容也会有合适的时机刷新到cacheline。把store buffer想象成一个FIFO的队列就可以了。

下面来看,当有了smp_mb()之后,程序的执行情况。所有的初始假设与上面相同。

序号 cpu0的步骤(执行set) cpu1的步骤(执行print)
1 想写入a=1,但是由于a不在自身的cacheline中,向cpu1发送read invalidate消息 执行while(b==0),由于b不在自身的cacheline中,向cpu0发送read消息
2 向store buffer中写入a=1 等待cpu0响应的read response消息
3 遇到smp_mb(),等待直到可以将store buffer中的内容刷新到cacheline 等待cpu0响应的read response消息
4 等待直到可以将store buffer中的内容刷新到cacheline 收到cpu0发来的read invalidate消息,发送a=0的值,同时将自身a所在的cacheline修改为invalidate状态
5 收到cpu1响应的read response和invalidate ack消息,将a=0的值设置到cacheline,随后store buffer中a=1的值刷新到cacheline,设置cacheline状态为M 等待cpu0响应的read response消息
6 由于b就在自身的cacheline中,并且状态为M或者E,设置值为b=1 等待cpu0响应的read response消息
7 收到cpu1的read请求,将b=1的值传递回去,同时设置该cacheline状态为s 等待cpu0响应的read response消息
8 收到cpu0的read response信息,将b设置为1,程序跳出循环
9 由于a所在的cacheline被设置为invalidate,因此向cpu0发送read请求
10 收到cpu1的read请求,以a=1响应,并且将自身的cacheline状态修改为s 等待cpu0的read response响应
11 收到read response请求,将a设置为1,执行程序判断,结果为真

可以看到,在有了内存屏障之后,程序的真实结果就和我们的预期结果相同了。

invalidate queue

使用了store buffer后,cpu的store性能会提升很多。然后store buffer的容量是很小的(越快的东西,成本就越高,一定就越小),cpu以中等的频率填充store buffer。如果不幸发生比较多的cache miss,那么很快store buffer就被填满了,cpu只能等待。又或者程序中调用了smp_mb()指令,这样后续的操作都只能进入store buffer,而不管相关cacheline是否处于M或者E状态。

store buffer很容易满的原因是因为收到其他cpu的invalidate ack的速度太慢。而cpu发送invalidate ack的速度太慢是因为cpu要等到将对应的cacheline设置为invalidate后才能发送invalidate ack。有的时候太多invalidate请求,cpu的处理速度就跟不上。为了加速这个流程,硬件设计者设计了invaldate queue来加速这个过程。收到的invalidate请求先放入invalidate queue,然后之后立刻响应invalidate ack消息。而cpu可以在随后慢慢的处理这些invalidate消息。当然,这里必须不能太慢。也就是说,cpu实际上给出了一个承诺,如果一个invalidatge请求在invalidate queue中,那么对于这个请求相关的cacheline,在该请求被处理完成前,cpu不会再发送任何与该cacheline相关的MESI消息。在有了store buffer和invalidate queue后,cpu的处理速度又可以更高。下面是结构图。

但是在引入了invalidate queue又会导致另外一个问题。下面先来看代码

public void set(){
  a=1;
  smp_mb();
  b=1;
}
public void print(){
 while(b==0)
 ;
 assert a==1;
}

代码与上面的例子相同,但是初始条件不同了。这次a同时存在于cpu0和cpu1之中,状态为s。b是cpu0独享,状态为E或者M。

序号 cpu0的步骤(执行set) cpu1的步骤(执行print)
1 想写入a=1,但是由于a的状态是s,向cpu1发送invalidate消息 执行while(b==0),由于b不在自身的cacheline中,向cpu0发送read消息
2 向store buffer中写入a=1 收到cpu0的invalidate消息,放入invalidate queue,响应invalidate ack消息。
3 遇到smp_mb(),等待直到可以将store buffer中的内容刷新到cacheline。立刻收到cpu0的invalidate ack,将store buffer中的a=1写入到cacheline,并且修改状态为M 等待cpu0响应的read response消息
4 由于b就在自己的cacheline中,写入b=1,修改状态为M 等待cpu0响应的read response消息
5 收到cpu1响应的read请求,将b=1作为响应回传,同时将cacheline的状态修改为s。 等待cpu0响应的read response消息
6 收到read response,将b=1写入cacheline,程序跳出循环
7 由于a所在的cacheline还未失效,load值,进行比对,assert失败
8 cpu处理invalidate queue的消息,将a所在的cachel
首页 上一页 1 2 3 4 下一页 尾页 2/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇SpringBoot | 第二十二章:定时任.. 下一篇SpringBoot | 第二十一章:异步开..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目