在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 預處理、編譯、彙編、鏈接的過程和選項