第四十八章:调度类
结丹后期涉及内核源码:
一
林小源在调度竞技场中,发现了一座巨大的阶梯。
阶梯有五层,每一层都站着一位守关人。最顶层是一个模糊的身影,几乎看不清面目;第二层是 Deadline,金色长袍在风中飘动;第三层是 RT,银色铠甲闪闪发光;第四层是 CFS,绿色长衫温文尔雅;最底层是一个懒洋洋的少年,正靠在柱子上打瞌睡——那是 idle。
"这是调度类的层次。"CFS 从第四层走下来,对林小源说,"每个调度类都实现了相同的接口——enqueue_task、dequeue_task、pick_next_task、task_tick——但有不同的调度策略。"
林小源看着阶梯,问道:"调度器怎么知道该用哪个调度类?"
CFS 指了指最顶层:"pick_next_task() 按优先级从高到低检查每个调度类。先检查 stop,再检查 dl,再检查 rt,再检查我,最后检查 idle。第一个有可运行进程的调度类,它的选择就是最终结果。"
"那 stop 调度类是什么?"林小源望着最顶层那个模糊的身影。
"stop 是最高优先级的调度类,"CFS 的表情变得严肃,"它只用于 CPU hotplug 等特殊情况。stop 进程可以抢占任何其他进程——包括 Deadline 和 RT。它几乎不会出现,但一旦出现,就是绝对的权威。"
二
林小源跟着 CFS 走到阶梯旁边的一面石壁前。石壁上刻着调度类的接口函数,每一个函数都有详细的注释。
"这种设计叫多态,"CFS 说,"每种调度类都实现了相同的接口,但行为不同。调度器的核心代码不需要知道具体的调度策略——它只需要调用接口函数,调度类自己知道该怎么做。"
林小源伸手触摸石壁上的 函数。指尖传来一阵温热,他仿佛看到了函数内部的执行过程:一个循环,从 stop 开始,依次调用每个调度类的 pick_next_task 方法,直到找到一个有可运行进程的调度类。
"这种设计的好处是什么?"林小源问。
"扩展性,"CFS 说,"如果有人想添加新的调度策略——比如专门为机器学习任务设计的调度器——只需要实现一个新的调度类,注册到调度器中,不需要修改核心代码。调度器的核心代码从 Linux 2.6 以来几乎没变过,但调度类不断演进。"
林小源想起了 CFS 取代 O(1) 调度器的历史。O(1) 调度器的问题在于它的启发式判断不准确,而 CFS 用 vruntime 精确计算公平性。但调度器的核心框架——调度类的多态设计——一直沿用至今。
三
林小源坐在阶梯的最低层,和 idle 少年并肩而坐。
"你看起来很闲,"林小源说。
idle 少年睁开一只眼睛:"我就是闲。只有当所有其他调度类都没有可运行进程时,才会选到我。但你别小看我——没有我,CPU 会空转浪费电。"
林小源笑了笑,又转向 CFS:"pick_next_task() 的效率怎么样?它需要遍历所有进程吗?"
CFS 摇头:"不需要。pick_next_task() 只检查每个调度类的第一个可运行进程。由于调度类只有五个,这个操作是 O(1) 的。层次设计提高了效率——不需要遍历成千上万的进程,只需要检查五个调度类。"
林小源看着五层阶梯,心中感慨。调度类的设计就像一个金字塔——越往上越特殊,越往下越普通。stop 几乎不出现,Deadline 和 RT 服务于特殊场景,CFS 管理大多数进程,idle 守护着最后的底线。
"每种调度类都有自己的位置,"CFS 说,"没有高低之分,只有职责不同。"
/*
* Linux 调度类的层次(从高到低):
*
* stop_sched_class — 最高优先级
* dl_sched_class — Deadline 调度器
* rt_sched_class — 实时调度器
* fair_sched_class — CFS 调度器
* idle_sched_class — 最低优先级
*
* 每个调度类实现相同的接口:
* enqueue_task() — 把进程加入运行队列
* dequeue_task() — 把进程从运行队列移除
* pick_next_task() — 选择下一个运行的进程
* task_tick() — 时钟中断处理
* task_fork() — fork 时的处理
*
* 调度器的 pick_next_task():
* 按优先级检查每个调度类
* 返回第一个有可运行进程的调度类选择的进程
*/
struct sched_class {
const char *name;
int priority;
int (*pick_next)(void);
};
int pick_next_stop(void) { return -1; } /* 通常没有进程 */
int pick_next_dl(void) { return 300; } /* Deadline 进程 */
int pick_next_rt(void) { return 200; } /* RT 进程 */
int pick_next_fair(void) { return 100; } /* CFS 进程 */
int pick_next_idle(void) { return 0; } /* idle 进程 */
printf("=== 调度类 — 多态设计 ===\n\n");
struct sched_class classes[] = {
{ "stop", 0, pick_next_stop },
{ "dl", 1, pick_next_dl },
{ "rt", 2, pick_next_rt },
{ "fair", 3, pick_next_fair },
{ "idle", 4, pick_next_idle },
};
int nr = sizeof(classes) / sizeof(classes[0]);
printf("调度类层次:\n");
for (int i = 0; i < nr; i++) {
printf(" %s (优先级 %d)\n", classes[i].name, classes[i].priority);
}
printf("\n--- pick_next_task() 的逻辑 ---\n");
printf("按优先级检查每个调度类:\n\n");
for (int i = 0; i < nr; i++) {
int pid = classes[i].pick_next();
printf("检查 %s: ", classes[i].name);
if (pid > 0) {
printf("选择 PID %d\n", pid);
printf("→ 调度完成,返回 PID %d\n", pid);
break;
} else {
printf("没有可运行进程,继续检查\n");
}
}
printf("\n--- 调度类的设计哲学 ---\n");
printf("stop: 不可抢占,用于 CPU hotplug\n");
printf("dl: 时间保证,硬实时任务\n");
printf("rt: 确定性,软实时任务\n");
printf("fair: 公平,普通进程\n");
printf("idle: 空闲,没有其他进程时运行\n");#include <stdio.h>
#include <string.h>
/*
* Linux 调度类的层次(从高到低):
*
* stop_sched_class — 最高优先级
* dl_sched_class — Deadline 调度器
* rt_sched_class — 实时调度器
* fair_sched_class — CFS 调度器
* idle_sched_class — 最低优先级
*
* 每个调度类实现相同的接口:
* enqueue_task() — 把进程加入运行队列
* dequeue_task() — 把进程从运行队列移除
* pick_next_task() — 选择下一个运行的进程
* task_tick() — 时钟中断处理
* task_fork() — fork 时的处理
*
* 调度器的 pick_next_task():
* 按优先级检查每个调度类
* 返回第一个有可运行进程的调度类选择的进程
*/
struct sched_class {
const char *name;
int priority;
int (*pick_next)(void);
};
int pick_next_stop(void) { return -1; } /* 通常没有进程 */
int pick_next_dl(void) { return 300; } /* Deadline 进程 */
int pick_next_rt(void) { return 200; } /* RT 进程 */
int pick_next_fair(void) { return 100; } /* CFS 进程 */
int pick_next_idle(void) { return 0; } /* idle 进程 */
int main() {
printf("=== 调度类 — 多态设计 ===\n\n");
struct sched_class classes[] = {
{ "stop", 0, pick_next_stop },
{ "dl", 1, pick_next_dl },
{ "rt", 2, pick_next_rt },
{ "fair", 3, pick_next_fair },
{ "idle", 4, pick_next_idle },
};
int nr = sizeof(classes) / sizeof(classes[0]);
printf("调度类层次:\n");
for (int i = 0; i < nr; i++) {
printf(" %s (优先级 %d)\n", classes[i].name, classes[i].priority);
}
printf("\n--- pick_next_task() 的逻辑 ---\n");
printf("按优先级检查每个调度类:\n\n");
for (int i = 0; i < nr; i++) {
int pid = classes[i].pick_next();
printf("检查 %s: ", classes[i].name);
if (pid > 0) {
printf("选择 PID %d\n", pid);
printf("→ 调度完成,返回 PID %d\n", pid);
break;
} else {
printf("没有可运行进程,继续检查\n");
}
}
printf("\n--- 调度类的设计哲学 ---\n");
printf("stop: 不可抢占,用于 CPU hotplug\n");
printf("dl: 时间保证,硬实时任务\n");
printf("rt: 确定性,软实时任务\n");
printf("fair: 公平,普通进程\n");
printf("idle: 空闲,没有其他进程时运行\n");
return 0;
}道藏笔记
内核启示
调度类是 Linux 调度器的多态设计。
调度类的层次(从高到低):
- — 最高优先级,用于 CPU hotplug
- — Deadline 调度器
- — 实时调度器
- — CFS 调度器
- — 最低优先级
每个调度类实现相同的接口:
- — 把进程加入运行队列
- — 把进程从运行队列移除
- — 选择下一个运行的进程
- — 时钟中断处理
的逻辑:
- 按优先级检查每个调度类
- 返回第一个有可运行进程的调度类选择的进程
- 时间复杂度 O(1)
调度类让调度器可以"扩展"——添加新策略不需要修改核心代码。
调度类之试
多个调度类按优先顺序接力挑选任务时,最后落到哪个通用挑选入口?