pick_next_task是进程调度的关键步骤,主要功能是从发生调度的CPU的运行队列中选择一个进程运行。本文主要介绍该函数的实现过程。系统中的调度顺序是:实时进程------->普通进程------>空闲进程。分别从属于三个调度类:rt_sched_class,fair_sched_class和 idle_sched_class(每个cpu都有且只有一个Idle 进程且不会阻塞)。
注:idle进程是所有进程的祖先叫做进程0 ,idle 进程或因为历史的原因叫做swapper 进程。
运行top命令可以查看当前的进程状态。
下面介绍函数的具体细节
call tree
/*主动入口*/
schedule(void)---------->
| __schedule()
| |
preempt_schedule(void)--> |-->pick_next_task(struct rq *rq, struct task_struct *prev)
/*内核态抢占的入口*/
static inline struct task_struct *
pick_next_task(struct rq *rq, struct task_struct *prev)
{
const struct sched_class *class = &fair_sched_class;
struct task_struct *p;
/*
* Optimization: we know that if all tasks are in
* the fair class we can call that function directly:
*/
if (likely(prev->sched_class == class &&
rq->nr_running == rq->cfs.h_nr_running)) {
p = fair_sched_class.pick_next_task(rq, prev);
if (unlikely(p == RETRY_TASK))
goto again;
/* assumes fair_sched_class->next == idle_sched_class */
if (unlikely(!p))
p = idle_sched_class.pick_next_task(rq, prev);
return p;
}
again:
for_each_class(class) {
p = class->pick_next_task(rq, prev);
if (p) {
if (unlikely(p == RETRY_TASK))
goto again;
return p;
}
}
BUG(); /* the idle class will always have a runnable task */
}
pick_next_task()的执行过程大致可以分成两个步骤:
步骤1.检查运行队列中是否含有实时进程,if语句中判断了当前cpu就绪队列中的进程数目是否与普通进程的就绪队列中的进程数目相同,如果相同就说明了系统中全是普通进程,直接通过cfs算法(完全公平调度算法Completely Fair Scheduler)的调度类的pick_next_task_fair函数来从普通进程的就绪队列中寻找进程即可。
cfs算法(完全公平调度算法)的调度类的pick_next_task_fair函数返回值有三种情况
- RETRY_TASK:表示有从属于更高优先级调度类的进程被唤醒------------>跳转到步骤2。
- 空指针:表示没有cfs调度类的进程处于就绪态------------------->在idle调度类中寻找进程。(idle调度类永远非空)
- struct task 结构体指针:表示选择到一个进程--------------->返回指针
步骤2.遍历调度类的链表,并从中选择一个优先级最高的进程。
在内核中的所有现有调度类是按优先级排列在调度类链表中的。高优先级的调度类指向下较低的优先级调度类。在当前Linux 版本中,其初始流程如下所示
rt_sched_class → fair_sched_class → idle_sched_class → NULL