プリエンプト
Rev.1を表示中。最新版はこちら。
プロセスは自らスリープしたりロック待ち等でschedule()をコールする事で、他のプロセスへ切り替えます。プリエンプトとは、カーネルモード下で非同期にプロセス切替えの事を言います。これは割り込みに起因する物で、例えば、タイマ割り込み発生毎に、カレントプロセスの実行時間を超えていないかチェックし、使い切っているならpreempt_schedule()をコールしプロセス切り替えを行います。プリエンプトは、static変数を参照/更新する掛かる関数内の処理下で問題となります。その実装途中でプロセスが切り替わり、他のプロセスが同じstatic変数を更新する場合です。gccはstatic変数をレジスタにセットし、以降のstatic変数での処理は、このレジスタを参照するコード展開いたします。gccは関数内での記述ロジックに基づいて最適化を行い、この変数が他のプロセス等により変更される事など知る由もありません。この回避としてmemory_barrier()を設定することで、以降の変数(memory)の参照は、更新されているかもしれない。と言うことで、以前の設定されたレジスタの値は廃棄し、改めて変数から参照/更新となります。
asmlinkage void __sched schedule(void) { struct task_struct *tsk = current; sched_submit_work(tsk); __schedule(); }preempt_schedule()は、thread_info->preempt_countが0でなく、割込みが禁止されているならタスク切り替えを致しません。通常割込みが禁止される事はなく、されているのは動作してるプロセスの実行権を奪われたくない。と言う意味合いがあります。(通常割り込みを禁止する事はありませんが、デバイス等ハードに掛かる初期化時は割り込み禁止状態で行われたりするようです。それならpreempt_schedule()がコールされる事はないのでは。と思いますが、自プロセスが各種関数を呼ぶ中で、その関数からコールされる場合があるからです。)
プリエンプトを禁止するために割り込みを禁止するのは良くありません。割り込みハンドラそのものが動作しません。従って割り込みを取り逃がすことになります。そこで、プリエンプトをthread_info->preempt_countでコントロールしています。thread_info->preempt_count=0ならプリエンプト可能です。 asmlinkage void __sched notrace preempt_schedule(void)
{ struct thread_info *ti = current_thread_info(); if (likely(ti->preempt_count || irqs_disabled())) return; do { add_preempt_count_notrace(PREEMPT_ACTIVE); __schedule(); sub_preempt_count_notrace(PREEMPT_ACTIVE); barrier(); } while (need_resched()); }preempt_disable()はinc_preempt_count()の後に、preempt_enable_no_resched()はdec_preempt_count()の前にbarrier()しています。これらのマクロは関数内の処理の中で展開され、inc_preempt_count()が完了するまでに他のプロセスに切り替わっているかもしれません。dec_preempt_count()はその瞬間他のプロセスに切り替わっているかもしれません。
なお、preempt_enable_no_resched()はpreempt_disable()でプリエンプトを不可とした後、許可するためにコールします。
#define preempt_count() (current_thread_info()->preempt_count) # define add_preempt_count(val) do { preempt_count() += (val); } while (0) # define sub_preempt_count(val) do { preempt_count() -= (val); } while (0) #define inc_preempt_count() add_preempt_count(1) #define dec_preempt_count() sub_preempt_count(1) #define preempt_disable() \ do { \ inc_preempt_count(); \ barrier(); \ } while (0) #define preempt_enable_no_resched() \ do { \ barrier(); \ dec_preempt_count(); \ } while (0)dec_preempt_count()は以下のように展開されます。拡張インラインの第3引数は破壊リソースで、memoryが破壊されるということで、掛かるレジスタで参照している変数は無効ということです。
do { __asm__ __volatile__("": : :"memory"); do { (current_thread_info()->preempt_count) -= (1); } while (0); } while (0);