3.7.5.6. task_tick_fair周期性调度器

主调度器schedule在选择最优的进程抢占处理器的时候,通过__schedule调用全局的pick_next_task函数,在全局的pick_next_task函数中,按照stop > dl > rt > cfs > idle的顺序依次 从各个调度器类中pick_next函数,从而选择一个最优的进程

周期性调度器的工作由scheduler_tick函数完成(定义在kernel/sched/core.c)在scheduler_tick中周期性调度器通过调用curr进程所属调度器类sched_class的task_tick函数完成周期性调度的工作

3.7.5.6.1. CFS的周期性调度

3.7.5.6.1.1. task_tick_fair与周期性调度

CFS完全公平调度器类通过task_tick_fair函数完成周期性调度的工作

static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
{
    struct cfs_rq *cfs_rq;
    struct sched_entity *se = &curr->se;    /获取当前进程curr所在的调度实体

    for_each_sched_entity(se) {     //在不支持组调度的条件下只循环一次,在组调度的条件下调度实体存在层次关系,更新子调度实体时必须更新父调度实体
        cfs_rq = cfs_rq_of(se);     //获取当前运行进程所在的cfs就绪队列
        entity_tick(cfs_rq, se, queued);    //完成周期性调度
    }

    if (static_branch_unlikely(&sched_numa_balancing))
        task_tick_numa(rq, curr);

    update_misfit_status(curr, rq);
    update_overutilized_status(task_rq(curr));
}

3.7.5.6.1.2. entity_tick函数

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(cfs_rq, curr, UPDATE_TG);   //更新load
    update_cfs_group(curr);

#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作出决策
        check_preempt_tick(cfs_rq, curr);
}

3.7.5.6.1.3. check_preempt_tick函数

check_preempt_tick函数的目的在于,判断是否需要抢占当前进程,确保没有哪个进程能够比延迟周期中确定的份额运行的更长,该份额对应的实际运行时间长度在sched_clice中计算。 进程在CPU上实际运行的时间由sum_exec_runtime - prev_sum_exec_runtime给出

因此抢占决策很容易做出决定,如果检查发现当前进程运行需要被抢占,那么通过resched_task发出重调度请求,这会在task_struct中设置TIF_NEED_RESCHED标志,核心调度器会在下一个适当的时机发起重调度

其实需要抢占的条件有下面两种可能性

  • curr进程实际运行时间比期望的运行时间长,此时说明curr已经运行了足够长的时间

  • curr进程与红黑树中最左进程Left虚拟运行时间各差值大于curr的期望运行时间

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;

    ideal_runtime = sched_slice(cfs_rq, curr);  //计算curr的理论上的运行时间
    delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;      //计算curr的实际运行时间
    if (delta_exec > ideal_runtime) {       //如果实际运行时间比理论上运行时间要长,说明curr进程已经运行了足够长的时间,应该调度新的进程抢占CPU了
        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 = __pick_first_entity(cfs_rq);
    delta = curr->vruntime - se->vruntime;  //计算红黑树最左端节点与curr进程的虚拟运行时间差值

    if (delta < 0)
        return;

    if (delta > ideal_runtime)
        resched_curr(rq_of(cfs_rq));    //设置重调度表示TIF_NEDD_RESCHED
}

周期性调度器不显式的进行调度,而是采用延迟调度的策,如果发现需要抢占,周期性调度器就设置进程的重调度标识TIF_NEED_RESCHED然后由主调度器完成调度工作

注解

TIF_NEED_RESCHED标识表明进程需要被调度,TIF前缀表明这是一个存储在进程thread_info中flag字段的一个标识信息.在内核的关键位置,会检查当前进程是否设置了重调度标志TIF_NEED_RESCHE