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


在多核(SMP)多線程的情況下,如果不知道CPU亂序執行的話,將會是一場噩夢,因為無論怎么進行代碼Review也不可能發現跟內存屏障(MB)相關的Bug。內存屏障分為兩類:

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

1. NVMeDirect中的內存屏障

/* nvmedirect/include/lib_nvmed.h */

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

由於NVMeDirect依賴於Linux內核的NVMe驅動(nvme.ko)實現,所以NVMeDirect並不需要實現它自己的與CPU相關的內存屏障。

2. SPDK中的內存屏障

/* 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為例,代碼實現如下:

  • 與編譯相關的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(), 例如:

/* 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()到底是怎么回事。

$ 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.  | 人們很少做他們相信是對的事。他們做比較方便做的事,然后便會后悔。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM