スピンロック
スピンロックの取得はspin_lockマクロで定義されています。preempt_disable()でカレントプロセスのpreempt_countをインクリメントします。preempt_count=0の時プリエンプトされます。_raw_spin_trylock()でロックが取得しにいきます。取得できなければ、__preempt_spin_loc()でビジーウエイトとよばれるループします。
その結果oldval(lock->lockの中身)が0でないなら、ロックが取得できたことになり、同時にlock->lockには0が設定され、ロック状態を設定したことになります。ロックできないという事は、lock->lock=0で、oldval=0と変換しても、ロック状態のlock->lock=0のままです。
プリエンプトが不可なら、_raw_spin_lock()が、可能ならpreempt_enable()で再スケジュールしてロックを取得しに行きます。プリエンプトが不可と言うことは、他のCPU下のプロセスがロックを取得していると言う事(たぶん)。プリエンプトが可と言うのは、このCPU下の他のプロセスがロックを取得している場合もある。ということ(たぶん)。
取得可能なら、preempt_disable()で、先のpreempt_enable()でインクリメントした、preempt_countを元に戻します。そして_raw_spin_trylock()でロック取得を試みます。ロック取得できる状態になってから、実際ロック取得するまでに、他のプロセスがロックを取得している場合があるからです。
#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()でロック取得を試みます。ロック取得できる状態になってから、実際ロック取得するまでに、他のプロセスがロックを取得している場合があるからです。




