スピンロックその6
月イチ更新と化したこのスピンロック連載、おそらく固定読者はいないと思いますが細々と続けてみます。
前回の最後に「次は(c)のケースだ」などと予告しましたが、(b)のケースとあまり変わり映えしないので思い切って省略します。ふりかえれば、その2でロック取得を追い始めて以来早くも3か月経ってしまいました。いい加減にロックを解放しなきゃどんなシステムも止まるわな、てなわけではありませんが話題を spin_unlock() に移します。
その2で確認したタスク・スケジューラの task_rq_lock() に対応する解放処理は、その名も task_rq_unlock() です。
kernel/sched.c
static inline void task_rq_unlock(runqueue_t *rq, unsigned long *flags)
__releases(rq->lock)
{
spin_unlock_irqrestore(&rq->lock, *flags);
}
関数のインタフェース宣言行と処理本体との間に見慣れない行がありますね。これはロックの取得・解放の数をカウントするツールのためのマクロだそうです([linux-pm] __releases and __acquires macros in compiler.h)。定義からしても通常のビルドでは消えますね。
#ifdef __CHECKER__
# define __acquires(x) __attribute__((context(0,1)))
# define __releases(x) __attribute__((context(1,0)))
#else
# define __acquires(x)
# define __releases(x)
#endif
さて task_rq_unlock() の実体は spin_unlock_irqrestore() であることが分りましたが、これもまたマクロです。
include/linux/spinlock.h
#define spin_unlock_irqrestore(lock, flags) \
_spin_unlock_irqrestore(lock, flags)
この _spin_unlock_irqrestore() の定義は Uni-Processor と SMP とで異なります。
まずは簡単そうな UP から見ていきましょう。
include/linux/spinlock_api_up.h
#define _spin_unlock_irqrestore(lock, flags) __UNLOCK_IRQRESTORE(lock, flags)
#define __UNLOCK_IRQRESTORE(lock, flags) \
do { local_irq_restore(flags); __UNLOCK(lock); } while (0)
この local_irq_restore() は割り込みの回復ですが、これは後回しにして、実体と思われる__UNLOCK() から先に確認しましょう。
include/linux/spinlock_api_up.h
#define __UNLOCK(lock) \
do { preempt_enable(); __release(lock); (void)(lock); } while (0)
この preempt_enable() は include/linux/preempt.h で定義されていますが、これはマクロが続く分かりにくい構造なので、諦めて gcc に展開させることにします。
次のような C 言語ソースファイル a.c を準備。
#include <linux/preempt.h>
preempt_enable();
カーネルのソースツリーの include ディレクトリを -I で指定した cc -E でプリプロセッサに掛けます。
$ cc -E -I ./linux-2.6.16.18/include a.c
[関係ないところは省略]
do { } while (0);
$
空っぽになるんですね。
次に CONFIG_PREEMPT を define してコンパイルすると、
$ cc -E -I ./linux-2.6.16.18/include -DCONFIG_PREEMPT a.c
[関係ないところは省略]
do { do { barrier(); do { (current_thread_info()->preempt_count) -= (1); } while (0); } while (0); barrier(); do { if (unlikely(test_thread_flag(TIF_NEED_RESCHED))) preempt_schedule(); } while (0); } while (0);
$
あまりに読みにくい。。。 indent コマンドで整形してみますか。
$ cc -E -I ./linux-2.6.16.18/include -DCONFIG_PREEMPT a.c | indent
[関係ないところは省略]
do
{
do
{
barrier ();
do
{
(current_thread_info ()->preempt_count) -= (1);
}
while (0);
}
while (0);
barrier ();
do
{
if (unlikely (test_thread_flag (TIF_NEED_RESCHED)))
preempt_schedule ();
}
while (0);
}
while (0);
$
余計な do {} while() を削ると、つまりこーゆーことですね。
barrier ();
(current_thread_info ()->preempt_count) -= (1);
barrier ();
if (unlikely (test_thread_flag (TIF_NEED_RESCHED)))
preempt_schedule ();
タスク毎の preempt_count というカウンタから1を引いています。その前後にはメモリバリアですね。最後は、もしも休止すべきタスクならば、ここで休止させると。
なんだかタスク・スケジューラの方に脱線しそうなので、ここで一旦休憩です。
切りがいいので今回はここまでとさせてください。



最近のコメント