barrierとsmp_mb


バリアとメモリバリアの最適化


バリアの最適化
コンパイラがソースコードをコンパイルすると、ソースコードが最適化され、CPUの並列実行に適したソースコードの命令が並べ替えられます.しかしながら、カーネル同期は命令の再ソートを回避しなければならず、最適化バリア(Optimization barrier)はコンパイラの再ソート最適化動作を回避し、コンパイラ時にバリアを最適化する前の命令がバリアを最適化した後に実行されないことを保証する.
Linuxはマクロbarrierで最適化バリアを実現し、gccコンパイラの最適化バリアマクロ定義は以下の通りである(include/linux/compiler-gcc.h):#define barrier()asm__ __volatile__("": : :"memory")
上記定義では、"_asm_"アセンブリ言語プログラムが挿入されていることを示します.「_volatile_」コンパイラがこの値を最適化することを阻止し、変数が同じ情報を格納した別名ではなく、ユーザー定義の正確なアドレスを使用していることを確認します.「memory」は、命令がメモリユニットを変更したことを示す.
メモリバリア
ソフトウェアは、読み書きバリアを介してメモリアクセス順序を強制することができます.読み書きバリアは壁のようなもので、読み書きバリアを設定する前に開始されたすべてのメモリアクセスは、バリアを設定した後に開始されたメモリアクセスよりも前に完了し、メモリアクセスがプログラムの順序で完了することを確認する必要があります.
読み書きバリアは、プロセッサフレームワークの特殊命令mfence(メモリバリア)、lfence(読み書きバリア)、sfence(書き込みバリア)によって完了します.「x 86-64フレームワーク仕様」の章を参照してください.また、x 86-64プロセッサでは、ハードウェアを操作するアセンブリ言語命令は「シリアル」であり、I/Oポートを操作するすべての命令、lock接頭辞付き命令、書き込み制御レジスタ、システムレジスタ、またはデバッグメモリのすべての命令(cliおよびstiなど)などのメモリバリアの役割も有する.
Linuxカーネルが提供するメモリバリアAPI関数は、次の表に示されています.メモリバリアは、マルチプロセッサおよびシングルプロセッサシステムに使用でき、マルチプロセッサシステムのみに使用される場合はsmp_xxx関数は、シングルプロセッサシステムでは何も必要ありません.
メモリバリアAPI関数は、メモリバリアのマクロ定義機能説明mb()がマルチプロセッサおよびシングルプロセッサのメモリバリアに適用されることを示す.rmb()は、マルチプロセッサおよびシングルプロセッサのリードメモリバリアに適している.wmb()は、マルチプロセッサおよびシングルプロセッサの書き込みメモリバリアに適しています.smp_mb()はマルチプロセッサのメモリバリアに適しています.smp_rmb()は、マルチプロセッサのリードメモリバリアに適しています.smp_wmb()は、マルチプロセッサの書き込みメモリバリアに適しています.
マルチプロセッサおよびシングルプロセッサに適したメモリバリアマクロ定義は、以下の通りです(include/asm-x 86/system.h).
#ifdef CONFIG_X86_32
/* “lock; addl $0,0(%%esp)” , 0 , , , 。 XMM2 CPU , */
#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)
#define rmb() alternative("lock; addl $0,0(%%esp)", "lfence", X86_FEATURE_XMM2)
#define wmb() alternative("lock; addl $0,0(%%esp)", "sfence", X86_FEATURE_XMM)
#else
#define mb() asm volatile("mfence":::"memory")
#define rmb() asm volatile("lfence":::"memory")
#define wmb() asm volatile("sfence" ::: "memory")
#endif
/* , x86-64 */
#define read_barrier_depends() do { } while (0) 

マクロ定義ead_barrier_depends()は、後のリードに依存するすべての保留リード操作をリフレッシュし、後のリード操作は、処理中のリード操作で返されたデータに依存する.x 86-64フレームワークでは、このマクロは必要ありません.これは、このバリアの前に、メモリ領域データから依存する読み取りが並べ替えられていないことを示しています.すべてのリード操作はこの原語を処理し、この原語のリード操作に従う前にメモリにアクセスすることを保証します(他のCPUのcacheは必要ありません).この原語は,ほとんどのCPUにおいてrmb()よりも軽量である.
ローカルCPUとコンパイラはメモリバリアのソート制限に従い、メモリバリア原語のみでソートを保証し、データに依存してもソートを保証することはできません.例えば、*qの読み出し動作はpの読み出し動作に依存し、この2つの読み出し動作はread_barrier_depends()分離.CPU 0およびCPU 1で実行されるプログラム文は、それぞれ以下のように列挙される.
CPU 0                                      CPU 1 b = 2; memory_barrier(); p = &b;                                      q = p;                                                   read_barrier_depends();                                                   d = *q;
以下のコードは、aとbの読み出し動作の間に依存関係がないため、Alpha,yが3に設定され、xが0に設定されるCPUがある.このようなデータ依存関係のない読み取り操作のように、rmb()をソートする必要があります.
CPU 0                                        CPU 1
a = 2; memory_barrier(); b = 3;                                         y = b;                                                    read_barrier_depends();                                                    x = a;
マルチプロセッサに適したメモリバリアマクロ定義は、以下の通りです(include/asm-x 86/system.h).
#ifdef CONFIG_SMP
#define smp_mb() mb()
#ifdef CONFIG_X86_PPRO_FENCE
# define smp_rmb() rmb()
#else
# define smp_rmb() barrier()
#endif
#ifdef CONFIG_X86_OOSTORE
# define smp_wmb() wmb()
#else
# define smp_wmb() barrier()
#endif
#define smp_read_barrier_depends() read_barrier_depends()
#define set_mb(var, value) do { (void)xchg(&var, value); } while (0)
#else
#define smp_mb() barrier()
#define smp_rmb() barrier()
#define smp_wmb() barrier()
#define smp_read_barrier_depends() do { } while (0)
#define set_mb(var, value) do { var = value; barrier(); } while (0)
#endif 

関数rdtsc_barrierはメモリバリアを追加してRDTSC推測を阻止するために使用され、定義されたコード領域でリードタイムスタンプカウンタ(Read Time-stamp Counter,RDTSC)関数(または関数get_cyclesまたはvread)を使用する場合、メモリバリアを追加してRDTSC推測を阻止する必要がある.次のように表示されます.
static inline void rdtsc_barrier(void)
{
    alternative(ASM_NOP3, "mfence", X86_FEATURE_MFENCE_RDTSC);
    alternative(ASM_NOP3, "lfence", X86_FEATURE_LFENCE_RDTSC);

}