调度器36—抢占—2—check_preempt_curr()

发布时间 2023-04-12 22:54:03作者: Hello-World3

基于MTK-4.19

一、函数分析

1. 函数实现

void check_preempt_curr(struct rq *rq, struct task_struct *p, int flags) //core.c
{
    const struct sched_class *class;

    /* 若任务p核当前任务属于同一调度类,则交由调度类自己去处理 */
    if (p->sched_class == rq->curr->sched_class) {
        rq->curr->sched_class->check_preempt_curr(rq, p, flags);
    } else {
        /* 优先级由高到低 */
        for_each_class(class) {
            /* 只遍历到curr所在优先级就行了 ==>CFS抢占不了RT */
            if (class == rq->curr->sched_class)
                break;
            /* 将 rq 的当前任务标记为“需要重新调度” */
            if (class == p->sched_class) {
                resched_curr(rq);
                break;
            }
        }
    }
    ...
}

2. 此函数执行逻辑分三种情况:

(1) rq->curr 和 p 属于同一调度类:交由调度类的 .check_preempt_curr 回调处理。

(2) rq->curr 的调度类优先级高于 p:什么也不做。

(3) rq->curr 的调度类优先级低于 p:会对curr进行需要重新调度标记。


3. 各调度类 .check_preempt_curr 回调分别为:

.check_preempt_curr = check_preempt_curr_stop, //stop_task.c
.check_preempt_curr = check_preempt_curr_dl,   //deadline.c
.check_preempt_curr = check_preempt_curr_rt,   //rt.c
.check_preempt_curr = check_preempt_wakeup     //fair.c
.check_preempt_curr = check_preempt_curr_idle, //idle.c

 

二、函数调用路径

1. 此函数主要在 fair.c 和 core.c 两个文件中调用

(1) fair.c:

    rt_mutex_setprio //core.c
    __sched_setscheduler //core.c
        check_class_changed //core.c if (prev_class != p->sched_class) 才执行.【1】调度类变更
            p->sched_class->switched_to(rq, p);
                fair_sched_class.switched_to //fair.c
                    switched_to_fair //fair.c 传参:(rq, p, 0)
            check_class_changed    //core.c if (oldprio != p->prio || dl_task(p)) 才执行.【2】优先级变更
                fair_sched_class.prio_changed //fair.c
                    prio_changed_fair //fair.c 传参:(rq, p, 0)
        load_balance //fair.c 【3】active load balance
            active_load_balance_cpu_stop
                attach_one_task //fair.c
            load_balance //fair.c 【4】负载迁移
                attach_tasks //fair.c
                    attach_task //fair.c 传参:(rq, p, 0)
                        check_preempt_curr

(2) core.c:

                _do_fork //fork.c 【1】 唤醒新任务
                    wake_up_new_task //core.c 传参:(rq, p, WF_FORK)
        try_to_wake_up //core.c    唤醒一般路径
            ttwu_queue //core.c
                ttwu_do_activate //core.c
            try_to_wake_up //core.c 对应被唤醒任务 p->on_rq == TASK_ON_RQ_QUEUED 的路径
                ttwu_remote //core.c
            __schedule //core.c 只针对worker,前一个worker sleep了,就唤醒下一个idle的worker线程
                try_to_wake_up_local //core.c
                    ttwu_do_wakeup //core.c 传参:(rq, p, wake_flags) 【2】唤醒任务
            migrate_swap //core.c 【3】MTK方案,让CFS和RT一起进行负载均衡
                migrate_swap_stop //core.c
                    __migrate_swap_task //core.c 传参:(dst_rq, p, 0)  使能 CONFIG_NUMA_BALANCING || CONFIG_MTK_SCHED_BIG_TASK_MIGRATE 才生效
        __set_cpus_allowed_ptr //core.c
        sched_exec //core.c 【4】exec创建新任务
            migration_cpu_stop in core.c
            migrate_tasks //core.c 【5】isolate cpu触发的迁移
                __migrate_task //core.c
                __set_cpus_allowed_ptr //core.c 【6】绑核
                    move_queued_task //core.c 传参:(rq, p, 0)
                        check_preempt_curr

可以看到,在调度类变更、优先级变化、主动均衡、任务迁移、唤醒、绑核 等路径都会触发抢占的检查。


注:MTK方案配置 CONFIG_MTK_SCHED_BIG_TASK_MIGRATE,描述是原始的 Linux 设计,RT & CFS 分别做负载平衡,让会导致不平衡。 也就是说,一个 CPU 有 2 个以上的任务,但是,一个 CPU 是 IDLE 我们让 RT & CFS 相互检查并使负载更加平衡。

注:isolate cpu时的迁移使用的是 migrate_tasks(),而负载均衡迁移任务使用的是 attach_tasks()/detach_tasks()。

 

三、IDLE调度类抢占实现

1. idle 调度类无条件将自己标记为需要重新调度

static void check_preempt_curr_idle(struct rq *rq, struct task_struct *p, int flags) //idle.c
{
    resched_curr(rq);
}

 

四、CFS调度类抢占实现

1. CFS 调度类根据虚拟时间判断是否要抢占

static void check_preempt_wakeup(struct rq *rq, struct task_struct *p, int wake_flags) //fair.c
{
    ...
    if (wakeup_preempt_entity(se, pse) == 1) {
        ...
        goto preempt;
    }

    return;

preempt:
    resched_curr(rq);
    ...
}

 

五、RT调度类抢占实现

1. RT调度类根据优先级来判断,新唤醒任务优先级高则抢占。若优先级相同默认不抢占,除非p是绑单核的无法迁移走,而rq->curr是可以迁移走的,才触发抢占。

static void check_preempt_curr_rt(struct rq *rq, struct task_struct *p, int flags)
{
    /* 若p的优先级高则触发抢占 */
    if (p->prio < rq->curr->prio) {
        resched_curr(rq);
        return;
    }

#ifdef CONFIG_SMP
    /*
     * 如果:
       * - 新唤醒的任务与当前任务具有同等优先级
       * - 新唤醒的任务是不可迁移的,而当前是可迁移的
     * - 当前任务将在下一次重新调度时被抢占
     *
     * 我们应该检查当前任务是否可以容易地移动到不同的cpu。 如果是这样,
     * 我们将重新调度以允许push逻辑尝试将当前移动到其他cpu上,为不可迁
     * 移任务腾出空间。
     */
    if (p->prio == rq->curr->prio && !test_tsk_need_resched(rq->curr))
        check_preempt_equal_prio(rq, p);
#endif
}

static void check_preempt_equal_prio(struct rq *rq, struct task_struct *p)
{
    /* 当前任务绑单核了,或测试发现无法为当前任务选到cpu(没有选到cpu返回0),则不触发抢占 */
    if (rq->curr->nr_cpus_allowed == 1 || !cpupri_find(&rq->rd->cpupri, rq->curr, NULL))
        return;

    /* 执行到这说明rq->curr是可以迁移走的,若p也是可以迁移走的,则不触发抢占 */
    if (p->nr_cpus_allowed != 1 && cpupri_find(&rq->rd->cpupri, p, NULL))
        return;

    /* rq->curr可以迁移走,而p无法迁移走,则触发抢占 */
    requeue_task_rt(rq, p, 1);
    /* 触发抢占 */
    resched_curr(rq);
}

 

六、DL调度类抢占实现

TODO


七、Stop调度类抢占实现

TODO