目录
2.6 CFS调度类
2.6.1 数据结构
2.6.2 CFS操作
2.6.3 队列操作
2.6.4 选择下一个进程
2.6.5 处理周期性调度器
2.6.6 唤醒抢占
2.6 CFS调度类
即完全公平调度类,用于调度普通进程。
2.6.1 数据结构
struct sched_class fair_sched_class = {.next = &idle_sched_class, 连接其他调度器类.enqueue_task = enqueue_task_fair, 将进程se插入到红黑树.dequeue_task = dequeue_task_fair,.yield_task = yield_task_fair,.check_preempt_curr = check_preempt_wakeup,.pick_next_task = pick_next_task_fair, 从红黑树中选择下一个运行的进程.put_prev_task = put_prev_task_fair,.set_next_task = set_next_task_fair,#ifdef CONFIG_SMP.balance = balance_fair,.select_task_rq = select_task_rq_fair,.migrate_task_rq = migrate_task_rq_fair,.rq_online = rq_online_fair,.rq_offline = rq_offline_fair,.set_cpus_allowed = set_cpus_allowed_common,#endif.task_tick = task_tick_fair, 由周期性调度器来调用.update_curr = update_curr_fair,}
CPU的就绪队列,每个CPU都有一个该结构
struct rq {struct cfs_rq cfs; CFS调度类的运行队列struct rt_rq rt; RT调度类的运行队列}//CFS运行队列
struct cfs_rq {struct load_weight load; 该队列进程的权重总和unsigned int nr_running; 该队列就绪进程数u64 exec_clock; 该队列总共占用的cpu时间u64 min_vruntime; 该队列进程中最小的vruntimestruct sched_entity *curr; 当前正执行进程的调度实体struct rb_root tasks_timeline; 红黑树struct rb_node *rb_leftmost; 因为经常访问,用于缓存红黑树最左节点。};
struct cfs_rq中min_vruntime作用:
如果一个进程sleep太久,则它的vruntime很小。
如果进程se->vruntime太小,则:
se->vruntime = CFS->min_vruntime
2.6.2 CFS操作
enqueue_entity()、dequeue_entity()、task_tick()都会调用update_curr()
更新各种时间,统计量
void update_curr(struct cfs_rq *cfs_rq) {struct sched_entity *curr = cfs_rq->curr;u64 now = rq_clock_task(rq_of(cfs_rq));u64 delta_exec;delta_exec = now - curr->exec_start; //delta_exec:进程此次调度的执行时间curr->exec_start = now;curr->sum_exec_runtime += delta_exec; 当前进程累计运行时间delta_exec_weighted = calc_delta_fair(delta_exec, curr); weighted方式计算如下图curr->vruntime += delta_exec_weighted; 更新当前进程vruntimeupdate_min_vruntime(cfs_rq); 更新cfs队列的min_vruntime}
nice值越大,prio优先级越高,查看prio_to_weight数组可知weight越大,根据上面公式可知,delta_exec_weighted越小,这样进程curr->vruntime增长较慢,值越小,进程se越靠近红黑树左边,越先被调度。
最终nice越大,越先被调用。
进程休眠时:
se->vruntime不增长,而就绪队列cfs_rq->min_vruntime会递增,唤醒后会se往红黑树左边靠近,越先被调度。
进程运行时:
se->vruntime增长,会往红黑树右边靠近,越后被调度。
如此就实现了CFS的公平性。
2.6.3 队列操作
enqueue_task_fair:
把当前进程加入到CFS就绪队列。
void enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags){update_curr(cfs_rq); 更新统计量place_entity(cfs_rq, se, 0); 更新vruntimeif (se != cfs_rq->curr)__enqueue_entity(cfs_rq, se);}
__enqueue_entity:
遍历红黑数,比较进程和树上进程的vruntime值,若值小在树的左节点,值大在树的右节点,找到插入位置后,将进程se插入到红黑树。
cfs会调度红黑树最左节点的进程。所以struct cfs_rq会缓存最左节点,即:
struct cfs_rq {struct rb_node *rb_leftmost;}
如何判断一个进程se是否已经在CPU就绪队列?
struct sched_entity {
unsigned int on_rq; 答:通过该成员值
}
2.6.4 选择下一个进程
pick_next_task_fair:
选择下一个执行进程。
static struct task_struct *pick_next_task_fair(struct rq *rq){struct task_struct *p;struct cfs_rq *cfs_rq = &rq->cfs;struct sched_entity *se;if (!cfs_rq->nr_running) 该CFS队列没有进程return NULL;se = pick_next_entity(cfs_rq); 取出红黑树最左节点对应进程的se,实现如下set_next_entity(cfs_rq, se); 从红黑树中删除选中的进程se,并更新统计}pick_next_entity -> __pick_first_entitystruct sched_entity *__pick_first_entity(struct cfs_rq *cfs_rq){struct rb_node *left = cfs_rq->rb_leftmost; 缓存的红黑树最左节点return rb_entry(left, struct sched_entity, run_node);//使用container_of宏,根据rb_node,得到对应se}
2.6.5 处理周期性调度器
每次时钟中断会调用task_tick_fair()
void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued){struct cfs_rq *cfs_rq;for_each_sched_entity(&curr->se) {cfs_rq = cfs_rq_of(se);entity_tick(cfs_rq, se, queued); //如下图}}
update_curr():周期更新统计量。
check_preempt_tick:
如果进程运行时间大于期望时间间隔, 调用resched_task来设置当前进程的TIF_NEED_RESCHED标志,让出CPU。
设置TIF_NEED_RESCHED 标志后,等到抢占时机时,检查标志,并实施抢占操作。
CFS调度类fair_sched_class,可用的policy:
SCHED_NORMAL
SCHED_BATCH
SCHED_ILDE
RT调度类rt_sched_class,可用的policy:
SCHED_RR
SCHED_FHFO
2.6.6 唤醒抢占
当do_fork()新进程,会调用wake_up_new_task唤醒新进程,然后调用check_preempt_curr。
void check_preempt_curr(struct rq *rq, struct task_struct *p, int flags){const struct sched_class *class;rq->curr->sched_class->check_preempt_curr(rq, p, flags);}
CFS中check_preempt_curr定义如下:
const struct sched_class fair_sched_class = {.check_preempt_curr = check_preempt_wakeup,}
check_preempt_wakeup:
检查当前新进程是否需要被抢占,以唤醒更高优先级的进程运行。
若需要让出CPU,让高优先级进程先运行,则调用resched_task:
resched_task设置当前进程TIF_NEED_RESCHED标记。
应确保se被抢占前至少运行时间大于最小时间限额,避免频繁切换。
注意:设置完进程的TIF_NEED_RESCHED标志不代表当前进程立马被切换出去,而是等到抢占时机点,再来检查TIF_NEED_RESCHED标记标志,执行切换进程工作。
TIF_NEED_RESCHED:存储在进程thread_info的flag成员中。
抢占时机点:
1. 中断返回内核态时。
2. 退出临界区,如释放自旋锁。