上文介绍了cfs的enqueue操作,那对应的,就需要dequeue,dequeue就是在rb-tree中删除一个任务
以schedule为例,其流程为
schedule _schedule pick_next_task pick_next_task_fair set_next_entity __dequeue_entity rb_erase_cached
我们可以看到dequeue的核心函数是__dequeue_entity, 根据上述流程可以知道,当任务被选中执行的时候,会选中rb-tree最小的任务后,主动调用__dequeue_entity来pop queue
其他非标准场景例如
这些场景上单独封装了和enqueue_task成对的函数,如下
static inline void dequeue_task(struct rq *rq, struct task_struct *p, int flags) { if (!(flags & DEQUEUE_NOCLOCK)) update_rq_clock(rq); if (!(flags & DEQUEUE_SAVE)) { sched_info_dequeued(rq, p); psi_dequeue(p, flags & DEQUEUE_SLEEP); } uclamp_rq_dec(rq, p); p->sched_class->dequeue_task(rq, p, flags); }
同样的也有成对的deactivative_task
void activate_task(struct rq *rq, struct task_struct *p, int flags) { enqueue_task(rq, p, flags); p->on_rq = TASK_ON_RQ_QUEUED; } void deactivate_task(struct rq *rq, struct task_struct *p, int flags) { p->on_rq = (flags & DEQUEUE_SLEEP) ? 0 : TASK_ON_RQ_MIGRATING; dequeue_task(rq, p, flags); }
值得注意的是,deactivate_task和dequeue_task有一些额外的flag判断,简单解析一下
说明这次deactivate_task大概率是因为睡眠或者迁移导致的dequeue,分别为sleep和migration设置了on_rq标志位
如果设计到睡眠,迁移,那么入队出队需要更新时间,但是如果是直接schedule,那么这个会默认带上,也就意味着没必要重复的更新时间
它和ENQUEUE_RESTORE成对,通过记录enqueue和dequeue的sched_info,然后在dequeue的时候故意不去将sched_info dequeue,这样可以在例如负载均衡的场景上获取sched_info信息统计负载,当然也可以调试.sched_info会更新enqueue和dequeue的时间戳
总结dequeue的大概流程为
deactivate_task dequeue_task dequeue_task_fair dequeue_entity __dequeue_entity rb_erase_cached
值得注意的是,我们在enqueue提到了为了维护公平性需要单独加上当前cpu的min_vruntime,那对应的就要在dequeue上单独减去当前cpu的min_vruntime,这个代码就藏在dequeue_entity中
if (!(flags & DEQUEUE_SLEEP)) se->vruntime -= cfs_rq->min_vruntime;
注意的是,如果不是SLEEP,那假设是MIDRATION,那是通过 update_curr() -> update_min_vruntime() 更新的,所以这里就不需要再多减一次了.
与enqueue成对的,本文介绍了cfs的dequeue,简单理解就是cfs的dequeue就是在rb-tree上删掉成员,所以有两个路径