2.6.2.5 调度逻辑 - 任务抢占

所谓抢占,就是停止当前正在执行的任务,换另一个任务来执行。导致这种情况发生的原因很多,例如当前任务已经运行了太长时间,需要让出CPU; 用户修改了任务优先级,导致当前任务应该被换下;或者优先级更高的任务被唤醒,需要立刻开始运行。但当这种情况发生时,调度器并不会真的立刻切换任务,而是调用 resched_curr() 函数为当前任务设置一个叫着 TIF_NEED_RESCHED 的标记位,该函数的主要逻辑如下:

/* file: kernel/sched/core.c */
void resched_curr(struct rq *rq) {
struct task_struct *curr = rq->curr;
int cpu;

lockdep_assert_held(&rq->lock);

/* 如果当前任务已经设置了 TIF_NEED_RESCHED 标记位,则返回 */
if (test_tsk_need_resched(curr))
return;

cpu = cpu_of(rq);

if (cpu == smp_processor_id()) {
/* 设置标记位 */
set_tsk_need_resched(curr);
set_preempt_need_resched();
return;
}
}

调用该函数的地方非常多,这里主要介绍两个典型场景:

1. 任务运行时间耗尽

经过前面的讨论,我们知道任务在每个调度周期内的时间配额是有限的,当任务耗尽了该时间片之后就需要让出CPU, 以便给其它任务提供运行的机会。上一节在讨论周期性调度时,我们看到函数 entity_tick 最后调用了 check_preempt_tick, 后者就是检查任务时间是否耗尽的函数,我们来看一下它的实现:

2. 新任务被创建

新任务创建后可能需要立刻投入运行,这时候也需要检查抢占情况。从用户态(user mode)来看,Linux的新任务通过系统调用 clone() 创建,其最终会调用到 kernel_clone 方法,该方法中与调度相关的逻辑裁剪如下:

函数 copy_process 为新的任务创建并初始化 task_struct, 中途会调用函数 sched_fork 对与调度相关的字段进行初始化:

函数 sched_forkwake_up_new_task 都定义在调度文件 kernel/sched/core.c 中,前者用来初始化各种调度相关的字段,而后者用来检查新创建的任务是否需要抢占当前任务,我们这里着重看一下后者的实现:

函数体中的 activate_task 最终会将新任务放入运行队列中,而 check_preempt_curr 就是最终判断抢占逻辑的函数。CFS 中实现该方法的函数是 check_preempt_wakeup, 仅保留主干逻辑,代码如下:

TIF_NEED_RESCHED 位被设置之后,调度器在下一次调度发生时就会将该任务换下,并从runqueue中挑选出下一个合适的任务来执行,下一节中我们将探讨调度时机的问题。

Last updated

Was this helpful?