一文真正掌握內存屏障memory_barrier及其用途

  在linux源碼中經常遇到__asm__函數。它其實是函數asm的宏定義

  #define __asm__ asm,asm函數讓系統執行彙編語句。

  __asm__常常與__volatile__一起出現。__volatile__限制編譯器不能對下面的彙編語句進行優化處理。

  現代cpu通常具有多級緩存,寄存器、一級、二級、三級緩存。當處理器發出內存訪問請求時,會先查看緩存內是否有請求數據。如果存在(命中),則不經訪問內存直接返回該數據;如果不存在(失效),則要先把內存中的相應數據載入緩存,再將其返回處理器。距離寄存器越遠、速度越慢、容量也越大。如下所示:

  緩存之所以有效,主要是因爲程序運行時對內存的訪問呈現局部性(Locality)特徵。這種局部性既包括空間局部性(Spatial Locality),也包括時間局部性(Temporal Locality)。有效利用這種局部性,緩存可以達到極高的命中率。

  但是這帶來一個問題,因爲cpu和編譯器的優化,編寫的代碼並不總是按照編寫的順序進行執行,可能被優化掉或合併掉,從而導致程序結果並不符合預期。爲了解決這種重排序導致的不正確性,程序中通常採用volatile修飾變量以避免被寄存器緩存。

   從本質上來說,volatile變量(不管是java還是c)都是通過內存屏障機制來實現的,只不過彙編指令("lock; addl $0,0(%%rsp)" : : : "memory", "cc")能夠保證本進程中之前的所有非volatile變量值都會從寄存器刷到L3緩存或內存,取決於是一個socket核內的不同core訪問還是跨socket核訪問。所以c/c++中如下顯示的內存屏障用法是需要一刀切刷乾淨的原因。

#define pg_memory_barrier_impl()        \
    __asm__ __volatile__ ("lock; addl $0,0(%%rsp)" : : : "memory", "cc")

/*
 * The memory barrier has to be placed here to ensure that any flag
 * variables possibly changed by this process have been flushed to main
 * memory, before we check/set is_set.
 */
pg_memory_barrier();

/* Quick exit if already set */
if (latch->is_set)
    return;

latch->is_set = true;

查看內存屏障的彙編實現

gcc -c -O2可以生成目標文件(默認是O0,看不出編譯器優化後的差異,走O2就很清晰的看出差異了),通過objdump將以十六進制和彙編代碼的形式顯示.o文件的內容。如下:

#include <iostream>
 
int foo = 10;
 
int main(int argc, const char * argv[]) {
    // insert code here...
    volatile int a = foo + 10;
    // __asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
    volatile int b = foo + 20;
    printf("%d %d\n",a,b);
    return 0;
}
[lightdb@lightdb-dev ~]$ gcc -O2 -c test_as.cpp 
[lightdb@lightdb-dev ~]$ objdump -D test_as.o
0000000000000000 <main>:
   0:    48 83 ec 18              sub    $0x18,%rsp
   4:    8b 05 00 00 00 00        mov    0x0(%rip),%eax        # a <main+0xa>
   a:    bf 00 00 00 00           mov    $0x0,%edi
   f:    8d 50 0a                 lea    0xa(%rax),%edx
  12:    83 c0 14                 add    $0x14,%eax
  15:    89 54 24 08              mov    %edx,0x8(%rsp)
  19:    89 44 24 0c              mov    %eax,0xc(%rsp)
  1d:    8b 54 24 0c              mov    0xc(%rsp),%edx
  21:    31 c0                    xor    %eax,%eax
  23:    8b 74 24 08              mov    0x8(%rsp),%esi
  27:    e8 00 00 00 00           callq  2c <main+0x2c>
  2c:    31 c0                    xor    %eax,%eax
  2e:    48 83 c4 18              add    $0x18,%rsp
  32:    c3                       retq   
  33:    66 66 2e 0f 1f 84 00     data16 nopw %cs:0x0(%rax,%rax,1)
  3a:    00 00 00 00 
  3e:    66 90                    xchg   %ax,%ax

 

然後包含了memory_barrier的差別。如下:

0000000000000000 <main>:
   0:    48 83 ec 18              sub    $0x18,%rsp
   4:    8b 05 00 00 00 00        mov    0x0(%rip),%eax        # a <main+0xa>
   a:    83 c0 0a                 add    $0xa,%eax
   d:    89 44 24 08              mov    %eax,0x8(%rsp)
  11:    f0 83 04 24 00           lock addl $0x0,(%rsp)      # 內存屏障
  16:    8b 05 00 00 00 00        mov    0x0(%rip),%eax        # 1c <main+0x1c>
  1c:    bf 00 00 00 00           mov    $0x0,%edi
  21:    83 c0 14                 add    $0x14,%eax
  24:    89 44 24 0c              mov    %eax,0xc(%rsp)
  28:    8b 54 24 0c              mov    0xc(%rsp),%edx
  2c:    31 c0                    xor    %eax,%eax
  2e:    8b 74 24 08              mov    0x8(%rsp),%esi
  32:    e8 00 00 00 00           callq  37 <main+0x37>
  37:    31 c0                    xor    %eax,%eax
  39:    48 83 c4 18              add    $0x18,%rsp
  3d:    c3                       retq   
  3e:    66 90                    xchg   %ax,%ax

如果a和b不是volatile,會進一步重排序,如下:

0000000000000000 <main>:
   0:    48 83 ec 08              sub    $0x8,%rsp
   4:    8b 05 00 00 00 00        mov    0x0(%rip),%eax        # a <main+0xa>
   a:    8d 70 0a                 lea    0xa(%rax),%esi
   d:    f0 83 04 24 00           lock addl $0x0,(%rsp)
  12:    8b 05 00 00 00 00        mov    0x0(%rip),%eax        # 18 <main+0x18>
  18:    bf 00 00 00 00           mov    $0x0,%edi
  1d:    8d 50 14                 lea    0x14(%rax),%edx
  20:    31 c0                    xor    %eax,%eax
  22:    e8 00 00 00 00           callq  27 <main+0x27>
  27:    31 c0                    xor    %eax,%eax
  29:    48 83 c4 08              add    $0x8,%rsp
  2d:    c3                       retq   
  2e:    66 90                    xchg   %ax,%ax

java可通過hsdis將字節碼文件.class生成彙編碼,這是sun官方提供的工具。如下:

public class Bar {
    volatile int a = 1;
    volatile static int b = 2;

    public int sum(int c) {
        return a + b + c;
    }

    public static void main(String[] args) {
        new Bar().sum(3);
    }
}

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp -XX:CompileCommand=dontinline,*Bar.sum -XX:CompileCommand=compileonly,*Bar.sum Bar

 

===

https://blog.csdn.net/qq_39312683/article/details/96908239  裏面的例子中彙編碼和gcc生成的有些大

 https://zhuanlan.zhihu.com/p/476210914 以jctools演示重排序的優化,主要靠padding

https://blog.csdn.net/yexiangCSDN/article/details/100773963  asm函數彙編解釋

https://www.cnblogs.com/yfii/p/14661346.html 生成彙編

https://blog.csdn.net/jackgo73/article/details/126031027

https://blog.csdn.net/wohu1104/article/details/110730799 預處理、編譯、彙編、鏈接的過程和選項

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章