操作系统调度原理:从CFS到多级反馈队列

调度器,说白了就是操作系统里那个决定「下一个谁上CPU」的模块。你写再多线程,如果调度器不给力,该响应的卡住,该算完的拖半天——我见过太多业务层优化半天,结果根子在调度策略上的案例了。

这一章,我们就把Linux CFS、时间片轮转、优先级调度、多级反馈队列这几个核心概念掰开揉碎。嗯,我会结合自己踩过的坑来讲。

2.1 Linux CFS调度器:公平不是平均

CFS的全称是Completely Fair Scheduler,完全公平调度器。从2.6.23内核开始它就是默认调度器了。

核心思想:不是给每个任务固定时间片,而是让每个任务获得相等的CPU时间比例。

CFS维护一个红黑树,键值是vruntime(虚拟运行时间)。每次选最左边那个节点——也就是vruntime最小的任务上CPU。

关键公式:vruntime += 实际运行时间 × (nice值对应的权重 / 默认权重)

优先级高的任务(nice值小),vruntime增长慢,所以能多占CPU;优先级低的增长快,自然少占。

我曾经在一个数据库中间件项目里,发现某个后台清理线程总是抢不过业务线程,导致内存回收延迟。查了半天,其实就是nice值没调——清理线程的vruntime增长太快,几乎轮不到它。把nice值从0调到-5,问题解决。

CFS的关键参数

参数 含义 默认值 我的建议
sched_latency_ns 目标调度延迟 6ms(桌面)/ 20ms(服务器) 交互型任务可以调小
sched_min_granularity_ns 最小执行粒度 0.75ms 太小会导致上下文切换过频
sched_wakeup_granularity_ns 唤醒抢占粒度 1ms 调小可减少唤醒延迟

调优技巧:如果你的应用是延迟敏感的(比如游戏服务器、高频交易),可以尝试把sched_latency_ns降到3ms以下。但注意——这会增加上下文切换次数,CPU开销会上升。

2.2 时间片轮转:最朴素的公平

时间片轮转(Round Robin)是最基础的调度算法。每个任务轮流上CPU,时间片一到就切换下一个。

你想想看,如果时间片太大,交互任务会卡顿;太小呢,切换开销又上去了。我记得早期Linux 2.4内核用的就是纯时间片轮转,那时候我做一个嵌入式GUI系统,鼠标移动都一卡一卡的——就是因为时间片设成了100ms。

时间片选择的经验法则:

  • 交互型任务:10-50ms,保证响应
  • 计算型任务:50-200ms,减少切换开销
  • 混合场景:建议用CFS,别自己手撸时间片

注意:现代操作系统很少单独用纯时间片轮转。CFS本质上是一种加权的时间片分配,但更灵活。如果你在写RTOS或者裸机调度,时间片轮转仍然是最可靠的选择。

2.3 优先级调度:谁急谁先上

优先级调度分两种:

  • 静态优先级:创建时定死,运行中不变
  • 动态优先级:根据行为调整(比如I/O密集型任务自动提权)

Linux的实时优先级范围是0-99,普通优先级通过nice值映射到100-139。数值越小,优先级越高。

优先级反转——这是个经典坑。低优先级任务持有锁,高优先级任务等锁,中优先级任务抢CPU,结果高优先级任务反而被低优先级任务阻塞。

我曾经在一个音视频处理流水线里遇到过:解码线程(高优先级)等锁,锁被日志写入线程(低优先级)持有,结果一个中优先级的网络线程把CPU占满了,解码线程活活饿死。解决方案是给日志线程也提优先级,或者用优先级继承协议。

避坑指南:

  • 不要轻易给业务线程设实时优先级(99),搞不好系统都卡死
  • 锁的持有者优先级要高于或等于等待者
  • 监控/proc/sys/kernel/sched_rt_runtime_us,防止实时任务饿死普通任务

2.4 多级反馈队列:兼顾响应与吞吐

多级反馈队列(MLFQ)是很多现代操作系统的调度基石。它维护多个队列,每个队列优先级不同,时间片也不同。

核心规则:

  1. 新任务进入最高优先级队列
  2. 时间片内主动让出CPU(I/O操作),保持优先级
  3. 时间片用完还没完成,降级到下一级队列
  4. 优先级高的队列先执行,同级别轮转

说白了,就是「交互型任务优先,计算型任务靠后」。I/O密集任务很快就能再次获得CPU,计算密集任务慢慢降到低优先级队列,用大时间片跑。

我记得调优一个Web服务器时,发现某些请求偶尔延迟飙高。用perf一看,是内核的MLFQ把长连接的处理线程降级了,导致响应变慢。解决方案是把Nginx的worker进程绑定到实时优先级,绕过MLFQ的降级逻辑。

2.5 知识体系总览

下面这张图把四种调度策略的关系和适用场景串起来了:

操作系统调度策略知识体系 操作系统调度策略知识体系 调度策略 CFS 完全公平 时间片轮转 优先级调度 多级反馈队列 特性 • 红黑树实现 • vruntime排序 • 加权公平 • 默认调度器 特性 • 固定时间片 • 循环切换 • 公平但低效 • 适合RTOS 特性 • 静态/动态 • 实时优先 • 优先级反转 • 需谨慎使用 特性 • 多级队列 • 动态降级 • I/O优先 • 兼顾响应 适用场景 通用服务器/桌面 适用场景 嵌入式/RTOS 适用场景 实时系统/音频 适用场景 交互式系统 现代操作系统(如Linux)综合使用多种策略:CFS + 优先级 + MLFQ思想

2.6 实战:如何选择调度策略

我个人的经验是:

  • 通用业务:用CFS默认配置,别乱动
  • 延迟敏感:调小sched_latency_ns,考虑绑定CPU
  • 计算密集:调大sched_min_granularity_ns,减少切换
  • 混合负载:用cgroup隔离,不同组用不同调度策略

快速检查当前调度器:

cat /sys/kernel/debug/sched_features
cat /proc/sys/kernel/sched_*