基于MTK linux-4.14
看代码过程中发现 put_prev_entity() 中判断 prev 的 se->on_rq 为真还执行 enqueue 操作,感到疑惑,追踪一下代码进行分析。
1. 相关代码段
__schedule(bool preempt) { /* 非抢占且非running状态,表示 prev 任务自己因为休眠主动放弃cpu的 */ if (!preempt && prev->state) { deactivate_task(rq, prev, DEQUEUE_SLEEP | DEQUEUE_NOCLOCK); //只有非抢占状态下才有执行,若是被抢占的,没有执行 dequeue_entity() { if (se != cfs_rq->curr) //此调用路径不成立,正在running的任务是已经dequeue的,在选prev时pick_next_task_fair()中已经dequeue过了。 __dequeue_entity(cfs_rq, se); se->on_rq = 0; //也就是说睡眠导致被切换的任务其 se->on_rq 才会被设置为0 ###### } prev->on_rq = 0; //也就是说睡眠导致被切换的任务其 task->on_rq 才会被设置为0 ###### } ... next = pick_next_task(rq, prev, &rf); /* 下面就直接 switch 了,没有on_rq相关内容了 */ if (likely(prev != next)) { ... rq->curr = next; //switch 的前一时刻才对 rq->curr 进行更新 ###### rq = context_switch(rq, prev, next, &rf); } }
dequeue_entity 中将 se->on_rq = 0,其常规调用路径如下,而抢占切换路劲下是没有对prev任务调用 deactivate_task 的。
__schedule deactivate_task dequeue_task //调度类的这个回调 dequeue_task_fair //几乎是唯一调用路径 dequeue_entity se->on_rq = 0;
若是在CFS任务之间切换,pick_next_task 调用的就是 pick_next_task_fair:
static struct task_struct *pick_next_task_fair(struct rq *rq, struct task_struct *prev, struct rq_flags *rf) { struct sched_entity *curr = cfs_rq->curr; /* * 由于我们在没有执行 put_prev_entity() 的情况下到达这里(这个函数的下面才执行), * 我们还需要考虑 cfs_rq->curr。 如果它仍然是一个runnable的实体,update_curr() * 将更新它的 vruntime,否则忘记我们见过它。 */ if (curr) { if (curr->on_rq) //此调度实体还在cfs_rq队列上,此路径下,对应的是prev->se,由上可知若是被抢占的才为真。 update_curr(cfs_rq); else curr = NULL; ... } ... se = pick_next_entity(cfs_rq, curr); //只是选出来一个se,并没有执行任何dequeue的操作 p = task_of(se); if (prev != p) { struct sched_entity *pse = &prev->se; ... put_prev_entity(cfs_rq, pse); //这里面将cfs_rq->curr = NULL set_next_entity(cfs_rq, se); //这里面将cfs_rq->curr = se } ... } //将prev任务放回队列 static void put_prev_entity(struct cfs_rq *cfs_rq, struct sched_entity *prev) { if (prev->on_rq) //为真表示是被抢占的 update_curr(cfs_rq); //此路径下应该是和上面update_curr重复了。。。 ... /* prev若是被抢占的,条件才成立。只有被抢占的,才需要执行挂回去的操作(若是sleep触发的切换就不用挂回去了)*/ if (prev->on_rq) { //se是on_rq状态了还要 enqueue! /* Put 'current' back into the tree. */ __enqueue_entity(cfs_rq, prev); //将被抢占的任务重新放回cfs队列。放回去之后其on_rq状态与其实际就在cfs_rq上就匹配上了 ... } cfs_rq->curr = NULL; //更新cfs_rq->curr } //从cfs_rq中选出一个任务来运行 static void set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) //fair.c { /* 'current' is not kept within the tree. * 这个注释应该就是说这个判断是为了避免对curr重复dequeue。 */ if (se->on_rq) { ... __dequeue_entity(cfs_rq, se); //虽然dequeue了,但是 se->on_rq 并没有清0 ###### } cfs_rq->curr = se; //更新cfs_rq->curr ... }
2. se->on_rq分析结论
准确来说,当任务被执行 set_next_entity() 选出去运行,其 se->on_rq 就不能正确表示其在 cfs_rq 上挂载状态了,直到其由于休眠被执行 deactivate_task() 而被切走,或由于被抢占执行 put_prev_entity() 将其重新放回队列中,其 se->on_rq 状态才与其是否的确挂在 cfs_rq 上相吻合。可以理解为 se->on_rq 表示 running+runnable 的调度实体。
3. 对于任务的 p->on_rq,选出来作为 next 时默认是不为0的,因为在任务入队列的路径中都是有赋值的:
(1) 被唤醒
try_to_wake_up //core.c //p->on_rq == 0 的情况 ttwu_queue //core.c ttwu_do_activate //core.c ttwu_activate //core.c enqueue_task p->on_rq = TASK_ON_RQ_QUEUED; //p->on_rq !=0 的情况 if (p->on_rq && ttwu_remote(p, wake_flags)) //ttwu_remote的唯一调用位置,作用主要是将p->state=TASK_RUNNING goto stat; //只是统计一些信息就成功返回了
看 ttwu_remote() 函数的注释,它也是将 p->on_rq !=0 当做是被抢占的情况了.
(2) 迁移过来
attach_one_task //fair.c attach_tasks //fair.c attach_task activate_task enqueue_entity p->on_rq = TASK_ON_RQ_QUEUED;
(3) 被抢占而插入队列
(4) 还有一种情况,当任务被迁移走时,其 p->on_rq 也是不等于0的
active_load_balance_cpu_stop //fair.c detach_one_task //fair.c load_balance //fair.c detach_tasks //fair.c detach_task p->on_rq = TASK_ON_RQ_MIGRATING; deactivate_task dequeue_entity
4. p->on_rq分析结论
对于 p->on_rq,若是由于睡眠被切走的才为0,其它情况下都不为0。应该是主要用来判断其是否是由于睡眠而被切走的,只有在睡眠状态下为0。一部分内核代码中通过判断 p->on_rq 不等于0来选择 runnable 的任务。也有一部分代码和 p->state 配合使用来断言特定状态下的任务,例如:
void set_task_cpu(struct task_struct *p, unsigned int new_cpu) //kernel/sched/core.c { /* * We should never call set_task_cpu() on a blocked task, * ttwu() will sort out the placement. */ WARN_ON_ONCE(p->state != TASK_RUNNING && p->state != TASK_WAKING && !p->on_rq); ... }
5. 若是没有使能 CONFIG_FAIR_GROUP_SCHED,那么 cfs_rq->curr 基本上等同于 rq->curr。