现在的compiler与CPU为了最佳化执行效能,必要时可能重新安排执行程式的流程顺序。
1.compiler最佳化可依据CPU的instruction issue数目,执行的latency cycles以及程式流程,在不影响程式上下文结果下重排或简化程式。
2.硬件设计最佳化:
基于以上原因,你的程式的执行顺序与流程可能在编译时被重排或者修改,CPU执行结果的出现顺序又可能与assembly看到的不同,compiler跟CPU只会保证执行结果上下文时正确的。这在SMP环境下,其他CPU或是IO因为上述因素可能得到非预期的执行结果。我们需要在某些CPU/CPU或CPU/IO之间需要通过的地址确保compiler与CPU的执行顺序,memory barrier提供这样的功能。
1.volatile
keyword:volatile是一个type qualifier。它声明锁修饰的变量的值有可能被memory-mapped IO或者是asynchronously interrupting function修改,这个关键字告诉compiler不要针对此变量的存取做最佳化。你可以对变量设定volatile,但是他对所有存取这个变量的地方都会造成效果。这造成能效的减损。Linux kernel里面提供ACCESS_ONCE()
macro在使用上进一步优化,只在需要的地方才套用volatile这个关键字,保留给programmer更多弹性:
#define ACCESS_ONCE(x) (*(volatile typeof(x)) &(x))
基本上就是在有需要阻止最佳化的地方通过类型转换来增加volatile修饰:
static int rcu_gp_in_progress(struct rcu_state *rsp) { return (ACCESS_ONCE(rsp->completed) != ACCESS_ONCE(rsp->gpnum)); }
表示我们希望读取rsp->completed
和rsp->gpnum
动作不要被最优化。
compiler barrier:compiler barrier 本身是一個 sequence point
int A, B; void foo() { A = B + 1; B = 0; }
有可能在加了-O2
被 reorder 成
B = 0; A = B + 1;
GCC 使用下列的 inline assembly 來表示 compiler barrier
asm volatile("" ::: "memory");
如同上面的程式碼改寫成以下的方式,我們可以確保編譯時期保持預期的順序。
A = B + 1; asm volatile("" ::: "memory"); B = 0;
Linux kernel 在include/linux/compiler-gcc.h 中定義 compiler barrier macro barrier() 。
#define barrier() __asm__ __volatile__("": : :"memory")
每个CPU architecture根据各自的memory model,通常会提供自己的barrier instruction,以达到不同程度的读写顺序的保证。如ARM dmb等,有的有不同程度与作用范围的barrier,以达到细读控制。
在 Linux 中,這些指令在 arch 下被包成通用的介面,分類介紹如下
mb()/rmb()/wmb()
:rmb() 確保 barrier 之前的 read operation 都能在 barrier 之後的 read operation 之前發生,簡單來說就是確保 barrier 前後的 read operation 的順序;wmb() 如同 rmb() 但是只針對 write operation。mb() 則是針對所有的 memory access。smp_mb()/smp_rmb()/smp_wmb()
:在 SMP 的系統被定義成 mb()/rmb()/wmb() ,UP 時就只是 compiler barrier。可特別用在只於 SMP 時才需要 barrier 的地方[8]。dma_rmb/dma_wmb()
:如果 architecture 對於 barrier 作用的範圍有提供更 fine-grained 的控制,在 device driver 中需要同步 CPU 與 IO 中的 memory data 時 我們就不需要使用作用達到整個系統的 barrier。smp_load_acquire()/smp_store_release()
: 这部分单向的barrier。ACQUIRE确保之后所有memory operation都只在ACQUIRE之后出现;RLEASE则是确保之前的所有memory operation都在RELEASE之前出现。通常这两个承兑出现。通过两个marco,确保之前的critical section之内的变量存取都在这次critical section前完成。read_barrier_depends()/smp_read_barrier_depends()
:只有在 barrier 上下的資料 存取有相依性時才有作用,這樣我們就可以避免使用 rmb() 達到更輕量的控制。但是這 只有 ALPHA CPU 才有支援,其他的 architecture 都是定義成空巨集。smp_mb__before_atomic()/smp_mb__after_atomic()
:在某些沒有 return value 的 atomic operation 中有些沒有使用 memory barrier。這兩個 macro 讓我們在這些操作前 後確保資料一致性。Barrier 在需要時幫助我們達到記憶體存取的順序的準確與可預測性。但是相對的它也減 低了原本效能最佳化的好處。它在其它 Concurrency 機制的實現上是個不可或缺的角色!