4. 调度类与调度策略
Linux 内核的进程调度器采用模块化设计,通过 调度类 (Scheduling Class) 实现不同调度策略的抽象与分层。每个调度类负责管理一组具有相同调度行为的进程,内核根据进程的优先级和策略选择最合适的调度类来执行。本章将深入剖析五个核心调度类及其对应的调度策略。
4.1 调度类层次结构与优先级
Linux 内核定义了五个调度类,它们按照优先级从高到低的顺序组织成一个链表。调度器在每次选择下一个要运行的进程时,会从最高优先级的调度类开始遍历,选择该调度类中优先级最高的进程。
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 相同或更长。 |
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 进程之间会按时间片轮流执行。时间片耗尽后,进程被放到同优先级队列的尾部。
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_DEADLINE | dl_sched_class | EDF + CBS | -1 (由 Runtime/Deadline 决定) | 无(基于带宽) | 硬实时任务 |
| SCHED_FIFO | rt_sched_class | FIFO | 1 - 99 | 无 | 实时控制、中断处理 |
| SCHED_RR | rt_sched_class | Round Robin | 1 - 99 | 有(默认 100ms) | 需要时间分片的实时任务 |
| SCHED_NORMAL | fair_sched_class | CFS | 0 (nice -20 ~ 19) | 无(基于 vruntime) | 普通用户进程、交互式应用 |
| SCHED_BATCH | fair_sched_class | CFS (批处理优化) | 0 (nice -20 ~ 19) | 无(基于 vruntime) | 后台编译、科学计算 |
| SCHED_IDLE | fair_sched_class | CFS (低优先级) | 极低 | 无 | 低优先级后台任务 |
4.4 调度类与策略的关联机制
每个进程的 task_struct 结构体中包含一个 sched_class 指针,指向其所属的调度类。内核通过 sched_fork() 和 sched_setscheduler() 系统调用来设置进程的调度策略,并自动关联到正确的调度类。
4.5 性能调优实践
- 延迟敏感型应用: 使用
chrt命令或sched_setscheduler()系统调用,将关键线程设置为 SCHED_FIFO 并赋予合适的优先级(如 50-80)。避免将整个进程设置为实时,仅对关键路径上的线程进行设置。 - 硬实时约束应用: 使用 SCHED_DEADLINE 策略。通过
sched_setattr()系统调用设置 Runtime、Deadline 和 Period。需要内核版本 >= 3.14。 - CPU 密集型后台任务: 使用 SCHED_BATCH 策略。可以减少交互式进程的干扰,提升缓存命中率和整体吞吐量。
- 监控与验证: 使用
/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, ¶m) == -1) {
perror("sched_setscheduler failed");
return 1;
}
printf("Set to SCHED_FIFO with priority 80\n");
return 0;
}