注:这里并没有详细分析到每个内部函数,如果要了解这些细节的话,可以看后面的
OTHER CFS CLASS API及
CFS主要的内部函数。
周期性调度器在调度框架上由 scheduler_tick完成:在每一个cpu的时钟周期都触发一次该函数调用,它更新运行队列的时钟及load,然后调用当前进程的调度器类的周期调度函数。
update_rq_clock(rq); /* 更新运行队列的时钟rq->clock*/
update_cpu_load_active(rq); /* 更新运行队列load,本质是将数组中先前存储的负荷值向后移动一个位置,将当前负荷记入数组的第一个位置 */
curr->sched_class->task_tick(rq, curr, 0);
我们再来看一下CFS调度器类的周期调度函数( task_tick_fair),该函数是对 entity_tick的组调度的一个封装(因为只有真正的task的se才会在cpu上运行,而group的se是不会在cpu上运行的,所以这里参数的se就是一个task,然后对从它到它所在的组的根的所有group se调用entity_tick),所以我们直接来看一下 entity_tick函数:
update_curr(cfs_rq); //完成当前se(这个se可以是task或group)的执行时间,虚拟时间,cfs_rq的相应时间及统计的更新:curr->exec_start=now;curr->sum_exec_runtime+=delta_exec;及curr->vruntime+= calc_delta_fair();更新cfs_rq的min_vruntime.其中delta_exec表示实质执行的时间,而calc_delta_fair则计算delta_exec相对应的虚拟时间,即delta_fair={if se->load.weight != NICE_0_LOAD return delta_exec; else return delta_exec *NICE_0_LOAD/se.load->weight},可以看到当该se是0优先级的话,那么它的虚拟时间等于实质执行的物理时间,否则如果该se的load越大那么它的虚拟时间就越小,这也是为什么load越大的进程能够执行的时间越多——它的虚拟时间增长的越慢
update_entity_shares_tick(cfs_rq); //这个函数只有对于SMP才有作用,所以我们这里先不分析
#ifdef CONFIG_SCHED_HRTICK //HRTICK情况,不知道干嘛
if (queued) {
resched_task(rq_of(cfs_rq)->curr);
return;
}
if (!sched_feat(DOUBLE_TICK) &&
hrtimer_active(&rq_of(cfs_rq)->hrtick_timer))
return;
#endif
if (cfs_rq->nr_running > 1 || !sched_feat(WAKEUP_PREEMPT)) //可运行的大于1,或者不支持WAKEUP_PREEMPT,后面这个条件在后来的版本已经被去掉了
check_preempt_tick(cfs_rq, curr); //该函数用于检查当前进程是否运行了足够长的时间(实质运行时间大于sched_slice理想运行时间或者当前运行队列最左边的se到现在的等待的时间已经超过curr理想应该运行的时间),如果出现了上面的情况则调用resched_task将该进程设置为TIF_NEED_RESCHED,即可调度的;如果运行时间小于sysctl_sched_min_granularity(最小执行时间)或者内核不允许WAKEUP_PREEMPT(内核允许的调度特性在/proc/sys/kernel/sched_features定义WAKEUP_PREEMPT使用第3 bit),则直接返回。注意这里的每个判断条件的顺序是非常重要的(前面的更重要):1.如果进程已经运行超过它的理想运行时间那么它将无条件被resched_task;2.如果不支持WAKEUP_PREEMPT那么直接返回不对当前的进行resched_task;3.如果运行的时间小于sysctl_sched_min_granularity那么也直接返回;4.最左边的等待时间大于理想运行时间时resched_task(curr),即要4满足的话,必须当前的执行时间小于它理想运行时间,并且支持WAKEUP_PREEMPT及运行了至少sysctl_sched_min_granularity,最后已经等待的时间大于理想运行时间
可以看到每个时钟都对会当前运行的se进行实质执行时间及虚拟执行时间进行更新,最后检查该进程是否运行的足够长的时间,如果是的话则将它置为TIF_NEED_RESCHED供主调度在适当的时机进行切换。可见周期性调度器还是比较简单,没有涉及到真正的调度任务,只是设置一个重调度请求的标志而已,下面看看主调器,也就是它来响应周期性调度器的 TIF_NEED_RESCHED。
图 CFS与周期调度器