Linux系统下,进程调度有主动和被动之分,最终是调用schedule()函数。主动是指进程需要等待资源而主动让出CPU,被动就是抢占(PREEMPT)。
主动schedule由业务代码自行决定,运行在Linux抢占式内核上要确保进程的运行时间就需要关注抢占。
抢占的过程分为两步:
设置调度标记
为CPU上正在运行的进程thread_info结构体里的flags成员设置TIF_NEED_RESCHED
执行抢占
kernel判断当前进程标记TIF_NEED_RESCHED并调用schedule()函数切换上下文
那么,什么时候设置TIF_NEED_RESCHED呢 ?
1.timer时钟中断
时钟中断处理函数会调用scheduler_tick
从而调用当前进程调度类里的task_tick(rq, curr, 0)函数
最后由check_preempt_tick检测是否需要重新调度,如果需要就会设置调度标记。
2. 创建新进程的时候
do_fork函数会调用task_fork设置TIF_NEED_RESCHED
对于实时调度类里面并没有设置task_fork回调函数,也就是说一个实时进程fork一个新进程的话不会设置调度标记。
3.唤醒进程的时候
ttwu_do_wakeup 会调用 check_preempt_curr设置调度标志,设置的条件是
当前为CFS进程或者当前为RT进程但是新进程的优先级更高。
4.修改进程nice值的时候
set_user_nice函数设置TIF_NEED_RESCHED的条件为正在运行的进程降低优先级或者其他进程增加优先级
5.负载均衡的时候
CFS调度器3.18版本的kernel已经用smp_send_reschedule()
替代 resched_cpu()了
RT调度器如果当前cpu上多于一个RT进程,就调用push_rt_task推给别的cpu,并且设置TIF_NEED_RESCHED
6.用户空间调用sched_setscheduler()设置调度policy
用户态调用sched_setscheduler()设置调度policy的时候如果sched_class从低优先级switch到高优先级则会调用resched_curr()函数设置TIF_NEED_RESCHED
抢占的时机:
设置TIF_NEED_RESCHED后就需要等待kernel在一定的时候去检测该flags从而调用schedule()函数切换上下文,kernel空间是可以关抢占的,user空间是无法关抢占的。抢占可分为内核态抢占和用户态抢占
1.用户态抢占
arch/arm64/kernel/entry.S文件里
在ret_to_user入口处最后会检查标志位再调用schedule
2.内核态抢占
在中断返回内核态的入口el1_irq最后会调用
preempt_schedule_irq进而再调schedule
当kernel调用preempt_enable函数的时候会执行preempt_schedule_common该函数再调用schedule