[SPDK/NVMe存儲技術分析]006 - 內存屏障(MB)

在多核(SMP)多線程的狀況下,若是不知道CPU亂序執行的話,將會是一場噩夢,由於不管怎麼進行代碼Review也不可能發現跟內存屏障(MB)相關的Bug。內存屏障分爲兩類:html

  • 跟編譯有關的內存屏障: 告訴編譯器,不要優化我,俺不須要
  • 跟CPU有關的內存屏障: 告訴CPU, 不要亂序執行,謝謝

1. NVMeDirect中的內存屏障linux

/* nvmedirect/include/lib_nvmed.h */

38 #define COMPILER_BARRIER() asm volatile("" ::: "memory")

因爲NVMeDirect依賴於Linux內核的NVMe驅動(nvme.ko)實現,因此NVMeDirect並不須要實現它本身的與CPU相關的內存屏障。多線程

2. SPDK中的內存屏障dom

/* src/spdk-17.07.1/include/spdk/barrier.h */

47 /** Compiler memory barrier */
48 #define spdk_compiler_barrier() __asm volatile("" ::: "memory")
49
50 /** Write memory barrier */
51 #define spdk_wmb()              __asm volatile("sfence" ::: "memory")
52
53 /** Full read/write memory barrier */
54 #define spdk_mb()               __asm volatile("mfence" ::: "memory")

在SPDK中,不只實現了與編譯相關的內存屏障,還實現了與CPU有關的內存屏障。 可是, 在與CPU有關的MB中, 讀內存屏障(Read memory barrier)並無實現。函數

3. DPDK中的內存屏障優化

在DPDK中,內存屏障的實現要複雜一點,由於支持x86, ARM和PowerPC三種平臺。 以x86爲例,代碼實現以下:atom

  • 與編譯相關的MB
/* src/dpdk-17.08/lib/librte_eal/common/include/generic/rte_atomic.h */

132 /**
133  * Compiler barrier.
134  *
135  * Guarantees that operation reordering does not occur at compile time
136  * for operations directly before and after the barrier.
137  */
138 #define rte_compiler_barrier() do {             \
139         asm volatile ("" : : : "memory");       \
140 } while(0)
  • 與CPU相關的MB
/* src/dpdk-17.08/lib/librte_eal/common/include/arch/x86/rte_atomic.h */

52 #define rte_mb()             _mm_mfence()
54 #define rte_wmb()            _mm_sfence()
56 #define rte_rmb()            _mm_lfence()

58 #define rte_smp_mb()         rte_mb()
60 #define rte_smp_wmb()        rte_compiler_barrier()
62 #define rte_smp_rmb()        rte_compiler_barrier()

64 #define rte_io_mb()          rte_mb()
66 #define rte_io_wmb()         rte_compiler_barrier()
68 #define rte_io_rmb()         rte_compiler_barrier()

另外,DPDK在對ARM32的MB支持中,使用了gcc的內嵌函數__sync_synchronize(), 例如:spa

/* src/dpdk-17.08/lib/librte_eal/common/include/arch/arm/rte_atomic_32.h */

52 #define rte_mb()  __sync_synchronize()
60 #define rte_wmb() do { asm volatile ("dmb st" : : : "memory"); } while (0)
68 #define rte_rmb() __sync_synchronize()

因而,讓咱們反彙編看看gcc的__sync_synchronize()究竟是怎麼回事。.net

$ cat -n foo.c
     1  int main(int argc, char *argv[])
     2  {
     3          int n = 0x1;
     4          __sync_synchronize();
     5          return ++n;
     6  }
$ gcc -g -Wall -m32 -o foo foo.c
$ gdb foo
...<snip>...
(gdb) disas /m main
Dump of assembler code for function main:
2       {
   0x080483ed <+0>:     push   %ebp
   0x080483ee <+1>:     mov    %esp,%ebp
   0x080483f0 <+3>:     sub    $0x10,%esp

3               int n = 0x1;
   0x080483f3 <+6>:     movl   $0x1,-0x4(%ebp)

4               __sync_synchronize();
   0x080483fa <+13>:    lock orl $0x0,(%esp)

5               return ++n;
   0x080483ff <+18>:    addl   $0x1,-0x4(%ebp)
   0x08048403 <+22>:    mov    -0x4(%ebp),%eax

6       }
   0x08048406 <+25>:    leave
   0x08048407 <+26>:    ret

End of assembler dump.

$ gcc -g -Wall -m64 -o foo foo.c
$ gdb foo
...<snip>...
(gdb) disas /m main
Dump of assembler code for function main:
2       {
   0x00000000004004d6 <+0>:     push   %rbp
   0x00000000004004d7 <+1>:     mov    %rsp,%rbp
   0x00000000004004da <+4>:     mov    %edi,-0x14(%rbp)
   0x00000000004004dd <+7>:     mov    %rsi,-0x20(%rbp)

3               int n = 0x1;
   0x00000000004004e1 <+11>:    movl   $0x1,-0x4(%rbp)

4               __sync_synchronize();
   0x00000000004004e8 <+18>:    mfence

5               return ++n;
   0x00000000004004eb <+21>:    addl   $0x1,-0x4(%rbp)
   0x00000000004004ef <+25>:    mov    -0x4(%rbp),%eax

6       }
   0x00000000004004f2 <+28>:    pop    %rbp
   0x00000000004004f3 <+29>:    retq

End of assembler dump.

由於沒有ARM平臺,就在x86上分別進行32位和64位的編譯,因而發現__sync_synchronize()對應的彙編指令是線程

  • 32位
4               __sync_synchronize();
   0x080483fa <+13>:    lock orl $0x0,(%esp)
  • 64位
4               __sync_synchronize();
   0x00000000004004e8 <+18>:    mfence

關於lock指令前綴和mfence指令,後面再講。

4. Linux內核中的內存屏障

Linux內核支持不少種平臺,這裏僅以x86爲例:

/* linux-4.11.3/arch/x86/include/asm/barrier.h */

13 #ifdef CONFIG_X86_32
14 #define mb()  asm volatile(ALTERNATIVE("lock; addl $0,0(%%esp)", "mfence", \
15                                        X86_FEATURE_XMM2) ::: "memory", "cc")
16 #define rmb() asm volatile(ALTERNATIVE("lock; addl $0,0(%%esp)", "lfence", \
17                                        X86_FEATURE_XMM2) ::: "memory", "cc")
18 #define wmb() asm volatile(ALTERNATIVE("lock; addl $0,0(%%esp)", "sfence", \
19                                        X86_FEATURE_XMM2) ::: "memory", "cc")
20 #else
21 #define mb()    asm volatile("mfence" ::: "memory")
22 #define rmb()   asm volatile("lfence" ::: "memory")
23 #define wmb()   asm volatile("sfence" ::: "memory")
24 #endif

5. 總結

5.1 在x86_64平臺上實現內存屏障(MB)

從NVMeDirect到SPDK, 再到DPDK和Linux內核, 咱們能夠得出在x86_64平臺上,與內存屏障(MB)有關的實現可概括爲:

  • 與編譯有關的MB實現
#define XXX_compiler_barrier()          asm volatile(""       ::: "memory")
  • 與CPU有關的MB實現
#define XXX_mb                          asm volatile("mfence" ::: "memory")
#define XXX_rmb                         asm volatile("lfence" ::: "memory")
#define XXX_wmb                         asm volatile("sfence" ::: "memory")

其中,

  • volatile是C語言的關鍵字,主要目的是告訴編譯器不要作優化。 關於volatile的說明, 請參考這裏
  • mfence是彙編指令,用於設定讀寫屏障(Memory)。有關mfence指令,請參考這裏
  • lfence是彙編指令,用於設定讀屏障 (Load)。
  • sfence也是彙編指令, 用於設定寫屏障 (Store)。

5.2 lock指令前綴

lock指令前綴與原子操做有關。對於Lock指令前綴的總線鎖,早期CPU芯片上有一條引線#HLOCK pin, 若是彙編語言的程序中在一條指令前面加上前綴"lock"(表示鎖總線),通過彙編之後的機器碼就使CPU在執行這條指令的時候把#HLOCK pin的電平拉低,持續到這條指令結束時放開,從而把總線鎖住,這樣同一總線上的別的CPU就暫時不能經過總線訪問內存了,保證了這條指令在多CPU環境中的原子性。

5.3 使用CPU內存屏障的根本緣由

在SMP(對稱多處理器)中,CPU是多核的,每一個核有本身的cache,讀寫內存都先經過cache。然而內存只有一個,核有多個,也就是說,同一份數據在內存中只有一份,但卻可能同時存在於多個cache line中。那麼,如何進行同步? 答案就是原子操做,注意原子操做的前提是獨佔。假如一個變量X同時存在於核1和核2的cache line中,那麼當核1想要對X進行"原子加(atomic add)"的時候必須先獨佔這個變量X,也就是告訴核2變量X的值在你的cache line已經失效了,之後想要操做X的時候到哥哥我這裏來取最新的值。這看起來很是像鎖,可是沒有用到鎖。(P.S.: 無鎖隊列的實現其實都離不開原子操做) 所以,咱們能夠這麼認爲,內存屏障(mb, wmb, rmb)的本質是用來在CPU各個核的cache line中進行通訊,保證內存數據的更新具備原子性

擴展閱讀:

People seldom do what they believe in. They do what is convenient, then repent.  | 人們不多作他們相信是對的事。他們作比較方便作的事,而後便會後悔。
相關文章
相關標籤/搜索