一、触发抢占的时机:
- 周期性时钟中断:检查时间片是否用完,如果用完触发抢占
scheduler_tick
curr->sched_class->task_tick(rq, curr, 0);
- 唤醒进程的时候,如果优先级高于cpu上的当前进程,就会发出抢占:try_to_wake_up(),最终会调用check_preempt_curr()检查是否触发抢占;
- 新进程创建的时候,如果新进程的优先级高于CPU上的当前进程,会触发抢占:sched_fork()->task_fork方法触发抢占;
- 修改nice值的时候:如果进程修改nice值导致优先级高于CPU上的当前进程,就会触发抢占:set_user_nice();
- 进行负载均衡的时候:在SMP系统上,进程调度器尽量使各个CPU之间的的负载保持均衡,而负载均衡操作可能会需要触发抢占;不同调度类有不同的负载均衡算法,涉及的核心代码也不一样,比如CFS类在load_balance()中触发抢占:
Load_balance->resched_cpu
二、执行抢占的时机:
2.1用户态抢占时机
(1)从系统调用返回用户态时
源文件:arch/x86/kernel/entry_64.S
sysret_careful:
bt $TIF_NEED_RESCHED,%edx
jnc sysret_signal
TRACE_IRQS_ON
ENABLE_INTERRUPTS(CLBR_NONE)
pushq_cfi %rdi
SCHEDULE_USER//会调用schedule
popq_cfi %rdi
jmp sysret_check
(2)从中断(异常)返回用户态时:
源文件:arch/x86/kernel/entry_64.S
retint_careful:
CFI_RESTORE_STATE
bt $TIF_NEED_RESCHED,%edx
jnc retint_signal
TRACE_IRQS_ON
ENABLE_INTERRUPTS(CLBR_NONE)
pushq_cfi %rdi
SCHEDULE_USER//会调用schedule
popq_cfi %rdi
GET_THREAD_INFO(%rcx)
DISABLE_INTERRUPTS(CLBR_NONE)
TRACE_IRQS_OFF
jmp retint_check
2.2 内核态抢占时机:Linux在2.6版本之后就支持内核抢占了,但是请注意,具体取决于内核编译时的选项:
CONFIG_PREEMPT_NONE=y
不允许内核抢占。这是SLES的默认选项。
CONFIG_PREEMPT_VOLUNTARY=y
在一些耗时较长的内核代码中主动调用cond_resched()让出CPU。这是RHEL的默认选项。
CONFIG_PREEMPT=y
允许完全内核抢占。
在 CONFIG_PREEMPT=y 的前提下,内核态抢占的时机是:
(1)中断处理程序返回内核空间之前会检查TIF_NEED_RESCHED标志,如果置位则调用preempt_schedule_irq()执行抢占。preempt_schedule_irq()是对schedule()的包装
ENTRY(retint_kernel)
cmpl $0,PER_CPU_VAR(__preempt_count)
jnz retint_restore_args
bt $9,EFLAGS-ARGOFFSET(%rsp) /* interrupts off? */
jnc retint_restore_args
call preempt_schedule_irq//产生调度时机
jmp exit_intr
(2)当内核从non-preemptible(禁止抢占)状态变成preemptible(允许抢占)的时候;
在preempt_enable()中,会最终调用 preempt_schedule 来执行抢占。preempt_schedule()是对schedule()的包装。
三、参考链接