プリエンプション
CONFIG_PREEMPTを定義しておくと、プロセスコンテキストでカーネル内のコードを実行している時でも他のプロセスに切りかわることができる。
このため、プロセスがカーネル空間のコード実行中にCPU時間を使いきった場合でも、速やかにプロセスが切り替えられる。プリエンプション機能がないと、カーネル空間のコードの実行が終了するまでプロセスの切り替えが遅れる。
通常Unixはユーザモードのみプリエンプティブでカーネルモードではノンプリエンプティブだが、CONFIG_PREEMPTを有効にするとカーネルモードでもプリエンプティブになる。
CONFIG_PREEMPTがなければLinux 2.4と同様にカーネルモードでは明示的に自分からSleepしてCPUを手放さない限り切り替わらない。
preempt_schedule_irq()
割り込みコンテキストからプリエンプトする場合こちらを使用する。(割り込み/例外からカーネルモードにもどる際、呼ばれる)
処理内容はpreempt_schedule()とほぼ同じ。schedule()を呼ぶ際は、割り込みを禁止にしている。割り込みが発生して再帰的に呼び出されるのを防ぐため。
(*1)
[関連関数]
preempt_disable()
preempt_enable()
このため、プロセスがカーネル空間のコード実行中にCPU時間を使いきった場合でも、速やかにプロセスが切り替えられる。プリエンプション機能がないと、カーネル空間のコードの実行が終了するまでプロセスの切り替えが遅れる。
通常Unixはユーザモードのみプリエンプティブでカーネルモードではノンプリエンプティブだが、CONFIG_PREEMPTを有効にするとカーネルモードでもプリエンプティブになる。
CONFIG_PREEMPTがなければLinux 2.4と同様にカーネルモードでは明示的に自分からSleepしてCPUを手放さない限り切り替わらない。
1.プリエンプションの実装
1.1 プリエンプションの実行
preempt_schedule()プリエンプションを行なう。
preempt_enable()から呼ばれる
preempt_enable()から呼ばれる
preempt_schedule()
{
/* Preemption可能かチェック */
if (unlikely(ti->preempt_count || irqs_disabled()))
return;
need_resched:
add_preempt_count(PREEMPT_ACTIVE);
/* preempt_countにPREEMPT_ACTIVEフラグを立てる */
schedule();
sub_preempt_count(PREEMPT_ACTIVE);
/* プリエンプション前に立てられたフラグを落す */
barrier();
if (unlikely(test_thread_flag(TIF_NEED_RESCHED)))
goto need_resched;
}
preempt_schedule_irq()
割り込みコンテキストからプリエンプトする場合こちらを使用する。(割り込み/例外からカーネルモードにもどる際、呼ばれる)
処理内容はpreempt_schedule()とほぼ同じ。schedule()を呼ぶ際は、割り込みを禁止にしている。割り込みが発生して再帰的に呼び出されるのを防ぐため。
1.2 プリエンプションの発生契機
以下の契機でプリエンプト処理(preempt_schedule(),preempt_schedule_irq())を呼び出す。- preempt_enable()呼び出し時
- 例外/割り込み処理からカーネルモードに戻る時(*1)
(ユーザーモードに戻る場合はプリエンプションではなく通常のプロセス切り替え)
(*1)
entry.S::resume_kernel: - 割り込みや例外からカーネルモードに戻る時に呼ばれる
preempt_countが0でなければPreemptDisable中なので、そのまま割り込み/例外から復帰(復帰先はカーネルモード)。TIF_NEED_RESCHEDが立っていたらpreempt_schedule_irq()を呼び出してプリエンプトする。
preempt_countが0でなければPreemptDisable中なので、そのまま割り込み/例外から復帰(復帰先はカーネルモード)。TIF_NEED_RESCHEDが立っていたらpreempt_schedule_irq()を呼び出してプリエンプトする。
CONFIG_PREEMPTが未定義の時はresume_kernelはiretで呼び出し元に戻るだけとなる。このため、カーネル空間のコード実行中にプロセスの切り替えは発生しない。
1.3 プリエンプション状態を示すフラグ
thread_info.preempt_countプリエンプションの禁止状態などが保存されている。プリエンプションの禁止状態以外にも割り込みの処理状態もここに保存される。
preempt_count == 0だとプリエンプション可能
PREEMPT_MASK: 0x000000ff
SOFTIRQ_MASK: 0x0000ff00
HARDIRQ_MASK: 0x0fff0000
PREEMPT_ACTIVE: 0x10000000
SOFTIRQ_MASK: 0x0000ff00
HARDIRQ_MASK: 0x0fff0000
PREEMPT_ACTIVE: 0x10000000
PREEMPT_MASK
プリエンプション禁止状態にされていることを意味する。preempt_disable()でインクリメントされる。
SOFTIRQ_MASK
S/W割り込み処理中であることを示す。
SOFTIRQ_MASK
S/W割り込み処理中であることを示す。
__do_softirq()でS/W割り込みの処理を開始するときにlocal_bh_disable()でSOFTIRQ_MASKの部分が加算されている。
HARDIRQ_MASK
H/W割り込み処理中であることを示す。
do_IRQ()でH/W割り込みの処理を開始するときにirq_enter()でHARDIRQ_MASKの部分が加算されるている。
do_IRQ()でH/W割り込みの処理を開始するときにirq_enter()でHARDIRQ_MASKの部分が加算されるている。
PREEMPT_ACTIVE
プリエンプションでプロセスが切り替わったことを意味する。schedule()で参照される。preempt_schedule()でプリエンプションする前に立てている。
プリエンプションでプロセスが切り替わったことを意味する。schedule()で参照される。preempt_schedule()でプリエンプションする前に立てている。
in_interrupt()で割り込み処理中かチェックするときもcurrent_thread_info()->preempt_countの(HARDIRQ_MASK
| SOFTIRQ_MASK)部分をチェックしている。
[関連関数]
preempt_disable()
プリエンプションを禁止にする。
inc_preempt_count()でthread_info.preempt_countをインクリメントしている。
preempt_enable()
プリエンプションを許可する。
プリエンプションが必要であればついでにプリエンプションも行なわれる。
プリエンプションが必要であればついでにプリエンプションも行なわれる。
dec_preempt_count()でpreemption_countをデクリメント後、preempt_check_resched()を呼んでプリエンプションが必要なら(TIF_NEED_RESCHED立っていたら)プリエンプションを実行している。