netbsd currentのmutex lockについて(その1)
またまたnetbsd currentのソースについて。
今度はmutexを読んでいる最中なので、ここについてもメモを残す。
なおlock debug機能はメモから外します。
ちなみにlock debug機能とは、ロックの状態を保持し、再帰呼び出しロックなど異常な状態などを検知したときにpanicなどしてくれる機能です。(あまり詳しく読んでいませんが....)
[初期化について]
mutex_init()でmutex変数の初期化を行う。
デフォルトでadaptive lockとみなされる。
[ロック/アンロックのI/F]
o Adaptive Lockの場合
mutex_enter()
mutex_exit()
o Spin Lockの場合
mutex_spin_enter()
mutex_spin_exit()
[上記I/Fの実体について]
__HAVE_MUTEX_STUBSおよび__HAVE_SPIN_MUTEX_STUBSが定義されていないアーキテクチャの場合、その実体はkern/kern_mutex.c内の以下のコードにより、mutex_vector_enter()およびmutex_vector_exit()となる。
#ifndef __HAVE_MUTEX_STUBS __strong_alias(mutex_enter,mutex_vector_enter); __strong_alias(mutex_exit,mutex_vector_exit); #endif #ifndef __HAVE_SPIN_MUTEX_STUBS __strong_alias(mutex_spin_enter,mutex_vector_enter); __strong_alias(mutex_spin_exit,mutex_vector_exit); #endif
また、__HAVE_MUTEX_STUBSが定義されているアーキテクチャは(alpha,hppa,m68k,mips,powerpc,sh3,sparc64,vax,x86)である。
これらのアーキテクチャでは、mutex_enter(),mutex_exit()の実装をarch/以下に独自に持っている。
少なくともmips,x86は、アーキ独自のmutex_enter/mutex_exitからそれぞれmutex_vector_enter/mutex_vector_exitが呼び出している。
よって、mutex_vector_enter/mutex_vector_exitの実装をそれぞれ見る。
[mutex_vector_enterその1]
いきなり、以下のマクロがあるが、単なる変数宣言。
普通の変数宣言+コメントではだめなのだろうか?
LOCKSTAT_COUNTER(spincnt); LOCKSTAT_COUNTER(slpcnt); LOCKSTAT_TIMER(spintime); LOCKSTAT_TIMER(slptime); LOCKSTAT_FLAG(lsflag);
mutexロックの種別がspin lockか調べる。
(mutex_enterもmutex_spin_enterも最終的にはmutex_vector_enterがコールされる)
if (MUTEX_SPIN_P(mtx)) {
割り込みレベルをあげた上でtry lockする。そしてロック取得に成功したらそのままreturnする。
(MUTEX_WANTLOCK,MUTEX_LOCKEDはlock debug機能なので略)
そして、ロックがとれない場合でかつMPな環境でない場合はpanic.
MPな環境なら、ロックがとれるまで頑張る。
do { if (panicstr != NULL) break; while (__SIMPLELOCK_LOCKED_P(&mtx->mtx_lock)) { SPINLOCK_BACKOFF(count); #ifdef LOCKDEBUG if (SPINLOCK_SPINOUT(spins)) MUTEX_ABORT(mtx, "spinout"); #endif /* LOCKDEBUG */ } } while (!__cpu_simple_lock_try(&mtx->mtx_lock));
なお、ループ中のBACKOFFだが、これはtry lockの間隔を開けるためのいわば「待ち」。
ロックを実現するための命令はキャッシュに関わらず大抵メモリへの直アクセスを行うためシステムパフォーマンスへの影響が大きい。
例えばx86の場合、__cpu_simple_lock_tryは以下の実装になっている。
static __inline int __cpu_simple_lock_try(__cpu_simple_lock_t *lockp) { uint8_t val; val = __SIMPLELOCK_LOCKED; __asm volatile ("xchgb %0,(%2)" : "=r" (val) :"0" (val), "r" (lockp)); __insn_barrier(); return val == __SIMPLELOCK_UNLOCKED; }
ここで、ロック取得に用いるxchgb命令(xchg命令)は以下のとおり自動バスロックを引き起こす。
7.1.2.1. 自動バスロック 次の操作時には、プロセッサは自動的に LOCK# 信号をアサートする。 ・ メモリを参照する XCHG 命令の実行中。 (IA-32 インテルアーキテクチャ ソフトウェアディベロッパーズマニュアル 下巻 7-4)
自動バスロックとは要するにシステムバスをロックすることで他のプロセッサがバスへの制御要求を出しても受付されないようにする仕組みである。
7.1.2.バスのロック IA-32 プロセッサは LOCK# の信号を備えている。 ある種の重要なメモリ操作の際、プロセッサは自動的にこの信号をアサートしてシステムバスをロックさせる。 この出力信号がアサートされている間は、他のプロセッサやバス・エージェントがバス制御要求を出しても無視される。 (IA-32 インテルアーキテクチャ ソフトウェアディベロッパーズマニュアル 下巻 7-4)
もし、待ちを入れずループ内でひたすらtry lockした場合、バスロックがかかりまくり、他のプロセッサのメモリアクセス要求が受け付けられず結果としてシステム全体のパフォーマンスが落ちてしまう。
そうならないようにするために、try lockするたびに待ちを入れるようにしている。
そしてロックが確保できたらreturnする。
続くコードはApaptive lockに関する処理になっている。
この箇所は優先度継承に関するturnstileというデータ構造に関して読んでいる最中でまだまとめきれていない。
よって、今日はここまでにします。