Skip to content

第三十六章:竞技场

结丹初期

涉及内核源码:

林小源第一次踏入调度竞技场时,热浪扑面而来。

那不是火的热,而是无数进程同时运行所产生的"势"——密密麻麻的灵力流在半空中交织、碰撞、争夺着有限的 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 需要更新。红黑树在不断地旋转、调整、平衡,而她站在树的顶端,冷静地掌控着这一切。

"记住她,"老者在林小源耳边低声说,"在这座竞技场里,她就是法则。"


c
/*
 * 调度器的核心任务:
 * 从所有可运行的进程中选择一个,分配 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");

道藏笔记

内核启示

是调度器的核心函数。

每次 CPU 需要切换进程时,都会调用

  1. — 选择下一个要运行的进程
  2. — 切换页表、栈、寄存器
  3. 新进程开始运行

Linux 调度器的层次(调度类):

  • — 最高优先级,用于 CPU hotplug
  • — Deadline 调度器
  • — 实时调度器(FIFO/RR)
  • — CFS(完全公平调度器)
  • — idle 进程

CFS 的核心思想:

  • 每个进程有一个 (虚拟运行时间)
  • 最小的进程获得 CPU
  • 的增长速度与进程权重成反比
  • 高优先级进程的 增长更慢

调度器是 CPU 时间的分配者——它决定了谁先运行,谁等待。


破关试炼

竞技场之试

本章把调度器比作竞技场,最终真正决定下一位上场任务的核心函数是什么?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道