基于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