Skip to content

第四十五章:RT 调度

结丹中期

涉及内核源码:

林小源在调度竞技场中,第一次看到了"特权阶层"。

那是一群穿着金色铠甲的进程,它们不走红黑树的通道,而是从一条独立的、更加宽阔的道路上走来。道路的尽头是一座高台——高台上有一排石座,每个石座上都刻着一个数字,从 1 到 99。

"RT 进程。"调度仙子站在林小源身旁,声音平静但带着一丝凝重,"实时进程。它们不参与 CFS 的公平竞争。它们有自己独立的运行队列,有自己独立的调度策略,有更高的优先级。"

林小源看着那些金色铠甲的进程:"它们的优先级比普通进程高?"

"不是'高'——是'绝对'。"调度仙子说,"只要有一个 RT 进程可运行,CFS 进程就得不到 CPU。这是调度类的优先级顺序:stop > dl > rt > fair > idle。"

一个穿着金色铠甲的进程走到高台前,向调度仙子行了一礼。林小源注意到它的铠甲上刻着两个字:FIFO。

"这是 SCHED_FIFO 进程,"调度仙子说,"先进先出。它会一直运行,直到主动让出 CPU。没有时间片限制。"

另一个穿着金色铠甲的进程走了过来,它的铠甲上刻着 RR。

"这是 SCHED_RR 进程,"调度仙子说,"轮转。同优先级的 RR 进程轮流运行,每个进程有固定的时间片——通常是 10ms。时间片用完后,进程会被移到同优先级队列的末尾。"

林小源在高台前观察了很久,发现了一个问题。

"如果一个 RT 进程陷入死循环,"他说,"它会独占 CPU?"

调度仙子点头:"对。因为 RT 优先级总是高于普通进程——只要 RT 进程可运行,普通进程就永远得不到 CPU。如果 RT 进程死循环,普通进程会被'饿死',系统变得无响应。"

"这不是一个严重的问题吗?"

"是。"调度仙子说,"所以内核有 RT 带宽限制——。默认情况下,RT 进程在每个周期内最多使用 950ms 的 CPU 时间,留 50ms 给普通进程。"

"950ms?"林小源皱眉,"那还是很多。"

"是的。"调度仙子说,"但这是为了保证实时任务的确定性。实时任务需要可预测的响应时间——如果内核频繁地抢占 RT 进程,实时任务的延迟就不可预测了。"

"所以这是'确定性' vs '公平'的权衡。"

"对。"调度仙子说,"CFS 追求'公平'——每个进程按权重获得 CPU 时间。RT 追求'确定性'——RT 进程的响应时间是可预测的。这是两种不同的价值。"

林小源在高台前站了很久,看着那些穿着金色铠甲的进程。

"创建 RT 进程需要特权吗?"他问。

调度仙子点头:"需要 能力。普通用户不能创建 RT 进程——否则恶意进程可以创建一个 RT 进程,独占 CPU,让整个系统无响应。"

"所以'特权'也是一种'责任'。"

"对。"调度仙子说,"RT 进程的特权是'绝对优先'——它们可以抢占任何普通进程。但这个特权的代价是'责任'——RT 进程必须正确地管理自己的行为,不能死循环,不能独占 CPU。"

林小源想起了自己在内核中看到的很多设计:特权总是伴随着责任,自由总是伴随着代价。nice 值可以自由调整,但只能降低自己;亲和性可以限制迁移范围,但会降低灵活性;RT 进程可以获得绝对优先,但需要 能力。

"还有两种 RT 策略的区别,"调度仙子补充道,"FIFO 进程会一直运行直到主动让出——适合那些知道什么时候该停的实时任务。RR 进程有时间片轮转——适合那些需要公平分享 CPU 的实时任务。"

"音频解码器用 FIFO?"

"对。"调度仙子说,"音频解码器需要在固定的时间内完成解码——它知道什么时候该停。而传感器读取器用 RR——多个传感器需要轮流读取数据。"

林小源点了点头。他终于理解了 RT 调度的本质:它不是"更好"的调度,而是"不同"的调度。CFS 和 RT 是两种不同的价值取向——公平 vs 确定性。内核的设计者没有选择其中之一,而是让两者共存,各司其职。


c
/*
 * RT 调度器的两种策略:
 *
 * SCHED_FIFO (先进先出):
 *   - 进程运行直到主动让出 CPU
 *   - 没有时间片限制
 *   - 同优先级的 FIFO 进程按先来后到调度
 *
 * SCHED_RR (轮转):
 *   - 同优先级的进程轮流运行
 *   - 每个进程有固定的时间片
 *   - 时间片用完后,移到同优先级队列的末尾
 *
 * RT 优先级:1 ~ 99
 * 值越大优先级越高
 * RT 进程总是优先于普通进程(CFS)
 */

struct rt_entity {
    int pid;
    char comm[16];
    int rt_priority;     /* 1 ~ 99 */
    int policy;          /* FIFO 或 RR */
    int time_slice;      /* 时间片 (ms) */
    int remaining;       /* 剩余时间片 */
};

#define SCHED_FIFO 1
#define SCHED_RR   2

printf("=== RT 调度器 — 特权阶层 ===\n\n");

/* RT 进程 */
struct rt_entity rt_procs[] = {
    { 100, "audio-daemon",  80, SCHED_FIFO, 0, 0 },
    { 200, "video-decoder", 70, SCHED_RR,  10, 10 },
    { 300, "sensor-reader", 60, SCHED_RR,  10, 10 },
    { 400, "logger",        50, SCHED_FIFO, 0, 0 },
};
int nr = sizeof(rt_procs) / sizeof(rt_procs[0]);

printf("RT 进程列表:\n");
printf("%-6s %-16s %-10s %-10s %-10s\n",
       "PID", "COMM", "RT_PRIO", "策略", "时间片");
printf("%-6s %-16s %-10s %-10s %-10s\n",
       "---", "---", "---", "---", "---");

for (int i = 0; i < nr; i++) {
    printf("%-6d %-16s %-10d %-10s %-10s\n",
           rt_procs[i].pid, rt_procs[i].comm,
           rt_procs[i].rt_priority,
           rt_procs[i].policy == SCHED_FIFO ? "FIFO" : "RR",
           rt_procs[i].policy == SCHED_FIFO ? "无限" : "10ms");
}

printf("\n--- RT 调度的规则 ---\n");
printf("1. RT 进程总是优先于普通进程\n");
printf("2. 高 RT_PRIO 的进程优先于低 RT_PRIO\n");
printf("3. 同优先级:\n");
printf("   FIFO: 先来后到,运行直到主动让出\n");
printf("   RR:   轮流运行,时间片 10ms\n\n");

printf("--- RT 调度的风险 ---\n");
printf("RT 进程可以\"饿死\"普通进程:\n");
printf("  如果一个 RT 进程死循环,普通进程永远得不到 CPU\n");
printf("  解决方案:RT 带宽限制(rt_runtime_us / rt_period_us)\n\n");
printf("RT 进程的创建需要特权:\n");
printf("  普通用户不能创建 RT 进程\n");
printf("  需要 CAP_SYS_NICE 能力\n");

道藏笔记

内核启示

RT 调度器为实时进程提供确定性的调度。

RT 调度器的两种策略:

  1. SCHED_FIFO — 先进先出,进程运行直到主动让出
  2. SCHED_RR — 轮转,同优先级进程轮流运行

RT 优先级:1 ~ 99,值越大优先级越高

RT 调度器的特性:

  • RT 进程总是优先于普通进程(CFS)
  • 高 RT_PRIO 的进程优先于低 RT_PRIO
  • SCHED_FIFO 没有时间片限制
  • SCHED_RR 有固定的时间片

RT 调度的风险:

  • RT 进程可以"饿死"普通进程
  • RT 带宽限制( /
  • 创建 RT 进程需要 能力

RT 调度器追求"确定性",CFS 追求"公平"——两种不同的价值。


破关试炼

RT 调度之试

普通进程不能随便进入 RT 调度,本章指出设置实时优先级需要哪项能力?

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

以修仙之名,悟内核之道