3. Linux 调度器概述:CFS 调度器、实时调度器、调度类与调度策略

Linux 内核的调度子系统是操作系统的核心组件,负责决定哪个进程在何时、在哪个 CPU 上运行。为了满足从嵌入式实时系统到大型 NUMA 服务器的多样化需求,Linux 调度器采用了模块化的架构,通过 调度类 (Scheduling Classes) 和 调度策略 (Scheduling Policies) 实现了高度的灵活性与可扩展性。本章将深入剖析 CFS 调度器、实时调度器以及它们之间的协作关系。

3.1 调度类架构与层次

Linux 调度器并非一个单一的算法,而是一个可插拔的调度类框架。每个调度类拥有自己的优先级和调度逻辑,内核按照优先级从高到低的顺序遍历调度类,选择优先级最高的、且有就绪进程的调度类来执行。

核心原则: 高优先级的调度类总是优先于低优先级的调度类。这意味着实时进程 (RT) 永远优先于普通进程 (CFS) 被调度。

以下是内核中定义的调度类及其优先级顺序:

优先级 (高→低) 调度类 内核标识 主要用途
1 停机调度类 (Stop) stop_sched_class 用于强制停止 CPU 执行特殊任务,如热插拔、迁移线程
2 限期调度类 (Deadline) dl_sched_class 最严格的实时调度,保证进程在指定截止时间前完成
3 实时调度类 (RT) rt_sched_class 用于软实时和硬实时任务,如音频/视频处理
4 公平调度类 (CFS) fair_sched_class 普通分时进程,追求公平分配 CPU 时间
5 空闲调度类 (Idle) idle_sched_class 当 CPU 无其他任务可运行时,运行空闲进程 (pid=0)

下图展示了调度类在核心调度流程中的层次调用关系:

Linux 调度类层次架构图 调度类层次架构与核心调度入口 核心调度器 (schedule()) 遍历调度类链表 (优先级降序) Stop 类 Deadline 类 RT 类 CFS 类 选择优先级最高的就绪进程 上下文切换 (context_switch)

3.2 完全公平调度器 (CFS)

CFS 是 Linux 默认的调度类,用于处理普通分时进程。它的设计哲学是“理想、精确的多任务 CPU”,即为每个进程分配一个精确的 CPU 时间比例。

3.2.1 核心模型:虚拟运行时 (vruntime)

CFS 摒弃了传统的时间片和优先级队列,引入了 虚拟运行时 (vruntime) 的概念。vruntime 是进程实际运行时间经过优先级加权后的结果。CFS 总是选择 vruntime 最小的进程来运行,从而保证所有进程的 vruntime 尽可能接近,实现“完全公平”。

  • 权重 (weight):进程的 nice 值 (从 -20 到 19) 对应一个权重。nice 值越低,权重越高,进程的 vruntime 增长越慢,从而获得更多的 CPU 时间。
  • 计算公式:vruntime += 实际运行时间 * (NICE_0_LOAD / 进程权重)。其中 NICE_0_LOAD 是 nice 值为 0 时的基准权重。
  • 数据结构:CFS 使用 红黑树 (Red-Black Tree) 来组织就绪队列。树的键值就是 vruntime,最左侧的节点就是 vruntime 最小的进程,即下一个要运行的进程。
调优启示: 在 NUMA 环境下,CFS 的负载均衡算法会尝试将进程迁移到其内存所在的 NUMA 节点,以减少远程内存访问延迟。理解 vruntime 是理解 CFS 负载均衡行为的基础。

下图展示了 CFS 红黑树与进程选择的关系:

CFS 红黑树与 vruntime 选择 CFS 红黑树结构:vruntime 最小的进程被优先选择 vruntime: 200 vruntime: 100 vruntime: 300 vruntime: 50 vruntime: 150 下一个运行进程

3.3 实时调度器 (RT)

实时调度器用于处理对响应时间有严格要求的进程。它提供了两种调度策略:SCHED_FIFO 和 SCHED_RR。实时进程的优先级范围是 0 (最高) 到 99 (最低),始终高于 CFS 进程 (nice 值对应的优先级为 100~139)。

策略 内核标识 行为描述 适用场景
SCHED_FIFO SCHED_FIFO 先进先出。进程一旦获得 CPU,除非被更高优先级的进程抢占或主动让出 (如 sleep、yield),否则会一直运行到结束。 需要低延迟、短时间执行的硬实时任务
SCHED_RR SCHED_RR 时间片轮转。与 FIFO 类似,但相同优先级的 RR 进程会按时间片轮流执行,防止一个进程独占 CPU。 需要公平性的软实时任务,如多媒体处理
SCHED_DEADLINE SCHED_DEADLINE 基于最早截止时间优先 (EDF) 算法。进程必须指定运行周期、运行时间和截止时间,内核保证在截止时间前完成。 最严格的实时系统,如工业控制、自动驾驶
风险提示: 将大量 CPU 密集型进程设置为 SCHED_FIFO 或 SCHED_RR 可能导致系统响应变慢,甚至使 CFS 进程 (包括 shell、sshd) 完全得不到 CPU 时间,造成系统“假死”。在生产环境中调整实时策略需格外谨慎。

3.4 调度策略与进程类型映射

用户可以通过 sched_setscheduler() 系统调用或 chrt 命令为进程设置调度策略。内核根据策略将进程分配到对应的调度类中。

下表总结了调度策略与调度类的对应关系:

调度策略 所属调度类 优先级范围 典型进程
SCHED_NORMAL (又名 SCHED_OTHER) CFS 100~139 (nice -20~19) 用户应用程序、服务、脚本
SCHED_BATCH CFS 100~139 批处理任务,倾向于减少唤醒抢占
SCHED_IDLE CFS 非常低 (权重极小) 低优先级的后台任务
SCHED_FIFO RT 0~99 实时音频处理、中断处理线程
SCHED_RR RT 0~99 需要时间片轮转的实时任务
SCHED_DEADLINE Deadline 0~99 (内部管理) 高精度定时器、实时控制循环

3.5 调度类协作示例

假设一个系统中有以下进程:一个 SCHED_FIFO (优先级 50) 的音频线程,一个 SCHED_OTHER (nice 0) 的 Web 服务器进程,以及一个 SCHED_IDLE 的日志备份进程。

  1. 调度决策:核心调度器首先检查 Stop 和 Deadline 类,发现没有就绪进程。然后检查 RT 类,发现音频线程就绪,于是选择它运行。
  2. 音频线程阻塞:当音频线程等待声卡 DMA 完成而进入睡眠状态时,调度器再次遍历。RT 类无就绪进程,于是进入 CFS 类。
  3. CFS 选择:CFS 从红黑树中选择 vruntime 最小的 Web 服务器进程运行。
  4. 空闲时刻:当 Web 服务器也阻塞时,CFS 类无就绪进程,最终进入 Idle 类,运行空闲进程,直到下一个定时器中断或硬件中断唤醒其他进程。
关键理解: 调度类的优先级是绝对的。只要 RT 类中有就绪进程,CFS 类中的进程就完全没有机会运行。这种设计保证了实时性的硬约束。

通过本章的学习,您应该已经掌握了 Linux 调度器的模块化架构、CFS 的 vruntime 模型、实时调度策略以及它们之间的协作关系。这些知识是后续深入理解调度域拓扑和 NUMA 负载均衡参数的基础。