1. CFS如何处理周期性调度器
周期性调度器的工作由scheduler_tick函数完成(定义在kernel/sched/core.c, line 2910), 在scheduler_tick中周期性调度器通过调用curr进程所属调度器类sched_class的task_tick函数完成周期性调度的工作
周期调度的工作形式上sched_class调度器类的task_tick函数完成, CFS则对应task_tick_fair函数, 但实际上工作交给entity_tick完成.
2 CFS的周期性调度
2.1 task_tick_fair与周期性调度
CFS完全公平调度器类通过task_tick_fair函数完成周期性调度的工作, 该函数定义在kernel/sched/fair.c?v=4.6#L8119
/*
* scheduler tick hitting a task of our scheduling class:
*/
static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
{
struct cfs_rq *cfs_rq;
/* 获取到当前进程curr所在的调度实体 */
struct sched_entity *se = &curr->se;
/* for_each_sched_entity
* 在不支持组调度条件下, 只循环一次
* 在组调度的条件下, 调度实体存在层次关系,
* 更新子调度实体的同时必须更新父调度实体 */
for_each_sched_entity(se)
{
/* 获取当当前运行的进程所在的CFS就绪队列 */
cfs_rq = cfs_rq_of(se);
/* 完成周期性调度 */
entity_tick(cfs_rq, se, queued);
}
if (static_branch_unlikely(&sched_numa_balancing))
task_tick_numa(rq, curr);
}
我们可以看到, CFS周期性调度的功能实际上是委托给entity_tick函数来完成的
2.2 entity_tick函数
在task_tick_fair中, 内核将CFS周期性调度的实际工作交给了entity_tick来完成, 该函数定义在kernel/sched/fair.c, line 3470中, 如下所示
static void
entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)
{
/*
* Update run-time statistics of the 'current'.
*/
update_curr(cfs_rq);
/*
* Ensure that runnable average is periodically updated.
*/
update_load_avg(curr, 1);
update_cfs_shares(cfs_rq);
#ifdef CONFIG_SCHED_HRTICK
/*
* queued ticks are scheduled to match the slice, so don't bother
* validating it and just reschedule.
*/
if (queued) {
resched_curr(rq_of(cfs_rq));
return;
}
/*
* don't let the period tick interfere with the hrtick preemption
*/
if (!sched_feat(DOUBLE_TICK) &&
hrtimer_active(&rq_of(cfs_rq)->hrtick_timer))
return;
#endif
if (cfs_rq->nr_running > 1)
check_preempt_tick(cfs_rq, curr);
}
首先, 一如既往的使用update_curr来更新统计量
接下来是hrtimer的更新, 这些由内核通过参数CONFIG_SCHED_HRTICK开启
然后如果cfs就绪队列中进程数目nr_running少于两个(< 2)则实际上无事可做. 因为如果某个进程应该被抢占, 那么至少需要有另一个进程能够抢占它(即cfs_rq->nr_running > 1)
如果进程的数目不少于两个, 则由check_preempt_tick作出决策
if (cfs_rq->nr_running > 1)
check_preempt_tick(cfs_rq, curr);
2.3 check_preempt_tick函数
在entity_tick中, 如果cfs的就绪队列中进程数目不少于2, 说明至少需要有另外一个进程能够抢占当前进程, 此时内核交给check_preempt_tick作出决策. check_preempt_tick函数定义在kernel/sched/fair.c, line 3308
/*
* Preempt the current task with a newly woken task if needed:
*/
static void
check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
unsigned long ideal_runtime, delta_exec;
struct sched_entity *se;
s64 delta;
/* 计算curr的理论上应该运行的时间 */
ideal_runtime = sched_slice(cfs_rq, curr);
/* 计算curr的实际运行时间
* sum_exec_runtime: 进程执行的总时间
* prev_sum_exec_runtime:进程在切换进CPU时的sum_exec_runtime值 */
delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;
/* 如果实际运行时间比理论上应该运行的时间长
* 说明curr进程已经运行了足够长的时间
* 应该调度新的进程抢占CPU了 */
if (delta_exec > ideal_runtime)
{
resched_curr(rq_of(cfs_rq));
/*
* The current task ran long enough, ensure it doesn't get
* re-elected due to buddy favours.
*/
clear_buddies(cfs_rq, curr);
return;
}
/*
* Ensure that a task that missed wakeup preemption by a
* narrow margin doesn't have to wait for a full slice.
* This also mitigates buddy induced latencies under load.
*/
if (delta_exec < sysctl_sched_min_granularity)
return;
se = __p