4. 调度类与调度策略

Linux 内核的进程调度器采用模块化设计,通过 调度类 (Scheduling Class) 实现不同调度策略的抽象与分层。每个调度类负责管理一组具有相同调度行为的进程,内核根据进程的优先级和策略选择最合适的调度类来执行。本章将深入剖析五个核心调度类及其对应的调度策略。

核心概念: 调度类按照优先级从高到低排列,形成一个分层调度体系。高优先级调度类中的进程会优先获得 CPU 资源,只有当高优先级调度类中没有可运行进程时,才会选择低优先级调度类中的进程。

4.1 调度类层次结构与优先级

Linux 内核定义了五个调度类,它们按照优先级从高到低的顺序组织成一个链表。调度器在每次选择下一个要运行的进程时,会从最高优先级的调度类开始遍历,选择该调度类中优先级最高的进程。

Linux 内核调度类层次结构 调度类层次结构(优先级从高到低) stop_sched_class dl_sched_class rt_sched_class fair_sched_class idle_sched_class 调度选择顺序 从 stop 开始遍历 选择第一个有就绪进程的类 idle 类始终存在一个就绪进程

4.2 调度类详解

4.2.1 stop_sched_class

优先级:最高。该调度类用于执行特殊的 迁移/停止 (migration/stop) 内核线程。这些线程负责在 CPU 之间迁移进程、执行 CPU 热插拔操作或处理紧急的调度事件。普通用户进程无法通过任何系统调用设置为此调度类。

  • 特性: 抢占所有其他调度类,不可被抢占(除非被另一个 stop 线程)。
  • 典型用途: migration/N 线程(N 为 CPU 编号)、stop/N 线程。
  • 性能影响: 执行时间极短,对系统整体延迟影响很小,但保证了跨 CPU 负载均衡的实时性。

4.2.2 dl_sched_class (Deadline)

优先级:第二高。实现 SCHED_DEADLINE 策略,基于 最早截止时间优先 (Earliest Deadline First, EDF) 算法,并辅以 常量带宽服务器 (Constant Bandwidth Server, CBS) 机制来保证调度确定性。

参数含义说明
Runtime运行时间在一个周期内,进程保证获得的最大 CPU 执行时间。
Deadline截止时间进程必须在此时间点之前完成一次运行。
Period周期进程的激活周期,通常与 Deadline 相同或更长。
适用场景: 硬实时和软实时任务,例如音视频编码/解码、工业控制、机器人操作系统 (ROS2) 等对延迟有严格上限要求的应用。

4.2.3 rt_sched_class (Real-Time)

优先级:第三高。实现 SCHED_FIFO 和 SCHED_RR 两种实时策略,基于静态优先级(范围 1-99,数值越大优先级越高)。

  • SCHED_FIFO (First In, First Out): 先进先出调度。一旦一个 FIFO 进程获得 CPU,它会一直运行直到主动放弃(如睡眠、等待 I/O)或被更高优先级的实时进程抢占。没有时间片概念。
  • SCHED_RR (Round Robin): 时间片轮转调度。与 FIFO 类似,但相同优先级的 RR 进程之间会按时间片轮流执行。时间片耗尽后,进程被放到同优先级队列的尾部。
注意事项: 实时进程(SCHED_FIFO/SCHED_RR)如果编写不当(例如死循环),可能导致系统卡死,因为其优先级高于普通进程。建议为实时进程设置合理的优先级上限,并确保其运行时间可控。

4.2.4 fair_sched_class (CFS)

优先级:第四高。实现 SCHED_NORMAL (也称为 SCHED_OTHER) 和 SCHED_BATCH 策略,基于 完全公平调度器 (Completely Fair Scheduler, CFS) 算法。

  • SCHED_NORMAL: 默认的普通分时调度策略。CFS 使用红黑树维护进程的 虚拟运行时间 (vruntime),每次选择 vruntime 最小的进程运行,确保所有进程获得公平的 CPU 时间份额。
  • SCHED_BATCH: 批处理调度策略。适用于非交互、CPU 密集型后台任务。CFS 会略微降低其唤醒优先级,减少上下文切换,提升整体吞吐量。
  • SCHED_IDLE: 极低优先级策略。只有系统中没有任何其他可运行进程时,此类进程才会获得 CPU。

4.2.5 idle_sched_class

优先级:最低。每个 CPU 核心都有一个 idle 线程,当没有其他进程需要运行时,调度器会选择 idle 线程。Idle 线程通常执行 HLT 指令让 CPU 进入低功耗状态,直到下一个定时器中断唤醒。

  • 特性: 始终存在一个可运行的 idle 进程,保证调度器总能找到一个“进程”来执行。
  • 性能指标: 系统空闲时间实际上就是 idle 进程的运行时间,是衡量 CPU 利用率的重要指标。

4.3 调度策略对比与选择

调度策略所属调度类调度算法优先级范围时间片典型应用
SCHED_DEADLINEdl_sched_classEDF + CBS-1 (由 Runtime/Deadline 决定)无(基于带宽)硬实时任务
SCHED_FIFOrt_sched_classFIFO1 - 99无实时控制、中断处理
SCHED_RRrt_sched_classRound Robin1 - 99有(默认 100ms)需要时间分片的实时任务
SCHED_NORMALfair_sched_classCFS0 (nice -20 ~ 19)无(基于 vruntime)普通用户进程、交互式应用
SCHED_BATCHfair_sched_classCFS (批处理优化)0 (nice -20 ~ 19)无(基于 vruntime)后台编译、科学计算
SCHED_IDLEfair_sched_classCFS (低优先级)极低无低优先级后台任务

4.4 调度类与策略的关联机制

每个进程的 task_struct 结构体中包含一个 sched_class 指针,指向其所属的调度类。内核通过 sched_fork() 和 sched_setscheduler() 系统调用来设置进程的调度策略,并自动关联到正确的调度类。

进程调度策略与调度类关联 进程调度策略与调度类关联机制 进程 task_struct policy: SCHED_NORMAL 策略选择逻辑 switch(policy) fair_sched_class sched_class 指针赋值 策略到调度类的映射关系 SCHED_NORMAL / SCHED_BATCH / SCHED_IDLE → fair_sched_class SCHED_FIFO / SCHED_RR → rt_sched_class SCHED_DEADLINE → dl_sched_class

4.5 性能调优实践

调优原则: 选择调度策略时,应遵循“够用就好”的原则。对于绝大多数应用,默认的 SCHED_NORMAL 已经足够。仅在明确需要低延迟或确定性调度时,才考虑使用实时或 Deadline 策略。
  1. 延迟敏感型应用: 使用 chrt 命令或 sched_setscheduler() 系统调用,将关键线程设置为 SCHED_FIFO 并赋予合适的优先级(如 50-80)。避免将整个进程设置为实时,仅对关键路径上的线程进行设置。
  2. 硬实时约束应用: 使用 SCHED_DEADLINE 策略。通过 sched_setattr() 系统调用设置 Runtime、Deadline 和 Period。需要内核版本 >= 3.14。
  3. CPU 密集型后台任务: 使用 SCHED_BATCH 策略。可以减少交互式进程的干扰,提升缓存命中率和整体吞吐量。
  4. 监控与验证: 使用 /proc/[pid]/sched 和 perf sched 工具观察进程的调度延迟和上下文切换次数,验证调优效果。
# 示例:将 PID 为 1234 的线程设置为 SCHED_FIFO,优先级 80
chrt -f -p 80 1234

# 示例:使用 sched_setscheduler() 系统调用(C 代码片段)
#include <sched.h>
#include <stdio.h>

int main() {
    struct sched_param param;
    param.sched_priority = 80;
    if (sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
        perror("sched_setscheduler failed");
        return 1;
    }
    printf("Set to SCHED_FIFO with priority 80\n");
    return 0;
}
风险提示: 将系统关键进程(如 sshd、systemd)设置为实时策略可能导致系统不稳定。建议在开发环境中充分测试,并设置实时进程的 CPU 亲和性,避免其独占所有 CPU 核心。