3. 调度类体系结构:stop_sched_class、dl_sched_class、rt_sched_class、fair_sched_class、idle_sched_class 的层次与优先级
Linux 内核的调度器并非一个单一的调度算法,而是采用了一种 可扩展的调度类(Scheduling Class) 架构。这种设计允许内核同时支持多种调度策略,每种策略由对应的调度类实现。调度类按照严格的优先级顺序组织成一个链表,内核在每次选择下一个运行的任务时,会从最高优先级的调度类开始遍历,直到找到一个有就绪任务的调度类为止。
这种层次化的架构是 Linux 调度器灵活性和实时性的基石。本章将深入剖析五个核心调度类的层次结构、优先级关系以及各自的职责。
3.1 调度类层次结构总览
Linux 内核定义了五个调度类,按照优先级从高到低排列如下:
- stop_sched_class:停止调度类(最高优先级)
- dl_sched_class:Deadline 调度类
- rt_sched_class:实时调度类
- fair_sched_class:完全公平调度类(CFS)
- idle_sched_class:空闲调度类(最低优先级)
内核通过 struct sched_class 结构体来抽象每个调度类,该结构体包含了一系列函数指针,如 enqueue_task、dequeue_task、pick_next_task 等。所有调度类的实例通过 next 指针链接成一个单向链表。
stop_sched_class 开始,依次调用每个调度类的 pick_next_task 方法。如果某个调度类返回了一个任务,则直接运行该任务,不再检查更低优先级的调度类。只有当所有高优先级调度类都没有就绪任务时,才会选择 idle_sched_class 中的空闲任务。
下面的 SVG 图展示了这五个调度类的层次结构与优先级关系:
3.2 各调度类详细解析
3.2.1 stop_sched_class(停止调度类)
优先级:最高(优先级 0)
职责:用于执行一些必须立即完成且不能被其他任务抢占的临界操作,例如 stop_machine 机制、CPU 热插拔、内核补丁(live patching)等。该调度类中的任务一旦被选中,将独占 CPU,直到任务主动完成。
- 特点:不可抢占,不可睡眠,执行时间极短。
- 典型场景:系统关机、CPU 上线/下线、ftrace 的某些跟踪点。
- 实现:通常只有一个 per-CPU 的停止任务。
stop_sched_class。该调度类仅供内核内部使用,滥用会导致系统锁死或严重性能问题。
3.2.2 dl_sched_class(Deadline 调度类)
优先级:第二高
职责:实现 SCHED_DEADLINE 策略,基于 最早截止时间优先(Earliest Deadline First, EDF) 算法,结合 常数带宽服务器(Constant Bandwidth Server, CBS) 机制,为实时任务提供确定性的调度保证。
每个 SCHED_DEADLINE 任务需要指定三个参数:
| 参数 | 含义 | 说明 |
|---|---|---|
| runtime | 运行时间 | 每个周期内任务需要执行的最大时间 |
| deadline | 截止时间 | 任务必须在该时间点之前完成 |
| period | 周期 | 任务释放的周期 |
调度规则:内核始终选择 deadline 最早的任务来运行。CBS 机制确保每个任务不会消耗超过其声明的 runtime,从而保护其他实时任务。
3.2.3 rt_sched_class(实时调度类)
优先级:第三高
职责:实现传统的 POSIX 实时调度策略:SCHED_FIFO 和 SCHED_RR。实时任务的优先级范围为 0(最高)到 99(最低)。
| 策略 | 行为 | 适用场景 |
|---|---|---|
| SCHED_FIFO | 先进先出,无时间片限制。高优先级任务会立即抢占低优先级任务。同一优先级的任务按队列顺序执行,直到主动让出 CPU 或阻塞。 | 需要极低延迟且执行时间可预测的实时任务 |
| SCHED_RR | 时间片轮转。与 SCHED_FIFO 类似,但同一优先级的任务会按时间片轮流执行。 | 需要公平性的软实时任务 |
sched_yield 或阻塞),可以无限期运行,这可能导致低优先级任务(包括 CFS 任务)饿死。SCHED_RR 通过时间片机制避免了同一优先级内的饿死问题,但无法解决不同优先级之间的饿死。
3.2.4 fair_sched_class(完全公平调度类)
优先级:第四高
职责:实现 完全公平调度器(Completely Fair Scheduler, CFS),这是 Linux 默认的调度类,用于普通进程(SCHED_NORMAL / SCHED_BATCH / SCHED_IDLE)。
CFS 的核心思想是模拟一个“理想的多任务处理器”,为每个任务分配一个 虚拟运行时间(vruntime)。调度器总是选择 vruntime 最小的任务来运行,从而保证所有任务获得公平的 CPU 时间份额。
- nice 值:影响任务的权重,范围 -20 到 +19。nice 值越低,权重越高,获得的 CPU 时间越多。
- 调度粒度:CFS 使用
targeted_latency和min_granularity来控制调度延迟和切换开销。 - 组调度:支持将任务分组,在组之间进行公平分配。
targeted_latency(通过 /proc/sys/kernel/sched_latency_ns)来降低调度延迟。对于批处理任务,可以增大 min_granularity 来减少上下文切换开销。
3.2.5 idle_sched_class(空闲调度类)
优先级:最低
职责:当所有其他调度类都没有就绪任务时,内核选择 idle_sched_class 中的 per-CPU 空闲任务(idle task) 来运行。空闲任务通常执行 halt 或 mwait 指令,使 CPU 进入低功耗状态,直到下一个中断到来。
- 特点:优先级最低,永远不会被主动调度,只有在没有其他任务可运行时才会被选中。
- 统计:空闲任务的运行时间通常被用作 CPU 空闲率的指标。
- 实现:每个 CPU 都有一个独立的空闲任务,其
task_struct在系统初始化时创建。
3.3 调度类优先级与任务选择流程
下面的 SVG 流程图展示了内核在 pick_next_task 函数中如何遍历调度类链表并选择下一个任务:
3.4 调度类优先级对比总结
下表总结了五个调度类的优先级、调度策略、典型用途和关键特性:
| 调度类 | 优先级 | 调度策略 | 典型用途 | 关键特性 |
|---|---|---|---|---|
| stop_sched_class | 最高 | 内核内部专用 | CPU 热插拔、stop_machine | 不可抢占,独占 CPU |
| dl_sched_class | 2 | SCHED_DEADLINE (EDF + CBS) | 硬实时任务、音视频处理 | 截止时间保证,带宽隔离 |
| rt_sched_class | 3 | SCHED_FIFO / SCHED_RR | 软实时任务、工业控制 | 静态优先级,可抢占 CFS |
| fair_sched_class | 4 | CFS (SCHED_NORMAL / BATCH / IDLE) | 普通用户进程、服务器负载 | 公平分配,基于 vruntime |
| idle_sched_class | 最低 | 空闲任务 | CPU 空闲时运行 | 最低优先级,低功耗 |
3.5 调度类与调度策略的映射关系
用户态通过 sched_setscheduler 系统调用为进程设置调度策略,内核将这些策略映射到对应的调度类:
- SCHED_NORMAL (SCHED_OTHER) →
fair_sched_class - SCHED_BATCH →
fair_sched_class(批处理风格,减少唤醒抢占) - SCHED_IDLE →
fair_sched_class(极低优先级,仅在 CPU 空闲时运行) - SCHED_FIFO →
rt_sched_class - SCHED_RR →
rt_sched_class - SCHED_DEADLINE →
dl_sched_class