调度入口__schedule() 主要做了几件事:
deactivate_task() -> pick_next_task() -> context_switch()
pick_next_task 的实现中主要两个步骤:
(IN __pick_next_task)
put_prev_task_balance(rq, prev, rf);
for_each_class(class) {
p = class->pick_next_task(rq);
if (p)
return p;
}
put_prev_task_balance 会从高优先级向低优先级搜索可调度的task,优先级的顺序是:
stop > deadline > real time > fair > idle
在 balance_rt 中如果 pushable_tasks 队列的最高优先级的task比当前优先级要小,且当前任务队列的load 很大了,则尝试为它们决定应该调度到哪个cpu上pull_rt_task()大概过程是:
- for cpu in 调度域中的其它cpus:
- 找到pushable_tasks的优先级比自己高的cpu2的pushable_tasks.
- 取出那个cpu2的pushable_tasks中优先级最高的task2.2
- 如果它比当前cpu1上task1优先级高,但没有那个cpu2上正执行的task2.1优先级高,则尝试迁移过来,执行resched_curr()
- 一个特殊情况是那个task不支持迁移,则为它加一个push_cpu_stop的stop级别的任务(我看的版本代码里要求添加stop任务要求cpu2 == smp_processor_id(),那按说一定不会添加成功,可能是历史原因吧)
- 当这个任务在那个cpu2上被调度时,会暂停cpu2正在执行的task2.1,为它找到合适的cpu去执行(有可能给到我当前的cpu1,也可能给到别人),从而给这个task2.2一个被pick的机会.
之后执行 put_prev_task_rt,这里会更新统计信息,如果发现有 task 已经超出了队列的时间片(sched_rt_runtime_exceeded),则尝试从调度域中其它的cpu瓜分时间片过来(do_balance_runtime)。如果成功的话,且这个任务还没结束,则把它放到可调度pushable_tasks队列上(enqueue_pushable_task)。如果无济于事的话,则从各层active 队列里摘除,并尝试插入到上面某个时间片充足的层级的group的active 队列中(dequeue_rt_entity),并重新调度这个实时队列(resched_curr)。
现在假设active队列上还有其它 active 的任务,执行 pick_next_task,找出下一个active 的最高优先级任务:
static struct task_struct *pick_next_task_rt(struct rq *rq)
{
struct task_struct *p = pick_task_rt(rq);
if (p)
set_next_task_rt(rq, p, true);
return p;
}
pick_task_rt 会根据bitmap 找到最高优先级的队列,从中拿出第一个task,作为要执行的task。set_next_task_rt 会把这个选中的task从pushable_tasks中摘除下来,并注册一个push_rt_tasks的callback,并在active队列上任务全耗尽时通过__balance_callbacks来调度pushable_tasks中剩下的task。
如果执行过程中有抢占或time_tick到达,则可能会把task 放回 active 队列(requeue_task_rt)。