CPU変数
Rev.2を表示中。最新版はこちら。
シングルプロセスシステムでは、割り込みを禁止すれば、リソースの競合を回避できますが、SMPではそうはいきません。SMPではそれぞれのCPUが独自に、しかも平行して、プロセスを実行しています。ユーザモードの延長線上にあるカーネルパスについてもです。しかしリソースは共有しています。従って、そのCPUで割り込みを禁止しても、他のCPUがそのCPUで実行しているクリティカル区間を、実行する事があるからです。その回避としてスピンロックと言う物が登場しました。スピンロックは一種のフラグ(メモリ)です。このメモリをアトミックに参照すれば、このフラグの更新での競合を回避することができます。アトミックのアクセスとは、そのメモリ参照を、メモリ回路で保証するからです。(たぶん)
しかし、スピンロックを取得する場合、取得するまでビジーウエイトでループすることになってしまいます(セマフォと違ってスピンロックは、スタティックな変数等を参照する時に利用されます。)。これはパフォーマンス的によくありません。そこで登場したのがCPU変数というものです。
CPU変数は、そのCPU下で動作しているシステム下でしか、参照できない(させない)と言う変数です。すなわち、CPU変数に関してシングルプロセスのシステムとなるわけで、その競合を回避するには、割り込みを禁止さえすれば言い訳です。従ってCPU変数でのアクセスは高速で、例えばページフレーム管理などは、このCPU変数として管理しています。トータルシステムとしてのページフレームの管理は、全CPU変数を参照することで可能でもあるわけです。
x86については、CPUその物にIDのような物を設定する事はできないようです。ではどのようにしてCPUをユニークに認識しているかと言うことですが、gsないしfsセグメントを各CPUにユニークに持たせ(CPUその物にIDを持たせると言うのでなく、そのCPUで動作している環境下に、IDを持たせると言った感じでしょうか。)、そのセグメントを参照することで、CPU毎に独自の変数を定義していているようです。(たぶん)
get_cpu_varは、CPU変数を取得するマクロです。引数のvarは取得するCPU変数です。このマクロは __this_cpu_ptrへと展開されるようです。(このあたりはアーキテクチャー依存です。) __this_cpu_ptrの詳細は今ひとつ分かりません。以下アバウトです。
__percpu_arg(1)は拡張インラインアセンブラの%1となり、this_cpu_offとなります。this_cpu_offもCPU変数のようで、CPU変数が始まるオフセットで、それと引数のvarのオフセットを足しこんで、tcp_ptr__に設定するというものです。
get_cpu_var(var) #define __this_cpu_ptr(ptr) \ ({ \ unsigned long tcp_ptr__; \ __verify_pcpu_ptr(ptr); \ asm volatile("add " __percpu_arg(1) ", %0" \ : "=r" (tcp_ptr__) \ : "m" (this_cpu_off), "0" (ptr)); \ (typeof(*(ptr)) __kernel __force *)tcp_ptr__; \ })ここで、 __percpu_arg(x)ですが、__percpu_prefixマクロを介して、__percpu_segでオーバプレフィックスするように展開しています。
#define __percpu_arg(x) __percpu_prefix "%P" #x #define __percpu_prefix "%%"__stringify(__percpu_seg)":"__percpu_segはCONFIG_X86_64でgsとし、そうでなければfsとしています。たぶんこのセグメントは、CPU毎のシステム起動時に、それぞれユニークな値を設定することでCPU毎にユニークな変数を定義しているのではと思います・・・。
#ifdef CONFIG_X86_64 #define __percpu_seg gs #define __percpu_mov_op movq #else #define __percpu_seg fs #define __percpu_mov_op movl # endif