カーネルプリエンプション
カーネルプリエンプションはカーネルパス下で、プロセスの切り替えを行う事をいいます。ただし、IO/セマフォ待ちとかで、プロセス切り替とは違います。例えば、カーネルはインターバルタイマーで、タイムシェアリングでプロセスの切り替えを行っています。タイマー割り込みは、プロセスがユーザモード/カーネルモードに関係なく発生します。プリエンプティブでないカーネルなら、タイマー割り込みがカーネルモードで発生したら、プロセスの切り替えは行いません。唯一ユーザモードで実行している時のみ、プロセス切り替えを行うことになります。プリエンプティブのカーネルなら、ユーザモード/カーネルモード関係なくプロセス切り替えを行うことが可能となります。(たぶん。)
プリエンプティブは、プロセス毎のthread_info構造体のpreempt_countで設定し、preempt_countが0の時、プリエンプティブされます。
割り込みが発生すると、アセンブラが書かれたラベルのinterruptへジャンプします。そこでは割り込み番号のチェック等を行った後、common_interruptへとジャンプします。
common_interruptで実際の割り込み処理は、do_IRQをコールすることで行っていて、その処理が終了するとret_from_intrへジャンプします。ここでカーネルプリエンプションのチェックが行われます。
preempt_countが0でなければ、プリエンプションは禁止です。restore_allで、プロセスの割り込まれたカーネルパスに復帰します。preempt_countが0で、thread_info構造体のflagsがNEED_RESCHEDなら、preempt_schedule_irqをコールすることで他のプロセスにCPUを手放します。
なお、割り込まれたプロセスの割り込みが、禁止されていたなら(コメントにあるように例外処理)、割り込まれたプロセスに復帰するようです。
プリエンプティブは、プロセス毎のthread_info構造体のpreempt_countで設定し、preempt_countが0の時、プリエンプティブされます。
割り込みが発生すると、アセンブラが書かれたラベルのinterruptへジャンプします。そこでは割り込み番号のチェック等を行った後、common_interruptへとジャンプします。
common_interruptで実際の割り込み処理は、do_IRQをコールすることで行っていて、その処理が終了するとret_from_intrへジャンプします。ここでカーネルプリエンプションのチェックが行われます。
common_interrupt:
addl $-0x80,(%esp) /* Adjust vector into the [-256,-1] range */
SAVE_ALL
TRACE_IRQS_OFF
movl %esp,%eax
call do_IRQ
jmp ret_from_intr
ENDPROC(common_interrupt)
ret_from_intrで、割り込まれたプロセスのコンテキストはスタック上にあります。スタックから、割り込まれたプロセスのCSセグメントをeaxレジスターに取得します。CSセグメントの特権情報をチェックすることで、ユーザモードで割り込まれたか、カーネルモードで割り込まれたかを判断します。カーネルモードで割り込まれたなら、resume_kernelへジャンプします。ユーザモードならresume_userspaceからrestore_allへとユーザモードに復帰することになります。
ret_from_intr:
GET_THREAD_INFO(%ebp)
check_userspace:
movl PT_EFLAGS(%esp), %eax # mix EFLAGS and CS
movb PT_CS(%esp), %al
andl $(X86_EFLAGS_VM | SEGMENT_RPL_MASK), %eax
cmpl $USER_RPL, %eax
jb resume_kernel # not returning to v8086 or userspace
ENTRY(resume_userspace)
LOCKDEP_SYS_EXIT
DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
# setting need_resched or sigpending
# between sampling and the iret
TRACE_IRQS_OFF
movl TI_flags(%ebp), %ecx
andl $_TIF_WORK_MASK, %ecx # is there any work to be done on
# int/exception return?
jne work_pending
jmp restore_all
resume_kernelでカーネルモードで割り込まれた時の処理で、プリエンプションにかかる処理となります。まず割り込まれたプロセスのthread_info構造体のメンバーpreempt_countをチェックします。thread_info構造体は、プロセス毎のカーネルスタックの延長上に有していて、ebpから取得できます。preempt_countが0でなければ、プリエンプションは禁止です。restore_allで、プロセスの割り込まれたカーネルパスに復帰します。preempt_countが0で、thread_info構造体のflagsがNEED_RESCHEDなら、preempt_schedule_irqをコールすることで他のプロセスにCPUを手放します。
なお、割り込まれたプロセスの割り込みが、禁止されていたなら(コメントにあるように例外処理)、割り込まれたプロセスに復帰するようです。
ENTRY(resume_kernel)
DISABLE_INTERRUPTS(CLBR_ANY)
cmpl $0,TI_preempt_count(%ebp) # non-zero preempt_count ?
jnz restore_all
need_resched:
movl TI_flags(%ebp), %ecx # need_resched set ?
testb $_TIF_NEED_RESCHED, %cl
jz restore_all
testl $X86_EFLAGS_IF,PT_EFLAGS(%esp) # interrupts off (exception path) ?
jz restore_all
call preempt_schedule_irq
jmp need_resched
END(resume_kernel)





