内核一共包含5种调度算法:
struct sched_class stop_sched_class; // 停机调度类: 停机进程
struct sched_class dl_sched_class; // 限期调度类: 限期进程
//SCHED_DEADLINE:限期进程调度策略,使task选择Deadline调度 器来调度运行;
struct sched_class rt_sched_class; // 实时调度类:实时进程
//SCHED_FIFO : 实时进程调度策略,先进先出调度没有时间片,没有更高优先级的状态下,只有等待主动让出CPU;
//SCHED_RR :实时进程调度策略,时间片轮转,进程使用完时间片之后加入优先级对应运行队列当中的尾部,把CPU让给同等优先级的其它进程;
struct sched_class fair_sched_class; // 公平调度类 : 普通进程
//SCHED_NORMAL : 普通进程调度策略,使task选择CFS调度器来调度运行:
//SCHED_IDLE: 普通进程调度策略,使task以最低优先级选择CFS调度器来调度运行:
struct sched_class idle_sched_class; // 空闲调度类 : 每个处理器上的空闲线程停机调度
停机调度类是优先级最高的调度类,停机进程(stop-task)是优先级最高的进程,可以抢占所有其他进程,其他进程不可以抢占停机进程。停机(stop是指stop machine)的意思是使处理器停下来,做更紧急的事情。
迁移线程: 在Linux内核中,每个CPU都有一个迁移线程守护程序来执行资源平衡作业。
/*
* Simple, special scheduling class for the per-CPU stop tasks:
*/
const struct sched_class stop_sched_class = {
.next = &dl_sched_class,
.enqueue_task = enqueue_task_stop,
.dequeue_task = dequeue_task_stop,
.yield_task = yield_task_stop,
.check_preempt_curr = check_preempt_curr_stop,
.pick_next_task = pick_next_task_stop,
.put_prev_task = put_prev_task_stop,
#ifdef CONFIG_SMP
.select_task_rq = select_task_rq_stop,
.set_cpus_allowed = set_cpus_allowed_common,
#endif
.set_curr_task = set_curr_task_stop,
.task_tick = task_tick_stop,
.get_rr_interval = get_rr_interval_stop,
.prio_changed = prio_changed_stop,
.switched_to = switched_to_stop,
.update_curr = update_curr_stop,
};目前只有迁移线程属于停机调度类,每个处理器有一个迁移线程(名称是 migration/<cpu_id>),用来把进程从当前处理器迁移到其他处理器,迁移线程对外伪装成实时优先级是99的先进先出实时进程。
停机进程没有时间片,如果它不主动让出处理器,那么它将一直霸占处理器。
引入停机调度类的一个原因是:为了支持限期调度类,迁移线程的优先级必须比限期进程的优先级高,能够抢占所有其他进程,才能快速处理调度器发出的迁移请求,把进程从当前处理器迁移到其他处理器。
限期调度
限期调度类使用最早期限优先算法,使用红黑树把进程按照绝对截止期限从小到大排序,每次调度时选择绝对截止期限最小的进程。
如果限期进程用完了它的运行时间,它将让出处理器,并且把它从运行队列中删除。在下一个周期的开始,重新把它添加到运行队列中。
const struct sched_class dl_sched_class = {
.next = &rt_sched_class,
.enqueue_task = enqueue_task_dl,
.dequeue_task = dequeue_task_dl,
.yield_task = yield_task_dl,
.check_preempt_curr = check_preempt_curr_dl,
.pick_next_task = pick_next_task_dl,
.put_prev_task = put_prev_task_dl,
#ifdef CONFIG_SMP
.select_task_rq = select_task_rq_dl,
.migrate_task_rq = migrate_task_rq_dl,
.set_cpus_allowed = set_cpus_allowed_dl,
.rq_online = rq_online_dl,
.rq_offline = rq_offline_dl,
.task_woken = task_woken_dl,
#endif
.set_curr_task = set_curr_task_dl,
.task_tick = task_tick_dl,
.task_fork = task_fork_dl,
.prio_changed = prio_changed_dl,
.switched_from = switched_from_dl,
.switched_to = switched_to_dl,
.update_curr = update_curr_dl,
};有些任务必须在指定时间窗口内完成。例如视频的编码与解码,CPU 必须以特定频率完成对应的数据处理;这类任务是优先级最高的用户任务,CPU 应该首先满足。
实时调度类
// kernel/sched/sched.h
/*
* This is the priority-queue data structure of the RT scheduling class:
*/
struct rt_prio_array {
DECLARE_BITMAP(bitmap, MAX_RT_PRIO+1); /* include 1 bit for delimiter */
struct list_head queue[MAX_RT_PRIO];
};位图bitmap用来快速查找第一个非空队列。数组queue的下标是实时进程的调度优先级,下标越小,优先级越高。
每次调度,先找到优先级最高的第一个非空队列,然后从队列中选择第一个进程。
使用先进先出调度策略的进程没有时间片,如果没有优先级更高的进程,并且它不主动让出处理器,那么它将一直霸占处理器。
2使用轮流调度策略的进程有时间片,用完时间片以后,进程加入队列的尾部。默认的时间片是5毫秒,可以通过文件/proc/sys/kernel/sched_rr_timeslice_ms修改时间片。
const struct sched_class rt_sched_class = {
.next = &fair_sched_class,
.enqueue_task = enqueue_task_rt,
.dequeue_task = dequeue_task_rt,
.yield_task = yield_task_rt,
.check_preempt_curr = check_preempt_curr_rt,
.pick_next_task = pick_next_task_rt,
.put_prev_task = put_prev_task_rt,
#ifdef CONFIG_SMP
.select_task_rq = select_task_rq_rt,
.set_cpus_allowed = set_cpus_allowed_common,
.rq_online = rq_online_rt,
.rq_offline = rq_offline_rt,
.task_woken = task_woken_rt,
.switched_from = switched_from_rt,
#endif
.set_curr_task = set_curr_task_rt,
.task_tick = task_tick_rt,
.get_rr_interval = get_rr_interval_rt,
.prio_changed = prio_changed_rt,
.switched_to = switched_to_rt,
.update_curr = update_curr_rt,
};实时任务(Real-time Task)对响应时间要求更高,例如编辑器软件,它可能由于等待用户输入长期处于睡眠之中,但一旦用户有输入动作,我们就期望编辑器能够立马响应,而不是等系统完成其它任务之后才开始反应,这一点对用户体验十分重要。
公平调度
Fair 调度类用来调度绝大多数用户任务,CFS 实现的就是这种调度类,其核心逻辑是根据任务的优先级公平地分配 CPU 时间。
公平调度类使用完全公平调度算法。完全公平调度算法引入了虚拟运行时间的概念:
虚拟运行时间=实际运行时间 * nice_0对应的权重/进程的权重
nice值和权重的对应关系如下:
// kernel/sched/core.c
const int sched_prio_to_weight[40] = {
/* -20 */ 88761, 71755, 56483, 46273, 36291,
/* -15 */ 29154, 23254, 18705, 14949, 11916,
/* -10 */ 9548, 7620, 6100, 4904, 3906,
/* -5 */ 3121, 2501, 1991, 1586, 1277,
/* 0 */ 1024, 820, 655, 526, 423,
/* 5 */ 335, 272, 215, 172, 137,
/* 10 */ 110, 87, 70, 56, 45,
/* 15 */ 36, 29, 23, 18, 15,
};// kernel/sched/fair.c
/*
* All the scheduling class methods:
*/
const struct sched_class fair_sched_class = {
.next = &idle_sched_class,
.enqueue_task = enqueue_task_fair,
.dequeue_task = dequeue_task_fair,
.yield_task = yield_task_fair,
.yield_to_task = yield_to_task_fair,
.check_preempt_curr = check_preempt_wakeup,
.pick_next_task = pick_next_task_fair,
.put_prev_task = put_prev_task_fair,
#ifdef CONFIG_SMP
.select_task_rq = select_task_rq_fair,
.migrate_task_rq = migrate_task_rq_fair,
.rq_online = rq_online_fair,
.rq_offline = rq_offline_fair,
.task_dead = task_dead_fair,
.set_cpus_allowed = set_cpus_allowed_common,
#endif
.set_curr_task = set_curr_task_fair,
.task_tick = task_tick_fair,
.task_fork = task_fork_fair,
.prio_changed = prio_changed_fair,
.switched_from = switched_from_fair,
.switched_to = switched_to_fair,
.get_rr_interval = get_rr_interval_fair,
.update_curr = update_curr_fair,
#ifdef CONFIG_FAIR_GROUP_SCHED
.task_change_group = task_change_group_fair,
#endif
};空闲调度
与 Stop 类似,Idle 调度类也是仅供内核使用的特殊调度类,其优先级最低,只有在没有任何用户任务时才会用到。内核会为每个 CPU 绑定一个内核线程kthread来完成该任务,该线程会在队列无事可做的情况下启动该任务,并将 CPU 的功耗降到最低。
// kernel/sched/idle.c
/*
* Simple, special scheduling class for the per-CPU idle tasks:
*/
const struct sched_class idle_sched_class = {
/* .next is NULL */
/* no enqueue/yield_task for idle tasks */
/* dequeue is not valid, we print a debug message there: */
.dequeue_task = dequeue_task_idle,
.check_preempt_curr = check_preempt_curr_idle,
.pick_next_task = pick_next_task_idle,
.put_prev_task = put_prev_task_idle,
#ifdef CONFIG_SMP
.select_task_rq = select_task_rq_idle,
.set_cpus_allowed = set_cpus_allowed_common,
#endif
.set_curr_task = set_curr_task_idle,
.task_tick = task_tick_idle,
.get_rr_interval = get_rr_interval_idle,
.prio_changed = prio_changed_idle,
.switched_to = switched_to_idle,
.update_curr = update_curr_idle,
};