2.6.2.6 调度逻辑 - 调度时机

从调度器的角度来看,真正的调度(即调度器完成上下文切换,正儿八经地换一个任务来执行)仅发生在函数 schedule() 中,剔除额外代码,我们来看一下该函数的主要流程:

/* file: kernel/sched/core.c */
asmlinkage __visible void __sched schedule(void) {
struct task_struct *tsk = current;

do {
preempt_disable();
/* 调用函数 __schedule 来做具体的工作 */
__schedule(false);
sched_preempt_enable_no_resched();
/* need_resched用来判断当前任务是否应该被抢占,此时的当前任务就是函数__schedule最新选择的任务,如果是的话那么继续调用函数__schedule以便调用下一个合适的任务。*/
} while (need_resched());
}

static void __sched notrace __schedule(bool preempt) {
struct task_struct *prev, *next;
unsigned long *switch_count;
unsigned long prev_state;
struct rq_flags rf;
struct rq *rq;
int cpu;

/* 获取到当前CPU序号,进而获取到其runqueue */
cpu = smp_processor_id();
rq = cpu_rq(cpu);
/* rq->curr 是当前正在执行的任务 */
prev = rq->curr;

prev_state = prev->state;
if (!preempt && prev_state) {
if (signal_pending_state(prev_state, prev)) {
    prev->state = TASK_RUNNING;
} else {
    /* preempt 如果为false,
        则说明此次调度不是由于任务抢占导致的,那么导致调度发生的原因就是任务主动要求让出CPU,
        对于由于IO事件进入睡眠的任务而言,需要先将其从运行队列中踢出去。该函数最终会调用调度类(sched_class)的
        dequeue_task 方法完成具体工作。*/
    deactivate_task(rq, prev, DEQUEUE_SLEEP | DEQUEUE_NOCLOCK);
}
}

/* 从队列中选择下一个任务,该函数最终会调用调度类(sched_class的函数pick_next_task方法。对于CFS而言,就是选择vruntime最小的任务
*/
next = pick_next_task(rq, prev, &rf);
/* 清除 prev 任务的TIF_NEED_RESCHED标记,因为此时它已经被抢占了 */
clear_tsk_need_resched(prev);

if (likely(prev != next)) {
/* 完成上下文切换,CPU将开始执行刚刚挑出来的任务next了 */
rq = context_switch(rq, prev, next, &rf);
}
}

关于选择下一个任务的语句 next = pick_next_task(rq, prev, &rf);, 其中函数 pick_next_task() 就是我们在调度类中讨论的那个方法,CFS 的实现是函数 pick_next_task_fair, 他的主要逻辑就是从红黑树中选择最左侧的任务,我们在这里不再深入细节讨论。

函数 schedule() 最后调用 context_switch() 完成上下文切换,至此,整个调度工作就完成了!

Last updated