3. Linux 调度器概述:CFS 调度器、实时调度器、调度类与调度策略
Linux 内核的调度子系统是操作系统的核心组件,负责决定哪个进程在何时、在哪个 CPU 上运行。为了满足从嵌入式实时系统到大型 NUMA 服务器的多样化需求,Linux 调度器采用了模块化的架构,通过 调度类 (Scheduling Classes) 和 调度策略 (Scheduling Policies) 实现了高度的灵活性与可扩展性。本章将深入剖析 CFS 调度器、实时调度器以及它们之间的协作关系。
3.1 调度类架构与层次
Linux 调度器并非一个单一的算法,而是一个可插拔的调度类框架。每个调度类拥有自己的优先级和调度逻辑,内核按照优先级从高到低的顺序遍历调度类,选择优先级最高的、且有就绪进程的调度类来执行。
以下是内核中定义的调度类及其优先级顺序:
| 优先级 (高→低) | 调度类 | 内核标识 | 主要用途 |
|---|---|---|---|
| 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) |
下图展示了调度类在核心调度流程中的层次调用关系:
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最小的进程,即下一个要运行的进程。
vruntime 是理解 CFS 负载均衡行为的基础。
下图展示了 CFS 红黑树与进程选择的关系:
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) 算法。进程必须指定运行周期、运行时间和截止时间,内核保证在截止时间前完成。 | 最严格的实时系统,如工业控制、自动驾驶 |
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 的日志备份进程。
- 调度决策:核心调度器首先检查 Stop 和 Deadline 类,发现没有就绪进程。然后检查 RT 类,发现音频线程就绪,于是选择它运行。
- 音频线程阻塞:当音频线程等待声卡 DMA 完成而进入睡眠状态时,调度器再次遍历。RT 类无就绪进程,于是进入 CFS 类。
- CFS 选择:CFS 从红黑树中选择
vruntime最小的 Web 服务器进程运行。 - 空闲时刻:当 Web 服务器也阻塞时,CFS 类无就绪进程,最终进入 Idle 类,运行空闲进程,直到下一个定时器中断或硬件中断唤醒其他进程。
通过本章的学习,您应该已经掌握了 Linux 调度器的模块化架构、CFS 的 vruntime 模型、实时调度策略以及它们之间的协作关系。这些知识是后续深入理解调度域拓扑和 NUMA 负载均衡参数的基础。