第三十六章:竞技场
结丹初期涉及内核源码:
一
林小源第一次踏入调度竞技场时,热浪扑面而来。
那不是火的热,而是无数进程同时运行所产生的"势"——密密麻麻的灵力流在半空中交织、碰撞、争夺着有限的 CPU 时间。竞技场的地面铺满了漆黑的晶体砖,每一块砖上都刻着一个进程的 PID 和 comm。而在竞技场的正中央,一棵巨大的红黑树拔地而起。
树的枝干一半漆黑如墨,一半殷红如血。每一个节点都悬浮着一个光团——那是 的灵体。节点的位置由它的 决定:左边的节点小,右边的节点大。树的最左侧,一个微微发亮的节点安静地悬在那里,像一颗等待被摘取的星辰。
"那就是下一个获得 CPU 的进程。"一个声音从林小源身后传来。
他回头,看到一个穿着灰色长袍的老者。老者的手中握着一根权杖,杖尖嵌着一颗不断旋转的沙漏——沙漏中的沙子流得很慢,但从未停止。
"我是 scheduler_tick(),"老者说,"每次时钟中断,我都会检查这棵树,更新当前进程的 ,然后判断是否需要切换。"
林小源指向树的最左侧:"那个节点……它就是 vruntime 最小的?"
"对。"老者点了点头,"在 CFS 的世界里,vruntime 最小的进程最先运行。这不是特权,是公平——每个进程都在这棵树上占据一个位置,谁的 vruntime 最小,谁就排在最前面。"
"那如果一个进程的 nice 值很低呢?它的 vruntime 增长会更慢,所以它会更频繁地出现在最左边?"
老者露出一丝笑意:"你理解得很快。来,我带你看看这个竞技场的运作。"
二
老者带着林小源走到红黑树的根部。近距离观察,林小源发现每个光团内部都有细小的数字在跳动——PID、comm、nice 值、权重,还有那个不断增长的 。
"注意看那边。"老者指向树的左侧区域。
两个相邻的节点正在缓慢地交换位置。一个标记为 (nice=0)的进程,它的 增长得比旁边标记为 (nice=10)的进程慢得多。每隔几息, 的 vruntime 就会超过 ,于是 被树的平衡力推向右边,而 则缓缓向左滑动。
"shell 的权重是 1024,"老者解释道,"batch 的权重只有 110。同样的实际运行时间,batch 的 vruntime 增长速度是 shell 的九倍多。"
林小源皱眉:"这不公平。"
"你觉得不公平?"老者反问,"shell 是交互式进程——用户在等它的响应。batch 是后台批处理任务——它可以在任何时候运行。如果你是调度器,你会怎么分配?"
林小源沉默了片刻。他想起了自己在炼气期时,师门分配修炼资源的情景——外门弟子分得少,内门弟子分得多,但到了秘境试炼时,外门弟子反而有优先权。
"我明白了,"林小源说,"公平不是平均分配。shell 需要低延迟,所以它的 vruntime 增长慢,让它更频繁地被选中。batch 不在乎延迟,所以它的 vruntime 增长得快,自然就少被选中。"
老者赞许地点头:"这就是 CFS 的'天道均衡'——不是绝对的平均,而是按权重分配。每个进程的 vruntime 应该趋于相等,但它们达到这个'相等'的速度不同。"
三
就在这时,一阵轻柔的风从红黑树的顶端吹下来。
林小源抬头,看到一个身影站在红黑树的根节点之上。她穿着素白的长裙,长发随风飘动,双眼清澈得像两汪深潭。她的手指轻轻搭在树干上,指尖泛着淡淡的银光——那是在计算每个节点的 vruntime。
"她是谁?"林小源压低声音问。
"调度仙子。"老者的语气中带着一丝敬畏,"CFS 调度器的化身。她每秒钟要做数千次调度决策——遍历红黑树,选择 vruntime 最小的进程,然后调用 完成上下文切换。"
调度仙子似乎感觉到了林小源的目光,微微侧头看了他一眼。那目光平静如水,没有温度,像一条 级别的内核日志——记录了,但不带任何情绪。
然后她收回目光,继续专注地审视着红黑树。她的嘴唇微动,似乎在默念着什么。林小源凑近了一些,听到了她的低语:
"……vruntime 3847,vruntime 3902,vruntime 3956……最左节点,PID 100,shell……"
她的手指轻轻一指,一道银光射向树的最左侧。那个光团亮了起来,开始缓缓上升——那是 的过程,shell 进程即将获得 CPU。
林小源屏住了呼吸。
调度仙子没有再看他。她太忙了——每一秒都有新的进程被唤醒,有旧的进程被阻塞,有进程的时间片用完,有进程的 vruntime 需要更新。红黑树在不断地旋转、调整、平衡,而她站在树的顶端,冷静地掌控着这一切。
"记住她,"老者在林小源耳边低声说,"在这座竞技场里,她就是法则。"
/*
* 调度器的核心任务:
* 从所有可运行的进程中选择一个,分配 CPU 时间。
*
* Linux 调度器的层次:
* stop_sched_class — 最高优先级,用于 CPU hotplug 等
* dl_sched_class — Deadline 调度器
* rt_sched_class — 实时调度器
* fair_sched_class — CFS(完全公平调度器)
* idle_sched_class — 最低优先级,idle 进程
*
* 调度的核心函数:schedule()
* 1. 选择下一个要运行的进程
* 2. 执行上下文切换
* 3. 新进程开始运行
*/
struct sched_entity {
int pid;
char comm[16];
unsigned long vruntime; /* 虚拟运行时间 */
int nice; /* 优先级 (-20 ~ 19) */
int weight; /* 权重(由 nice 计算) */
};
/* nice 值到权重的映射(简化) */
int nice_to_weight(int nice) {
/* 真实内核使用 sched_prio_to_weight[] 表 */
static int weights[] = {
88761, 71755, 56483, 46273, 36291, /* nice -20 ~ -16 */
29154, 23254, 18705, 14949, 11916, /* nice -15 ~ -11 */
9548, 7620, 6100, 4904, 3906, /* nice -10 ~ -6 */
3121, 2501, 1991, 1586, 1277, /* nice -5 ~ -1 */
1024, 820, 655, 526, 423, /* nice 0 ~ 4 */
335, 272, 215, 172, 137, /* nice 5 ~ 9 */
110, 87, 70, 56, 45, /* nice 10 ~ 14 */
36, 29, 23, 18, 15, /* nice 15 ~ 19 */
};
return weights[nice + 20];
}
printf("=== 调度竞技场 ===\n\n");
/* 几个进程在竞技场中竞争 */
struct sched_entity procs[] = {
{ .pid = 100, .comm = "shell", .vruntime = 1000, .nice = 0 },
{ .pid = 200, .comm = "compiler", .vruntime = 800, .nice = 5 },
{ .pid = 300, .comm = "vim", .vruntime = 1200, .nice = 0 },
{ .pid = 400, .comm = "batch", .vruntime = 600, .nice = 10 },
};
int nr = sizeof(procs) / sizeof(procs[0]);
/* 计算权重 */
for (int i = 0; i < nr; i++) {
procs[i].weight = nice_to_weight(procs[i].nice);
}
printf("竞技场中的进程:\n");
printf("%-6s %-12s %-10s %-6s %-10s\n",
"PID", "COMM", "vruntime", "nice", "weight");
printf("%-6s %-12s %-10s %-6s %-10s\n",
"---", "---", "---", "---", "---");
for (int i = 0; i < nr; i++) {
printf("%-6d %-12s %-10lu %-6d %-10d\n",
procs[i].pid, procs[i].comm,
procs[i].vruntime, procs[i].nice, procs[i].weight);
}
/* 选择 vruntime 最小的进程 */
int min_idx = 0;
for (int i = 1; i < nr; i++) {
if (procs[i].vruntime < procs[min_idx].vruntime)
min_idx = i;
}
printf("\n下一个获得 CPU 的进程:\n");
printf(" PID %d (%s), vruntime = %lu\n",
procs[min_idx].pid, procs[min_idx].comm,
procs[min_idx].vruntime);
printf("\n--- schedule() 的核心逻辑 ---\n");
printf("1. 调用 pick_next_task() 选择下一个进程\n");
printf("2. CFS: 选择 vruntime 最小的进程\n");
printf("3. 执行 context_switch() 切换到新进程\n");
printf("4. 新进程开始运行\n");#include <stdio.h>
#include <stdlib.h>
/*
* 调度器的核心任务:
* 从所有可运行的进程中选择一个,分配 CPU 时间。
*
* Linux 调度器的层次:
* stop_sched_class — 最高优先级,用于 CPU hotplug 等
* dl_sched_class — Deadline 调度器
* rt_sched_class — 实时调度器
* fair_sched_class — CFS(完全公平调度器)
* idle_sched_class — 最低优先级,idle 进程
*
* 调度的核心函数:schedule()
* 1. 选择下一个要运行的进程
* 2. 执行上下文切换
* 3. 新进程开始运行
*/
struct sched_entity {
int pid;
char comm[16];
unsigned long vruntime; /* 虚拟运行时间 */
int nice; /* 优先级 (-20 ~ 19) */
int weight; /* 权重(由 nice 计算) */
};
/* nice 值到权重的映射(简化) */
int nice_to_weight(int nice) {
/* 真实内核使用 sched_prio_to_weight[] 表 */
static int weights[] = {
88761, 71755, 56483, 46273, 36291, /* nice -20 ~ -16 */
29154, 23254, 18705, 14949, 11916, /* nice -15 ~ -11 */
9548, 7620, 6100, 4904, 3906, /* nice -10 ~ -6 */
3121, 2501, 1991, 1586, 1277, /* nice -5 ~ -1 */
1024, 820, 655, 526, 423, /* nice 0 ~ 4 */
335, 272, 215, 172, 137, /* nice 5 ~ 9 */
110, 87, 70, 56, 45, /* nice 10 ~ 14 */
36, 29, 23, 18, 15, /* nice 15 ~ 19 */
};
return weights[nice + 20];
}
int main() {
printf("=== 调度竞技场 ===\n\n");
/* 几个进程在竞技场中竞争 */
struct sched_entity procs[] = {
{ .pid = 100, .comm = "shell", .vruntime = 1000, .nice = 0 },
{ .pid = 200, .comm = "compiler", .vruntime = 800, .nice = 5 },
{ .pid = 300, .comm = "vim", .vruntime = 1200, .nice = 0 },
{ .pid = 400, .comm = "batch", .vruntime = 600, .nice = 10 },
};
int nr = sizeof(procs) / sizeof(procs[0]);
/* 计算权重 */
for (int i = 0; i < nr; i++) {
procs[i].weight = nice_to_weight(procs[i].nice);
}
printf("竞技场中的进程:\n");
printf("%-6s %-12s %-10s %-6s %-10s\n",
"PID", "COMM", "vruntime", "nice", "weight");
printf("%-6s %-12s %-10s %-6s %-10s\n",
"---", "---", "---", "---", "---");
for (int i = 0; i < nr; i++) {
printf("%-6d %-12s %-10lu %-6d %-10d\n",
procs[i].pid, procs[i].comm,
procs[i].vruntime, procs[i].nice, procs[i].weight);
}
/* 选择 vruntime 最小的进程 */
int min_idx = 0;
for (int i = 1; i < nr; i++) {
if (procs[i].vruntime < procs[min_idx].vruntime)
min_idx = i;
}
printf("\n下一个获得 CPU 的进程:\n");
printf(" PID %d (%s), vruntime = %lu\n",
procs[min_idx].pid, procs[min_idx].comm,
procs[min_idx].vruntime);
printf("\n--- schedule() 的核心逻辑 ---\n");
printf("1. 调用 pick_next_task() 选择下一个进程\n");
printf("2. CFS: 选择 vruntime 最小的进程\n");
printf("3. 执行 context_switch() 切换到新进程\n");
printf("4. 新进程开始运行\n");
return 0;
}道藏笔记
内核启示
是调度器的核心函数。
每次 CPU 需要切换进程时,都会调用 :
- — 选择下一个要运行的进程
- — 切换页表、栈、寄存器
- 新进程开始运行
Linux 调度器的层次(调度类):
- — 最高优先级,用于 CPU hotplug
- — Deadline 调度器
- — 实时调度器(FIFO/RR)
- — CFS(完全公平调度器)
- — idle 进程
CFS 的核心思想:
- 每个进程有一个 (虚拟运行时间)
- 最小的进程获得 CPU
- 的增长速度与进程权重成反比
- 高优先级进程的 增长更慢
调度器是 CPU 时间的分配者——它决定了谁先运行,谁等待。
竞技场之试
本章把调度器比作竞技场,最终真正决定下一位上场任务的核心函数是什么?