カーネルプリエンプション
Rev.2を表示中。最新版はこちら。
カーネルプリエンプションはカーネルパス下で、プロセスの切り替えを行う事をいいます。ただし、IO/セマフォ待ちとかで、プロセス切り替とは違います。例えば、カーネルはインターバルタイマーで、タイムシェアリングでプロセスの切り替えを行っています。タイマー割り込みは、プロセスがユーザモード/カーネルモードに関係なく発生します。プリエンプティブでないカーネルなら、タイマー割り込みがカーネルモードで発生したら、プロセスの切り替えは行いません。唯一ユーザモードで実行している時のみ、プロセス切り替えを行うことになります。プリエンプティブのカーネルなら、ユーザモード/カーネルモード関係なくプロセス切り替えを行うことが可能となります。プリエンプティブは、プロセス毎の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_allresume_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)