• Sword 内存屏障-读写屏障指令


    写屏障指令
        cpu为了优化指令的执行效率,引入了store buffer(forwarding),而又因此导致了指令执行顺序的变化。

    要保证这种顺序一致性,无法靠硬件优化,需要在软件层面支持,cpu提供了写屏障(write memory barrier)指令,

    Linux操作系统将写屏障指令封装成了smp_wmb()函数,cpu执行smp_mb()的思路是,会先把当前store buffer中的数据刷到cache之后,

    再执行屏障后的"写入操作",该思路有两种实现方式: 一是简单地刷store buffer,但如果此时远程cache line没有返回,则需要等待,

    二是将当前store buffer中的条目打标,然后将屏障后的"写入操作"也写到store buffer中,cpu继续干其他的事,当被打标的条目全部刷到cache line,

    之后再刷后面的条目,以第二种实现逻辑为例,我们看看以下代码执行过程:

    void foo() {
        a = 1;
        smp_wmb()
        b = 1;
    }
    void bar() {
        while (b == 0) continue;
        assert(a == 1)
    }
    cpu1执行while(b == 0),由于cpu1的cache中没有b,发出Read b消息。
    cpu0执行a
    =1,由于cpu0的cache中没有a,因此它将a(当前值1)写入到store buffer并发出Read Invalidate a消息。
    cpu0看到smp_wmb()内存屏障,它会标记当前store buffer中的所有条目(即a
    =1被标记)。
    cpu0执行b
    =1,尽管b已经存在在cache中(Exclusive),但是由于store buffer中还存在被标记的条目,因此b不能直接写入,只能先写入store buffer中。
    cpu0收到Read b消息,将cache中的b(当前值0)返回给cpu1,将b写回到内存,并将cache line状态改为Shared。
    cpu1收到包含b的cache line,继续while (b
    == 0)循环。
    cpu1收到Read Invalidate a消息,返回包含a的cache line,并将本地的cache line置为Invalid。
    cpu0收到cpu1传过来的包含a的cache line,然后将store buffer中的a(当前值1)刷新到cache line,并且将cache line状态置为Modified。
    由于cpu0的store buffer中被标记的条目已经全部刷新到cache,此时cpu0可以尝试将store buffer中的b
    =1刷新到cache,
    但是由于包含b的cache line已经不是Exclusive而是Shared,因此需要先发Invalidate b消息。
    cpu1收到Invalidate b消息,将包含b的cache line置为Invalid,返回Invalidate ACK。
    cpu1继续执行while(b
    == 0),此时b已经不在cache中,因此发出Read消息。
    cpu0收到Invalidate ACK,将store buffer中的b
    =1写入Cache。
    cpu0收到Read消息,返回包含b新值的cache line。
    cpu1收到包含b的cache line,可以继续执行while(b
    == 0),终止循环,然后执行assert(a == 1),此时a不在其cache中,因此发出Read消息。
    cpu0收到Read消息,返回包含a新值的cache line。
    cpu1收到包含a的cache line,断言为真。

    Invalid Queue
    引入了store buffer,再辅以store forwarding,写屏障,看起来好像可以自洽了,然而还有一个问题没有考虑: store buffer的大小是有限的,

    所有的写入操作发生cache missing(数据不再本地)都会使用store buffer,特别是出现内存屏障时,

    后续的所有写入操作(不管是否cache missing)都会挤压在store buffer中(直到store buffer中屏障前的条目处理完),因此store buffer很容易会满,

    当store buffer满了之后,cpu还是会卡在等对应的Invalidate ACK以处理store buffer中的条目。因此还是要回到Invalidate ACK中来,

    Invalidate ACK耗时的主要原因是cpu要先将对应的cache line置为Invalid后再返回Invalidate ACK,一个很忙的cpu可能会导致其它cpu都在等它回Invalidate ACK。

    解决思路还是化同步为异步: cpu不必要处理了cache line之后才回Invalidate ACK,而是可以先将Invalid消息放到某个请求队列Invalid Queue,

    然后就返回Invalidate ACK。CPU可以后续再处理Invalid Queue中的消息,大幅度降低Invalidate ACK响应时间。
    加入了invalid queue之后,cpu在处理任何cache line的MSEI状态前,都必须先看invalid queue中是否有该cache line的Invalid消息没有处理。

    另外,它也再一次破坏了内存的一致性。请看代码:

    //假设a, b的初始值为0,a在cpu0,cpu1中均为Shared状态,b在cpu0独占(Exclusive状态),cpu0执行foo,cpu1执行bar
    void foo() {
        a = 1;
        smp_wmb()
        b = 1;
    }
    void bar() {
        while (b == 0) continue;
        assert(a == 1)
    }
    cpu0执行a=1,由于其有包含a的cache line,将a写入store buffer,并发出Invalidate a消息。
    cpu1执行while(b
    == 0),它没有b的cache,发出Read b消息。
    cpu1收到cpu0的Invalidate a消息,将其放入Invalidate Queue,返回Invalidate ACK。
    cpu0收到Invalidate ACK,将store buffer中的a
    =1刷新到cache line,标记为Modified。
    cpu0看到smp_wmb()内存屏障,但是由于其store buffer为空,因此它可以直接跳过该语句。
    cpu0执行b
    =1,由于其cache独占b,因此直接执行写入,cache line标记为Modified。 cpu0收到cpu1发的Read b消息,将包含b的cache line写回内存并返回该cache line,本地的cache line标记为Shared。
    cpu1收到包含b(当前值1)的cache line,结束while循环。
    cpu1执行assert(a
    == 1),由于其本地有包含a旧值的cache line,读到a初始值0,断言失败。
    cpu1这时才处理Invalid Queue中的消息,将包含a旧值的cache line置为Invalid。

    问题在于第9步中cpu1在读取a的cache line时,没有先处理Invalid Queue中该cache line的Invalid操作,其实cpu还提供了读屏障指令,Linux将其封装成smp_rmb()函数,将该函数插入到bar函数中,就像这样:

    void foo() {
        a = 1;
        smp_wmb()
        b = 1;
    }
    void bar() {
        while (b == 0) continue;
        smp_rmb()
        assert(a == 1)
    }

    和smp_wmb()类似,cpu执行smp_rmb()的时,会先把当前invalidate queue中的数据处理掉之后,再执行屏障后的"读取操作"。

    备注

    smp_rmb(): 在invalid queue的数据被刷完之后再执行屏障后的读操作。
    smp_wmb(): 在store buffer的数据被刷完之后再执行屏障后的写操作。
    smp_mb(): 同时具有读屏障和写屏障功能。
  • 相关阅读:
    Luogu 1080 【NOIP2012】国王游戏 (贪心,高精度)
    Luogu 1314 【NOIP2011】聪明的质检员 (二分)
    Luogu 1315 【NOIP2011】观光公交 (贪心)
    Luogu 1312 【NOIP2011】玛雅游戏 (搜索)
    Luogu 1525 【NOIP2010】关押罪犯 (贪心,并查集)
    Luogu 1514 引水入城 (搜索,动态规划)
    UVA 1394 And Then There Was One / Gym 101415A And Then There Was One / UVAlive 3882 And Then There Was One / POJ 3517 And Then There Was One / Aizu 1275 And Then There Was One (动态规划,思维题)
    Luogu 1437 [HNOI2004]敲砖块 (动态规划)
    Luogu 1941 【NOIP2014】飞扬的小鸟 (动态规划)
    HDU 1176 免费馅饼 (动态规划)
  • 原文地址:https://www.cnblogs.com/zhanggaofeng/p/15202435.html
Copyright © 2020-2023  润新知