一文读懂 Linux 调度策略:7 种策略映射关系与适用场景全梳理
一、简介调度策略为何是 Linux 性能的核心杠杆Linux 内核调度器是操作系统最核心的子系统之一直接决定进程何时获得 CPU 时间、获得多长时间。在多核处理器普及、异构计算兴起的今天调度策略的选择已成为系统性能调优的首要杠杆云计算场景容器密度提升 30% 往往源于 CFS 参数的精细化调优实时控制场景机械臂控制周期从 10ms 抖动降至 100μs 确定性依赖 SCHED_FIFO 的正确配置桌面交互场景Chrome 多标签流畅度与 CFS 的 vruntime 计算密切相关然而生产环境中策略滥用现象普遍将普通计算任务绑定 SCHED_FIFO 导致系统 starvation或对实时任务使用 CFS 造成不可接受的延迟抖动。本文从源码机制、实验验证、场景映射三个维度建立完整的调度策略知识体系。二、核心概念调度类与策略的层次架构2.1 调度类Scheduling Class——策略的容器Linux 内核采用模块化调度类设计通过sched_class结构体实现策略扩展调度类源码文件管理策略调度优先级stop_sched_classsched.c停止任务CPU 热插拔最高-1dl_sched_classsched/deadline.cSCHED_DEADLINE0rt_sched_classsched/rt.cSCHED_FIFO, SCHED_RR1-99fair_sched_classsched/fair.cSCHED_NORMAL, SCHED_BATCH, SCHED_IDLE100-139idle_sched_classsched/idle.c空闲任务最低关键洞察调度类优先级固定高优先级类存在可运行任务时低优先级类完全无法获得 CPU。2.2 七种调度策略详解// include/uapi/linux/sched.h #define SCHED_NORMAL 0 /* CFS 默认策略 */ #define SCHED_FIFO 1 /* 实时 FIFO */ #define SCHED_RR 2 /* 实时轮询 */ #define SCHED_BATCH 3 /* CFS 批处理变体 */ #define SCHED_IDLE 5 /* CFS 空闲任务 */ #define SCHED_DEADLINE 6 /* 截止时间调度 */ #define SCHED_EXT 7 /* 可扩展调度BPF6.12*/策略调度类优先级范围核心特性典型误用SCHED_NORMALfairnice: -20~19公平分享、交互奖励实时任务误用SCHED_FIFOrt1-99抢占式、无时间片长期占用 CPUSCHED_RRrt1-99抢占式、RR 时间片同优先级任务过多SCHED_BATCHfairnice: -20~19减少抢占、提高吞吐交互任务误用SCHED_IDLEfair始终最低仅空闲时运行期望及时响应SCHED_DEADLINEdl动态计算带宽隔离、截止时间保证参数配置错误SCHED_EXTextBPF 程序定义用户态可编程调度内核版本不支持2.3 关键术语表术语定义源码关联vruntime虚拟运行时间CFS 公平度量struct sched_entity::vruntimert_runtime_us实时任务每周期最大运行时间/proc/sys/kernel/sched_rt_runtime_usdl_bwDeadline 任务带宽占比struct dl_bw::bwsched_domain调度域多核负载均衡单元struct sched_domainload_balance跨 CPU 负载均衡算法kernel/sched/fair.c: load_balance()三、环境准备可复现实验平台搭建3.1 硬件环境组件规格用途CPUx86_64≥4 核支持 TSC多核调度实验内存≥8 GB大负载测试存储SSD快速编译内核3.2 软件环境组件版本安装命令Ubuntu Server22.04 LTS基础系统Linux 内核6.6 LTS含 sched_ext源码编译perf6.6apt install linux-tools-commonbpftrace0.19apt install bpftracestress-ng0.15apt install stress-ng3.3 内核编译与 sched_ext 启用#!/bin/bash # setup_kernel.sh - 一键搭建实验环境 set -e KERNEL_VERSION6.6.21 wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-${KERNEL_VERSION}.tar.xz tar -xf linux-${KERNEL_VERSION}.tar.xz cd linux-${KERNEL_VERSION} # 配置启用 SCHED_DEADLINE 调试与 sched_ext make defconfig ./scripts/config --enable CONFIG_SCHED_DEBUG ./scripts/config --enable CONFIG_SCHEDSTATS ./scripts/config --enable CONFIG_SCHED_CORE ./scripts/config --enable CONFIG_SCHED_CLASS_EXT ./scripts/config --enable CONFIG_DEBUG_PREEMPT make -j$(nproc) sudo make modules_install sudo make install echo 内核安装完成重启后选择新内核3.4 验证环境# 验证调度类存在 cat /proc/sys/kernel/sched_schedstats # 应输出 1 # 验证 sched_ext 可用6.12 ls /sys/kernel/sched_ext/ # 应存在 # 安装测试工具 sudo apt install rt-tests sysstat四、应用场景云原生数据库的混合负载调度在云原生分布式数据库如 TiDB、CockroachDB的生产环境中典型负载构成OLTP 事务处理短查询延迟敏感需 SCHED_NORMAL 高 nice 优先级OLAP 分析查询长扫描吞吐优先适用 SCHED_BATCH 减少缓存污染Raft 共识协议心跳超时严格关键路径使用 SCHED_FIFO 保证 10ms 内响应compaction 后台任务磁盘 IO 密集型SCHED_IDLE 避免影响前台调度策略误用案例某金融客户将 compaction 设为 SCHED_FIFO 优先级 50导致 Raft 心跳任务同策略优先级 60在 compaction 运行时被饿死引发频繁 leader 选举P99 延迟从 5ms 恶化至 2s。修复方案compaction 降级为 SCHED_IDLERaft 保持 SCHED_FIFO 优先级 80。五、实际案例与步骤七策略全量实验5.1 实验一CFS 三策略对比NORMAL/BATCH/IDLE目标量化 SCHED_BATCH 的吞吐优势与 SCHED_IDLE 的延迟代价。代码策略设置与负载生成// cfs_compare.c #define _GNU_SOURCE #include sched.h #include stdio.h #include stdlib.h #include unistd.h #include sys/time.h #include sys/resource.h // 设置 CFS 策略与 nice 值 int set_cfs_policy(int policy, int nice_val) { struct sched_param param {0}; // 验证策略有效性 if (policy ! SCHED_NORMAL policy ! SCHED_BATCH policy ! SCHED_IDLE) { fprintf(stderr, Invalid CFS policy: %d\n, policy); return -1; } // 设置调度策略 if (sched_setscheduler(0, policy, param) -1) { perror(sched_setscheduler); return -1; } // 设置 nice 值对 SCHED_IDLE 无效 if (policy ! SCHED_IDLE setpriority(PRIO_PROCESS, 0, nice_val) -1) { perror(setpriority); return -1; } printf(Set policy%d, nice%d (pid%d)\n, policy, nice_val, getpid()); return 0; } // CPU 密集型任务计算素数 void cpu_intensive_task(int iterations) { struct timeval start, end; gettimeofday(start, NULL); long long count 0; for (int i 2; i iterations; i) { int is_prime 1; for (int j 2; j * j i; j) { if (i % j 0) { is_prime 0; break; } } if (is_prime) count; } gettimeofday(end, NULL); double elapsed (end.tv_sec - start.tv_sec) * 1000.0 (end.tv_usec - start.tv_usec) / 1000.0; printf(Completed: %lld primes in %.2f ms\n, count, elapsed); } int main(int argc, char *argv[]) { if (argc ! 4) { fprintf(stderr, Usage: %s policy nice iterations\n, argv[0]); fprintf(stderr, Policy: 0NORMAL, 3BATCH, 5IDLE\n); return 1; } int policy atoi(argv[1]); int nice_val atoi(argv[2]); int iterations atoi(argv[3]); if (set_cfs_policy(policy, nice_val) ! 0) return 1; cpu_intensive_task(iterations); return 0; }编译与运行gcc -O2 -o cfs_compare cfs_compare.c # 三策略并行对比iterations500000 echo SCHED_NORMAL, nice0 taskset -c 0 ./cfs_compare 0 0 500000 echo SCHED_BATCH, nice0 taskset -c 0 ./cfs_compare 3 0 500000 echo SCHED_IDLE taskset -c 0 ./cfs_compare 5 0 500000 wait预期结果与分析策略典型完成时间原因SCHED_NORMAL基准标准公平调度SCHED_BATCH-10%~15%减少抢占提高缓存局部性SCHED_IDLE5x~10x仅空闲时运行被严重饿死监控命令# 实时观察调度延迟 watch -n 0.5 cat /proc/$(pgrep cfs_compare)/sched | grep -E se.vruntime|nr_migrations # 统计上下文切换 perf stat -e context-switches,cs -p $(pgrep cfs_compare) sleep 55.2 实验二实时策略优先级反转观测目标复现优先级反转现象验证 SCHED_FIFO 的抢占行为。代码三任务优先级模型// priority_inversion.c #define _GNU_SOURCE #include pthread.h #include stdio.h #include stdlib.h #include unistd.h #include sched.h #include time.h #define HIGH_PRIO 80 #define MID_PRIO 50 #define LOW_PRIO 20 static pthread_mutex_t lock PTHREAD_MUTEX_INITIALIZER; static volatile int shared_resource 0; // 设置实时优先级 void set_rt_priority(int prio) { struct sched_param param {.sched_priority prio}; if (pthread_setschedparam(pthread_self(), SCHED_FIFO, param)) { perror(pthread_setschedparam); exit(1); } } // 高优先级任务尝试获取被低优先级持有的锁 void* high_task(void* arg) { set_rt_priority(HIGH_PRIO); printf([H] Started, priority%d\n, HIGH_PRIO); struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, start); pthread_mutex_lock(lock); // 此处阻塞等待 L 释放 clock_gettime(CLOCK_MONOTONIC, end); double wait_ms (end.tv_sec - start.tv_sec) * 1000.0 (end.tv_nsec - start.tv_nsec) / 1e6; printf([H] Got lock after %.3f ms\n, wait_ms); shared_resource; pthread_mutex_unlock(lock); return NULL; } // 中优先级任务纯计算会抢占低优先级 void* medium_task(void* arg) { set_rt_priority(MID_PRIO); printf([M] Started, priority%d, spinning...\n, MID_PRIO); // 模拟 500ms 计算抢占 L volatile unsigned long long spin 0; struct timespec deadline; clock_gettime(CLOCK_MONOTONIC, deadline); deadline.tv_nsec 500 * 1000000; while (clock_gettime(CLOCK_MONOTONIC, (struct timespec){0}) || (deadline.tv_sec 0 || deadline.tv_nsec 0)) { spin; } printf([M] Done spinning\n); return NULL; } // 低优先级任务持有锁被中优先级抢占 void* low_task(void* arg) { set_rt_priority(LOW_PRIO); printf([L] Started, priority%d, taking lock...\n, LOW_PRIO); pthread_mutex_lock(lock); printf([L] Got lock, entering critical section (will be preempted)\n); // 500ms 临界区期间会被 M 抢占 usleep(500000); printf([L] Releasing lock\n); pthread_mutex_unlock(lock); return NULL; } int main() { pthread_t t_low, t_mid, t_high; printf( Priority Inversion Demo \n); printf(Expected: H waits ~500ms (Ls CS) Ms spin time\n); printf(With PI: H waits ~500ms only\n\n); // 创建线程注意启动顺序确保竞争条件 pthread_create(t_low, NULL, low_task, NULL); usleep(10000); // 确保 L 先拿到锁 pthread_create(t_mid, NULL, medium_task, NULL); usleep(10000); // 确保 M 开始运行 pthread_create(t_high, NULL, high_task, NULL); pthread_join(t_low, NULL); pthread_join(t_mid, NULL); pthread_join(t_high, NULL); return 0; }编译与运行gcc -O2 -o priority_inversion priority_inversion.c -pthread # 需要 root 设置实时优先级 sudo ./priority_inversion启用优先级继承对比// 在 main() 中创建锁前添加 pthread_mutexattr_t attr; pthread_mutexattr_init(attr); pthread_mutexattr_setprotocol(attr, PTHREAD_PRIO_INHERIT); pthread_mutex_init(lock, attr);结果对比配置H 等待时间说明无 PI~1000msL 被 M 抢占H 等待 LM有 PI~500msL 继承 H 的优先级M 无法抢占5.3 实验三SCHED_DEADLINE 带宽隔离验证目标验证 DL 调度器的带宽隔离特性防止任务超支。代码周期任务与过载测试// deadline_test.c #define _GNU_SOURCE #include sched.h #include stdio.h #include stdlib.h #include unistd.h #include sys/syscall.h #include linux/sched.h // 内核未导出需自行定义 struct sched_attr { __u32 size; __u32 sched_policy; __u64 sched_flags; __s32 sched_nice; __u32 sched_priority; __u64 sched_runtime; __u64 sched_deadline; __u64 sched_period; }; #ifndef SCHED_DEADLINE #define SCHED_DEADLINE 6 #endif int sched_setattr(pid_t pid, const struct sched_attr *attr, unsigned int flags) { return syscall(__NR_sched_setattr, pid, attr, flags); } int main(int argc, char *argv[]) { if (argc ! 4) { fprintf(stderr, Usage: %s runtime_us deadline_us period_us\n, argv[0]); fprintf(stderr, Example: %s 10000 20000 20000 (50% bandwidth)\n, argv[0]); return 1; } __u64 runtime atoll(argv[1]) * 1000; // 转 ns __u64 deadline atoll(argv[2]) * 1000; __u64 period atoll(argv[3]) * 1000; struct sched_attr attr { .size sizeof(struct sched_attr), .sched_policy SCHED_DEADLINE, .sched_runtime runtime, .sched_deadline deadline, .sched_period period, }; if (sched_setattr(0, attr, 0) -1) { perror(sched_setattr); return 1; } printf(SCHED_DEADLINE set: runtime%.2fms, deadline%.2fms, period%.2fms\n, runtime/1e6, deadline/1e6, period/1e6); // 模拟周期任务运行 runtime然后睡眠到下一周期 while (1) { struct timespec start, now; clock_gettime(CLOCK_MONOTONIC, start); // 模拟计算负载 volatile unsigned long long spin 0; while (spin runtime / 10) spin; // 简化模拟 clock_gettime(CLOCK_MONOTONIC, now); double exec_ms (now.tv_sec - start.tv_sec) * 1000.0 (now.tv_nsec - start.tv_nsec) / 1e6; printf(Job executed in %.3f ms\n, exec_ms); // 使用 sched_yield 让调度器处理截止时间 sched_yield(); } return 0; }运行与监控gcc -O2 -o deadline_test deadline_test.c # 创建 2 个 DL 任务各申请 60% 带宽总和 120%第二个将失败 sudo ./deadline_test 12000 20000 20000 # 60% sudo ./deadline_test 12000 20000 20000 # 60%预期失败 # 监控 DL 运行队列 watch -n 0.5 cat /proc/sched_debug | grep -A5 dl_rq预期结果# 第一个任务成功 SCHED_DEADLINE set: runtime12.00ms, deadline20.00ms, period20.00ms # 第二个任务失败带宽超限 sched_setattr: No space left on device5.4 实验四sched_ext BPF 自定义调度器内核 6.12目标体验用户态可编程调度实现简单的 FIFO 调度器。BPF 程序代码// minimal.bpf.c #include vmlinux.h #include bpf/bpf_helpers.h #include bpf/bpf_tracing.h char LICENSE[] SEC(license) GPL; // 自定义调度简单 FIFO选择最早入队任务 SEC(struct_ops/minimal_select_cpu) s32 BPF_PROG(minimal_select_cpu, struct task_struct *p, s32 prev_cpu, u64 wake_flags) { // 优先使用之前运行的 CPU缓存友好 if (bpf_cpumask_test_cpu(prev_cpu, p-cpus_ptr)) return prev_cpu; return bpf_cpumask_any_distribute(p-cpus_ptr); } SEC(struct_ops/minimal_enqueue) void BPF_PROG(minimal_enqueue, struct task_struct *p, u64 enq_flags) { // 直接入队由核心调度器处理 scx_bpf_dispatch(p, SCX_DSQ_GLOBAL, SCX_SLICE_DFL, enq_flags); } SEC(struct_ops/minimal_dispatch) void BPF_PROG(minimal_dispatch, s32 cpu, struct task_struct *prev) { // 从全局队列取任务 scx_bpf_consume(SCX_DSQ_GLOBAL); } // 注册调度类操作 SEC(.struct_ops) struct sched_ext_ops minimal_ops { .select_cpu (void *)minimal_select_cpu, .enqueue (void *)minimal_enqueue, .dispatch (void *)minimal_dispatch, .name minimal, };编译与加载# 依赖 libbpf 和 clang sudo apt install libbpf-dev clang llvm # 编译 clang -O2 -target bpf -c minimal.bpf.c -o minimal.bpf.o # 加载使用 bpftool 或 sched_ext 加载器 sudo bpftool struct_ops register minimal.bpf.o # 验证加载 cat /sys/kernel/sched_ext/root/ops # 应显示 minimal六、常见问题与解答问题现象诊断与解决sched_setscheduler: Operation not permitted非 root 设置实时优先级需 CAP_SYS_NICE 能力或修改/etc/security/limits.confrealtime - rtprio 99SCHED_DEADLINE 返回ENODEV内核未启用 CONFIG_SCHED_DEADLINE检查grep SCHED_DEADLINE /boot/config-$(uname -r)重新编译内核实时任务导致系统无响应SCHED_FIFO 任务死循环无 yield添加 watchdog或使用 SCHED_RR 强制时间片sched_ext BPF 加载失败invalid func unknown#195内核版本 6.12或 libbpf 版本不匹配CFS 任务 nice -20 仍不够快需要硬实时保证升级到 SCHED_FIFO/SCHED_DEADLINEnice 仅影响权重而非抢占七、实践建议与最佳实践7.1 策略选择决策树开始 │ ├─ 是否需要硬实时保证截止时间 1ms │ ├─ 是 → 任务数量少→ SCHED_FIFO │ │ └─ 多 → SCHED_DEADLINE │ └─ 否 → 继续 │ ├─ 是否为后台批处理大计算量无交互 │ └─ 是 → SCHED_BATCH │ ├─ 是否为最低优先级兜底任务 │ └─ 是 → SCHED_IDLE │ └─ 默认 → SCHED_NORMAL按需调整 nice7.2 生产环境检查清单[ ]sched_rt_runtime_us预留非实时任务带宽建议 5%-10%[ ] 实时任务绑定隔离 CPUisolcpustaskset[ ] 启用 SCHED_DEADLINE 时验证总带宽 ≤ 100%[ ] 使用cyclictest验证调度延迟sudo cyclictest -p99 -i100 -d600[ ] 监控/proc/schedstat检测异常迁移7.3 学术研究数据收集#!/bin/bash # collect_sched_data.sh - 论文数据收集脚本 OUTPUTsched_data_$(date %Y%m%d_%H%M%S) mkdir -p $OUTPUT # 1. 调度器统计 cat /proc/schedstat $OUTPUT/schedstat_start.txt # 2. 运行负载 stress-ng --cpu 4 --io 2 --vm 2 --timeout 300 STRESS_PID$! # 3. 周期性采样 for i in {1..30}; do echo Sample $i $OUTPUT/proc_sched_debug.log cat /proc/sched_debug $OUTPUT/proc_sched_debug.log cat /proc/loadavg $OUTPUT/loadavg.log sleep 10 done kill $STRESS_PID # 4. 后处理 cat /proc/schedstat $OUTPUT/schedstat_end.txt echo 数据收集完成$OUTPUT/八、总结与应用场景策略核心适用场景关键配置参数SCHED_NORMAL通用服务、Web 后端、数据库连接池nice,sched_latency_nsSCHED_BATCH数据 ETL、编译构建、科学计算同 NORMAL减少抢占SCHED_IDLE日志压缩、备份任务、索引重建无参数自动最低SCHED_FIFO工业控制、音频处理、硬实时中断priority 1-99, CPU 隔离SCHED_RR同优先级多实时任务、教学演示priority,sched_rr_timeslice_msSCHED_DEADLINE视频编解码、自动驾驶感知、机器人控制runtime, deadline, periodSCHED_EXT研究实验、自定义调度策略如 ML 推理调度BPF 程序掌握 Linux 七种调度策略的底层机制与适用边界是构建高性能、可预测系统的基石。无论是撰写学术论文中的实验设计还是优化生产环境的延迟表现本文提供的源码、实验与数据收集方法均可直接复用。建议读者从 SCHED_NORMAL/BATCH 对比实验入手逐步深入到实时策略与 sched_ext 编程最终形成完整的调度子系统知识体系。参考文献Linux Kernel Documentation:Documentation/scheduler/Bovet, D. P., Cesati, M. (2005).Understanding the Linux KernelLi, T., et al. (2019). Linux Kernel Scheduler: A Survey.ACM Computing Surveyssched_ext: https://github.com/sched-ext/scx