2. 调度类与调度策略:从内核视角理解任务分类
Linux 内核的调度器并非一个单一的调度算法,而是采用 调度类(Scheduling Class) 的层次化架构。每个调度类负责管理一组具有相同调度策略的任务,并按照优先级顺序被内核调用。理解这五个调度类及其对应的调度策略,是进行 CFS 参数调优的前提。
核心概念: 调度类决定了“谁先运行”,而调度策略决定了“如何运行”。内核遍历调度类时,优先级高的调度类中的可运行任务会优先获得 CPU。
2.1 调度类层次结构
内核按照严格的优先级顺序遍历调度类,从最高优先级到最低优先级依次为:stop_sched_class、dl_sched_class、rt_sched_class、fair_sched_class、idle_sched_class。只有当高优先级调度类中没有可运行的任务时,才会降级到下一个调度类。
关键理解: 只要
rt_sched_class 中有任务在运行,fair_sched_class(CFS)中的任务就无法获得 CPU 时间片。这也是为什么实时任务会“饿死”普通进程的原因。
2.2 调度类详解
| 调度类 | 优先级 | 核心用途 | 典型任务 |
|---|---|---|---|
| stop_sched_class | 最高 (0) | 强制停止 CPU 上的当前任务,用于内核内部关键操作 | CPU hotplug、stop_machine、kexec |
| dl_sched_class | 高 (1) | 实现 SCHED_DEADLINE 策略,提供最严格的实时保证 | 音视频编码、工业控制、硬实时任务 |
| rt_sched_class | 中 (2) | 实现 SCHED_FIFO / SCHED_RR 策略,软实时调度 | 中断处理线程、高优先级用户进程 |
| fair_sched_class | 低 (3) | 实现 CFS 调度器,公平分配 CPU 时间 | 普通用户进程、服务器应用、批处理任务 |
| idle_sched_class | 最低 (4) | CPU 空闲时运行 idle 线程,执行节能指令 | pid=0 的 idle 进程、halt/ mwait 指令 |
2.3 调度策略与调度类的映射关系
调度策略是用户空间通过 sched_setscheduler() 系统调用为进程设置的属性。内核根据策略将进程分配到对应的调度类中。并非所有策略都能映射到所有调度类。
2.4 CFS 相关的三种调度策略
虽然 SCHED_NORMAL、SCHED_BATCH 和 SCHED_IDLE 都归属于 fair_sched_class,但它们在 CFS 调度器内部的行为存在显著差异,直接影响 CPU 时间片的分配和抢占行为。
- SCHED_NORMAL (标准策略): 默认策略。CFS 根据
nice值计算权重,公平分配 CPU 时间。任务可以主动被更高优先级的任务抢占。 - SCHED_BATCH (批处理策略): 适用于 CPU 密集型、非交互式任务。CFS 会略微降低该任务的唤醒抢占优先级,减少上下文切换,提升缓存命中率。
- SCHED_IDLE (空闲策略): 优先级极低,仅在系统 CPU 空闲时才会获得时间片。即使设置了负的
nice值,其权重仍然低于任何SCHED_NORMAL任务。
| 策略 | 权重计算 | 唤醒抢占 | 典型场景 | 时间片分配 |
|---|---|---|---|---|
| SCHED_NORMAL | 基于 nice 值 (0~39) | 正常唤醒抢占 | Web 服务器、数据库、交互式应用 | 按权重公平分配 |
| SCHED_BATCH | 基于 nice 值 (0~39) | 延迟唤醒抢占 | 科学计算、视频转码、数据挖掘 | 按权重公平分配,但更倾向于连续运行 |
| SCHED_IDLE | 固定极低权重 | 几乎不抢占 | 后台垃圾回收、低优先级日志处理 | 仅当 CPU 空闲时分配 |
调优陷阱: 不要将交互式任务设置为
SCHED_BATCH,否则会导致明显的响应延迟。同样,将 CPU 密集型任务设置为 SCHED_IDLE 可能导致任务永远无法完成。
2.5 查看与设置调度策略
在实际调优过程中,我们经常需要查看或修改进程的调度策略。以下是一些常用命令和系统调用接口。
2.5.1 使用 chrt 命令
# 查看进程的调度策略和优先级
chrt -p 1234
# 将进程设置为 SCHED_BATCH 策略
chrt -b -p 0 1234
# 将进程设置为 SCHED_IDLE 策略
chrt -i -p 0 1234
2.5.2 使用 sched_setscheduler 系统调用
#include <sched.h>
#include <stdio.h>
int main() {
struct sched_param param;
param.sched_priority = 0; // SCHED_NORMAL/BATCH/IDLE 优先级必须为 0
// 设置当前进程为 SCHED_BATCH
if (sched_setscheduler(0, SCHED_BATCH, ¶m) == -1) {
perror("sched_setscheduler");
return 1;
}
printf("Successfully set to SCHED_BATCH\n");
return 0;
}
2.6 调度类与 CFS 调优的关系
理解调度类层次结构对于 CFS 参数调优至关重要:
- 优先级隔离: CFS 参数(如
sched_latency_ns、sched_min_granularity_ns)只影响fair_sched_class内的任务。实时任务(RT)和 Deadline 任务不受 CFS 参数影响。 - 饥饿风险: 如果系统中有大量 RT 或 Deadline 任务持续运行,CFS 任务可能长时间无法获得 CPU。此时需要调整 RT 任务的带宽限制(
/proc/sys/kernel/sched_rt_runtime_us)。 - 策略选择: 对于后台批处理任务,使用
SCHED_BATCH可以减少不必要的抢占,配合 CFS 的sched_wakeup_granularity_ns参数可以获得更好的吞吐量。
实践建议: 在开始 CFS 参数调优之前,先使用
ps -eo pid,policy,ni,comm 或 chrt -p <pid> 检查关键进程的调度策略。确保没有不必要的实时任务占用 CPU,导致 CFS 调优效果被掩盖。