2. Linux 进程调度基础:CFS 调度器原理、nice 值与权重、调度延迟与最小粒度

2.1 CFS 调度器核心原理

完全公平调度器(Completely Fair Scheduler, CFS)是 Linux 内核自 2.6.23 版本起默认采用的进程调度算法。其设计哲学并非追求绝对的“公平”,而是在多任务环境下为每个进程提供虚拟运行时间(vruntime)上的公平,从而在宏观上实现 CPU 时间的均衡分配。

CFS 摒弃了传统的时间片轮转模型,转而使用红黑树(Red-Black Tree)来管理可运行进程。红黑树以进程的 vruntime 为键值进行排序,最左侧的节点(即 vruntime 最小的进程)是下一个被调度执行的进程。这种设计使得调度器能够以 O(log n) 的时间复杂度快速找到最需要 CPU 的进程。

核心公式:
vruntime += 实际运行时间 * (NICE_0_LOAD / 进程权重)

其中 NICE_0_LOAD 是 nice 值为 0 时的标准权重(1024)。该公式表明:权重越高的进程,其 vruntime 增长越慢,从而获得更多的 CPU 执行机会。

MySQL 关联: 当 MySQL 后台线程(如 InnoDB 的 IO 线程、刷脏页线程)与用户查询线程共享 CPU 时,CFS 的 vruntime 计算方式决定了高优先级(低 nice 值)的查询线程能抢占更多 CPU 时间,从而降低查询延迟。
CFS 红黑树调度结构 vruntime=150 vruntime=100 vruntime=200 vruntime=80 ← 调度 vruntime=130 vruntime=180 说明: • 红黑树最左侧节点(vruntime 最小)是下一个被调度执行的进程。 • 每次调度周期,调度器从树中选取最左节点,更新其 vruntime 后重新插入。

2.2 nice 值与权重映射

在 CFS 中,nice 值不再是传统意义上时间片的加减,而是通过一个全局的权重数组 prio_to_weight[] 映射为进程的调度权重。nice 值的范围是 -20 到 +19,默认值为 0。权重与 nice 值呈指数级关系,每相差 1 个 nice 值,权重变化约 10%。

nice 值 权重(weight) 相对 nice=0 的 CPU 占比 典型场景
-20 88761 约 86.7 倍 实时性要求极高的系统进程
-10 9548 约 9.3 倍 音频/视频处理
0 1024 1.0 倍(基准) 普通用户进程(默认)
10 110 约 0.11 倍 后台批处理任务
19 15 约 0.015 倍 极低优先级空闲任务
MySQL 调优警告: 不要轻易将 MySQL 进程的 nice 值设置为负值(提高优先级)。虽然这能降低查询延迟,但可能导致其他系统服务(如 SSH、监控代理)因得不到 CPU 而超时,引发运维事故。建议通过 cgroups 或 taskset 进行更精细的 CPU 隔离。
nice 值与权重映射关系 nice 值(-20 → +19) 88761 -20 9548 -10 1024 0(基准) 110 +10 15 +19 权重随 nice 值呈指数级变化:每降低 1 个 nice 值,权重增加约 10%。

2.3 调度延迟与最小粒度

CFS 引入了两个关键参数来控制调度行为的精细度:调度延迟(sched_latency)和最小粒度(min_granularity)。这两个参数直接决定了进程在被抢占前能连续运行的最长时间,对 MySQL 这类延迟敏感型应用影响显著。

2.3.1 调度延迟(sched_latency)

调度延迟是 CFS 保证所有可运行进程在一个周期内至少被调度一次的目标时间间隔。默认值为 6ms(可通过 /proc/sys/kernel/sched_latency_ns 查看和修改)。当可运行进程数量较少时,每个进程在一个调度周期内获得的时间片为 sched_latency / 进程数。

2.3.2 最小粒度(min_granularity)

最小粒度是进程被调度后至少运行的时间下限,防止进程切换过于频繁导致上下文切换开销过大。默认值为 0.75ms(可通过 /proc/sys/kernel/sched_min_granularity_ns 查看)。当进程数量较多时,每个进程的时间片会被截断至最小粒度,以保证调度延迟不超过 sched_latency 的约束。

时间片计算规则:
if (进程数 * min_granularity <= sched_latency)
    时间片 = sched_latency / 进程数
else
    时间片 = min_granularity

当进程数超过 sched_latency / min_granularity(默认 8 个)时,调度延迟将不再保证,每个进程仅获得最小粒度的时间片。

调度延迟与最小粒度关系 时间轴(ms) 进程 A(3ms) 进程 B(3ms) 进程 C(3ms) 调度延迟 = 9ms(3 进程 × 3ms) 0.75ms 关键参数: • sched_latency(调度延迟):默认 6ms,保证所有进程在此时长内至少运行一次。 • min_granularity(最小粒度):默认 0.75ms,进程至少运行的时间下限。 • 当进程数 ≤ 8 时,每个进程获得 sched_latency / 进程数 的时间片;超过 8 个时,每个进程仅获得 min_granularity。 • MySQL 场景:减少可运行进程数(如绑定 CPU)可让每个查询线程获得更大的时间片,降低调度延迟。

2.4 调度器参数对 MySQL 的影响路径

理解 CFS 调度器参数后,我们可以梳理出其对 MySQL 查询延迟的影响路径:

  1. nice 值 → 权重 → vruntime 增长率:高权重(低 nice 值)的 MySQL 线程 vruntime 增长慢,在红黑树中更靠左,被调度更频繁。
  2. sched_latency → 时间片大小:较大的调度延迟意味着每个 MySQL 线程获得更长的时间片,减少上下文切换次数,降低缓存污染。
  3. min_granularity → 切换频率:过小的最小粒度会导致 MySQL 线程频繁被抢占,增加查询尾延迟(tail latency)。
  4. 进程数 → 调度延迟保证:当 MySQL 服务器上可运行进程数过多时,调度延迟保证失效,每个线程仅获得最小粒度时间片,查询延迟急剧上升。
实践建议: 对于专用 MySQL 服务器,建议将 sched_latency_ns 调大至 12ms-24ms,并将 sched_min_granularity_ns 调大至 1.5ms-3ms,以减少上下文切换。同时通过 taskset 将 MySQL 线程绑定到专用 CPU 核心,减少可运行进程数,确保每个查询线程获得充足的时间片。
调度参数 → MySQL 查询延迟影响路径 nice 值 (权重映射) sched_latency (调度延迟) min_granularity (最小粒度) 可运行进程数 (CPU 负载) vruntime 增长率 / 时间片大小 (调度频率与执行时长) MySQL 查询延迟