人人都会AI编程

调度策略与调度类:CFS 完全公平调度器

更新时间:2026-07-12

Linux 内核为了高效管理 CPU 时间,实现了多种调度策略,并将它们组织在一个灵活的“调度类”框架中。对于绝大多数普通进程(即通过 fork() 产生或从命令行启动的程序),内核默认采用 SCHED_NORMAL(历史上也称 SCHED_OTHER)策略,而实现这一策略的正是 CFS(Completely Fair Scheduler,完全公平调度器)

1. 核心目标:理想的完美公平
CFS 的设计目标就是在多个竞争 CPU 时间的任务之间,尽可能模拟“理想多任务”的效果:如果有 N 个同优先级的任务,每个任务应该分别获得恰好 1/N 的 CPU 时间。现实的 CPU 一次只能运行一个任务(一个 CPU 核心一次执行一个进程,这里简化讨论),因此调度器必须以时间片轮换的方式近似这一目标。CFS 并不直接分配固定长度的“时间片”,而是追踪每个任务的“虚拟运行时间”来决定谁应该被紧接着运行。

2. 虚拟运行时间(vruntime)与红黑树
每个可运行进程在内核中都有一个 vruntime 字段,它表示该进程已经累积使用的 CPU 时间的“虚拟”值。CFS 维护一棵按 vruntime 排序的红黑树(rbtree),树中最左侧的节点就是当前 vruntime 最小的进程,也就是“最值得被补偿”的进程,它会成为下一个运行的进程。

当一个进程运行时,它的 vruntime 随着实际物理时间增加,但增长的速率会受进程权重影响:权重越高的进程,vruntime 增长越慢,因此它会在树中停留在较左侧的位置,从而被调度得更频繁。权重与传统的 nice 值直接关联,nice 值越低(优先级越高),权重越大,vruntime 增长越慢。

3. nice 值与权重的映射
用户可以通过 nice 命令(或 setpriority() 系统调用)为进程设置一个从 -20(最高优先级)到 +19(最低优先级)的 nice 值。内核内部将这些 nice 值映射成对应的权重。默认 nice 0 的权重为 1024。简单的对应关系是:nice 值每差 1,CPU 时间分配比例大约差 10%。例如,两个只消耗 CPU 的进程,nice 0 和 nice 5 竞争同一核心时,nice 0 的进程大约会获得 1.1/(1.1+1) ≈ 52.4% 的 CPU,而 nice 5 的进程获得约 47.6%。

这种设计让优先级调整变得平滑而可控,不会出现“高优先级任务把低优先级任务完全饿死”的情况,因为即使是最低优先级任务也能获得一小部分时间,只是数量相对较少。

4. 调度类体系与抢占
CFS 只是 Linux 众多调度类中的一种,内核通过 struct sched_class 将不同调度策略串联起来。常见调度类按优先级由高到低为:

  • DL(Deadline):用于严格期限的实时任务(SCHED_DEADLINE)。
  • RT(Real-Time):POSIX 实时进程,支持 SCHED_FIFO 和 SCHED_RR 策略。
  • CFS(Fair):所有普通进程(SCHED_NORMAL)。
  • IDLE:仅在系统完全空闲时才运行的超低优先级任务。

每当需要选择下一个任务时,调度器从最高优先级的调度类开始询问:如果该类有可运行进程,就选中该类提供的下一个进程;否则向下查询。这意味着一个正在运行的 CFS 进程随时可能被新的实时进程抢占。在 CFS 内部,如果新就绪进程的 vruntime 比当前运行进程小很多,也会触发抢占,保证低延迟响应。

5. 实用接口与调试

  • 调整优先级:启动新进程时可以用 nice -n 5 command,对于已有进程可以用 renice -n 5 -p PID(需适当权限)。
  • 查看调度参数ps -eo pid,ni,comm 可以显示进程的 nice 值;top 中的 NI 列也是 nice 值。
  • CFS 细节观察/proc/sched_debug 文件输出各 CPU 运行队列的详细信息,包括当前运行的进程、红黑树内容、vruntime 等,对深入诊断调度问题非常有用。
  • 实时监控perf sched 命令可以录制和重放调度事件,精确分析任务切换的时间线。

CFS 自 2007 年进入主线内核以来,已被证明能够很好地平衡桌面应用的交互性、服务器的吞吐量和嵌入式设备的确定性需求。理解它基于虚拟运行时间的公平模型,不仅能帮助你在性能调优时正确设置进程优先级,也有助于看懂系统负载背后真正的调度行为。