スピンロック
Rev.1を表示中。最新版はこちら。
スピンロックの取得はspin_lockマクロで定義されています。preempt_disable()でカレントプロセスのpreempt_countをインクリメントします。preempt_count=0の時プリエンプトされます。_raw_spin_trylock()でロックが取得しにいきます。取得できなければ、__preempt_spin_loc()でビジーウエイトとよばれるループします。#define spin_lock(lock) \ do { \ preempt_disable(); \ if (unlikely(!_raw_spin_trylock(lock))) \ __preempt_spin_lock(lock); \ } while (0)_raw_spin_trylock()は、lock->lockとoldval=0(oldvalはeax,ebx,ecx,edxの中のレジスタで展開される。)を、xchgbで交換します。もし、movコマンドでこの操作をおこなうと、変換途中で他のCPUで、そのlock->lockが更新されることがありますが、xchgbはアトミックで行われ、本変換中は、他のCPUからlock->lockをアクセスできません。
その結果oldval(lock->lockの中身)が0でないなら、ロックが取得できたことになり、同時にlock->lockには0が設定され、ロック状態を設定したことになります。ロックできないという事は、lock->lock=0で、oldval=0と変換しても、ロック状態のlock->lock=0のままです。
static inline int _raw_spin_trylock(spinlock_t *lock) { char oldval; __asm__ __volatile__( "xchgb %b0,%1" :"=q" (oldval), "=m" (lock->lock) :"0" (0) : "memory"); return oldval > 0; }__preempt_spin_lock()がビジーウエイトと呼ばれる処理になり、プリエンプトが可能かどうかによってその処理は、異なっています。
プリエンプトが不可なら、_raw_spin_lock()が、可能ならpreempt_enable()で再スケジュールしてロックを取得しに行きます。プリエンプトが不可と言うことは、他のCPU下のプロセスがロックを取得していると言う事(たぶん)。プリエンプトが可と言うのは、このCPU下の他のプロセスがロックを取得している場合もある。ということ(たぶん)。
void __preempt_spin_lock(spinlock_t *lock) { if (preempt_count() > 1) { _raw_spin_lock(lock); return; } do { preempt_enable(); while (spin_is_locked(lock)) cpu_relax(); preempt_disable(); } while (!_raw_spin_trylock(lock)); } #define spin_is_locked(x) (*(volatile signed char *)(&(x)->lock) <= 0)_raw_spin_lock()は、アセンブラのlock命令を介して、lock->lockをデクリメントして、それが0になるまでループし続けます。
static inline void _raw_spin_lock(spinlock_t *lock) { __asm__ __volatile__( spin_lock_string :"=m" (lock->lock) : : "memory"); } #define spin_lock_string \ "\n1:\t" \ "lock ; decb %0\n\t" \ <- lock->lockのデクリメント "js 2f\n" \ <- ロックされていたら2:へジャンプ LOCK_SECTION_START("") \ <- ロックできたらこの処理がされる。 "2:\t" \ "rep;nop\n\t" \ "cmpb $0,%0\n\t" \ <- lock->lockが0かチェック "jle 2b\n\t" \ <- 0より小さいと2:へジャンプ(ビジーウエイト) "jmp 1b\n" \ <- 1:へジャンプして、再度ロック取得プリエンプトが可の場合、このCPU下のプロセスがロックを取得している可能性があるため、preempt_enable()で再シュケジュールを行います。(ここでのpreempt_enable()はプリエンプトを可能にするためでなく、再シュケジュールのためにコールします。)そして、ロックを取得できるまでループします(取得はしません。チェックだけです。)
取得可能なら、preempt_disable()で、先のpreempt_enable()でインクリメントした、preempt_countを元に戻します。そして_raw_spin_trylock()でロック取得を試みます。ロック取得できる状態になってから、実際ロック取得するまでに、他のプロセスがロックを取得している場合があるからです。