2. 调度类与调度策略:从内核视角理解任务分类

Linux 内核的调度器并非一个单一的调度算法,而是采用 调度类(Scheduling Class) 的层次化架构。每个调度类负责管理一组具有相同调度策略的任务,并按照优先级顺序被内核调用。理解这五个调度类及其对应的调度策略,是进行 CFS 参数调优的前提。

核心概念: 调度类决定了“谁先运行”,而调度策略决定了“如何运行”。内核遍历调度类时,优先级高的调度类中的可运行任务会优先获得 CPU。

2.1 调度类层次结构

内核按照严格的优先级顺序遍历调度类,从最高优先级到最低优先级依次为:stop_sched_class、dl_sched_class、rt_sched_class、fair_sched_class、idle_sched_class。只有当高优先级调度类中没有可运行的任务时,才会降级到下一个调度类。

Linux 调度类层次结构 调度类优先级层次结构 stop_sched_class (最高优先级) dl_sched_class (Deadline) rt_sched_class (Real-Time) fair_sched_class (CFS) 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() 系统调用为进程设置的属性。内核根据策略将进程分配到对应的调度类中。并非所有策略都能映射到所有调度类。

调度策略与调度类映射 调度策略 → 调度类 映射关系 SCHED_NORMAL SCHED_BATCH SCHED_IDLE SCHED_FIFO / SCHED_RR SCHED_DEADLINE fair_sched_class (CFS) fair_sched_class (CFS) fair_sched_class (CFS) rt_sched_class dl_sched_class 映射规则 SCHED_NORMAL/BATCH/IDLE 全部映射到 fair_sched_class 但内部行为不同

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, &param) == -1) {
        perror("sched_setscheduler");
        return 1;
    }
    printf("Successfully set to SCHED_BATCH\n");
    return 0;
}

2.6 调度类与 CFS 调优的关系

理解调度类层次结构对于 CFS 参数调优至关重要:

  1. 优先级隔离: CFS 参数(如 sched_latency_ns、sched_min_granularity_ns)只影响 fair_sched_class 内的任务。实时任务(RT)和 Deadline 任务不受 CFS 参数影响。
  2. 饥饿风险: 如果系统中有大量 RT 或 Deadline 任务持续运行,CFS 任务可能长时间无法获得 CPU。此时需要调整 RT 任务的带宽限制(/proc/sys/kernel/sched_rt_runtime_us)。
  3. 策略选择: 对于后台批处理任务,使用 SCHED_BATCH 可以减少不必要的抢占,配合 CFS 的 sched_wakeup_granularity_ns 参数可以获得更好的吞吐量。
实践建议: 在开始 CFS 参数调优之前,先使用 ps -eo pid,policy,ni,comm 或 chrt -p <pid> 检查关键进程的调度策略。确保没有不必要的实时任务占用 CPU,导致 CFS 调优效果被掩盖。