引言
进程调度器是操作系统的核心组件之一,它决定了哪个进程获得 CPU 执行时间。Linux 内核的调度器经历了从早期简单的 O(n) 调度器到 2.6 系列的 O(1) 调度器,最终演进为现在的 完全公平调度器(Completely Fair Scheduler, CFS)。本文将深入剖析 Linux 进程调度器的核心算法、调度策略、cgroup v2 CPU 控制以及 NUMA 感知调度的实战技巧。
一、调度器架构总览
Linux 调度器采用分层设计:
- 调度策略层:定义进程如何被调度和抢占
- 调度器类层:实现具体的调度算法(CFS、RT、Deadline)
- 核心调度层:提供 schedule()、context_switch() 等通用框架
Linux 支持 5 种调度策略,优先级从高到低排列:
| 策略 | 类型 | 调度类 | 说明 |
|---|---|---|---|
| SCHED_FIFO | 实时 | stop_sched_class → dl_sched_class → rt_schedclass | 先进先出,无时间片抢占,运行直到主动让出或更高级抢占 |
| SCHED_RR | 实时 | rt_sched_class | 轮转,有时间片,同优先级进程按时间片轮转 |
| OTHER/IDLEPRIO | 普通 | idle_sched_class | 空闲优先级,具有负的 nice 值 |
| SCHED_NORMAL | 普通 | fair_sched_class (CFS) | 默认策略,由 CFS 管理 |
| SCHED_BATCH | 普通 | fair_sched_class (CFS) | 批处理,降低唤醒抢占,适合后台任务 |
| SCHED_DEADLINE | EDF | dl_sched_class | 最早截止时间优先(Earliest Deadline First),基于 CBS 算法 |
二、CFS 完全公平调度器核心算法
CFS 的核心思想是虚拟运行时间(vruntime):为每个进程维护一个虚拟时钟,使其在所有可运行进程之间均匀推进。
2.1 虚拟运行时间计算
vruntime 的计算公式:
vruntime += (实际运行时间 × NICE_0_LOAD) / 进程权重
关键特性:
- 权重大的进程(高优先级/低 nice)vruntime 增长慢,获得更多 CPU
- nice 值每降低 1,权重增加约 25%(1.25 倍),CPU 时间相应增加
- 权重与 nice 值的转换关系:
weight = 1024 / (1.25 ^ nice)
2.2 红黑树数据结构
CFS 使用红黑树以 vruntime 为 key 组织所有可运行进程。最左侧节点即为 vruntime 最小(最该被执行)的进程。
- 插入:O(log n),新进程的 vruntime 取当前最小值减去一定的 lag(避免饥饿)
- 选择:O(1),直接取最左节点
- 删除:O(log n)
2.3 调度延迟与粒度
CFS 通过两个关键参数控制调度行为和公平:
- sched_latency(默认 6ms):一个完整调度周期内每个进程应分配的时间
- min_granularity(默认 0.75ms):最小时间片,低于此阈值进程不抢占
实际进程延迟计算:process_time = max(min_granularity, sched_latency / nr_running)
2.4 核心函数路径
CFS 调用的核心函数:
schedule()
→ pick_next_task_fair()
→ pick_next_entity() // 从红黑树选出最左节点
→ set_next_entity() // 更新当前运行实体
context_switch()
→ switch_mm() // 切换地址空间
→ switch_to() // 切换寄存器上下文
三、实时调度策略
3.1 SCHED_FIFO
First In, First Out —— 先进先出策略,同级进程按创建顺序执行,运行直到主动放弃 CPU(阻塞、sched_yield 或被更高优先级抢占)。
使用场景:对延迟极其苛刻的实时任务,如信号处理、工业控制。
struct sched_param param = { .sched_priority = 50 };
pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m);
3.2 SCHED_RR
Round Robin —— 轮转策略,同优先级进程以时间片(默认 100ms)轮转执行。
使用场景:需要多实时任务平等共享 CPU,如音视频处理管线。
3.3 SCHED_DEADLINE(最早截止时间优先)
基于 CBS (Constant Bandwidth Server) 算法的 EDF 调度策略,是 Linux 最强大的实时策略。
进程需声明三个参数:
- Runtime:每个周期内需要的运行时间
- Deadline:每个周期的截止时间
- Period:运行时间与截止时间的周期
struct sched_attr attr = {
.size = sizeof(attr),
.sched_policy = SCHED_DEADLINE,
.sched_runtime = 10 * 1000 * 1000, // 10ms
.sched_deadline = 20 * 1000 * 1000, // 20ms
.sched_period = 20 * 1000 * 1000, // 20ms
};
syscall(SYS_sched_setattr, 0, &attr, 0);
CBS 算法保证每个进程获得精确的带宽分配,适合需要严格时序保证的应用。
四、cgroup v2 CPU 控制器
cgroup v2 提供了细粒度的 CPU 资源管理能力,支持权重分配与硬上限控制。
4.1 接口概览
| 文件 | 说明 |
|---|---|
| cpu.weight | 相对权重(1-10000),默认 100。比例分配 CPU |
| cpu.max | 带宽限制,格式 "$MAX $PERIOD",单位微秒 |
| cpu.max.burst | 允许超出 burst 的微秒数 |
| cpu.pressure | CPU 压力信息(PSI) |
| cpu.stat | 统计:usage_usec、user_usec、system_usec、nr_periods、nr_throttled |
4.2 cpu.weight 按比例分配
假设两个 cgroup A (weight=100) 和 B (weight=300) 竞争空闲 CPU:
A 获得 25% CPU,B 获得 75% CPU
如果 C (weight=200) 加入:A=100/600≈16.7%,B=300/600=50%,C=200/600≈33.3%。
4.3 cpu.max 硬上限
限制 cgroup 内所有进程的 CPU 使用量:
# 限制为 0.5 核:每 100ms 周期内最多运行 50ms
echo "50000 100000" > /sys/fs/cgroup/myapp/cpu.max
# 2 核 burst:允许累计 burst
echo "200000 100000" > /sys/fs/cgroup/myapp/cpu.max
echo "300000" > /sys/fs/cgroup/myapp/cpu.max.burst
4.4 PSI(Pressure Stall Information)
PSI 提供 cgroup 级别的资源压力监控:
$ cat /sys/fs/cgroup/myapp/cpu.pressure
some avg10=12.34 avg60=5.67 avg300=2.89 total=123456789
$ cat /sys/fs/cgroup/myapp/memory.pressure
some avg10=0.00 avg60=0.00 avg300=0.00 total=0
full avg10=0.00 avg60=0.00 avg300=0.00 total=0
解读:
- some:至少一个任务因该资源被阻塞的比例(10s/60s/300s 滚动平均)
- full:所有任务同时被阻塞的比例
- avg10=12.34 表示最近 10 秒内 12.34% 的任务因 CPU 被阻塞
五、上下文切换优化
上下文切换是调度的直接开销,频繁切换会显著降低系统性能。
5.1 上下文切换类型
- 自愿切换(voluntary):进程主动放弃 CPU(I/O 阻塞、sleep、wait)
- 非自愿切换(involuntary):被抢占(时间片用完、更高优先级到来)
5.2 监控与诊断
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
8 0 0 4.2G 256M 3.1G 0 0 0 0 250000 850000 15 5 80 0 0
# cs 列 = 上下文切换次数/秒 = 850000(过高)
$ perf sched record -- sleep 1
$ perf sched latency
-------------------------------------------------------------------------------------------------
Task | Runtime ms | Switches | Average delay ms | Maximum delay ms |
-------------------------------------------------------------------------------------------------
kworker/0:1H | 0.123 | 45 | 0.001 | 0.010 |
chrome | 120.456 | 892 | 0.023 | 45.678 | ← 延迟异常
-------------------------------------------------------------------------------------------------
5.3 减少上下文切换的策略
- CPU 亲和性(Affinity):将进程绑定到特定 CPU,减少缓存失效
$ taskset -cp 2-3 <pid>
$ sched_setaffinity() // 编程接口
// cgroup v2 接口
echo "2-3" > /sys/fs/cgroup/myapp/cpuset.cpus
- 中断隔离:将中断绑定到特定 CPU,避免打断工作负载
# 将中断亲和性移到 CPU 0-1
echo "3" > /proc/irq/IRQ_NUMBER/smp_affinity
- 无锁通信:使用 ring buffer、RCU read lock 替代互斥锁
- 批量处理:合并系统调用(io_uring 的批量提交)
- 降低调度频率:增大 sched_min_granularity_ns(注意时延影响)
六、NUMA 感知调度
非一致性内存访问(NUMA)架构中,进程访问本地节点内存速度远快于跨节点。
6.1 NUMA 拓扑发现
$ numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
node 0 size: 257894 MB
node 0 free: 180234 MB
node 1 cpus: 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
node 1 size: 258123 MB
node 1 free: 201456 MB
node distances: node 0 1
0: 10 21
1: 21 10
6.2 numactl 绑定运行
# 在 node1 运行,分配 node1 内存
numactl --cpunodebind=1 --membind=1 ./my_app
# 交错分配内存(适用于大内存应用)
numactl --interleave=all ./my_app
6.3 自动 NUMA 平衡
Linux 提供自动 NUMA 平衡内核功能(默认开启):
$ sysctl kernel.numa_balancing
kernel.numa_balancing = 1
# 查看统计
$ cat /proc/<pid>/numa_maps
7f4e00000000 default file=/usr/lib/libc.so.6 N0=234 N1=166
7f4e20000000 default heap N0=523 N1=77 ← 大量内存仍在 node0
手动触发页迁移:
# 将进程页面迁移到 node0
migratepages <pid> 1 0
6.4 调度器与 NUMA 的协同
Linux 调度器的 NUMA 感知特性:
- 自动页面扫描:每 1 秒扫描进程地址空间,识别热页位置
- 页迁移:将热页迁移到进程所在的 NUMA 节点
- 内存均衡(memory balancing):避免所有进程挤在同一节点
控制参数:
kernel.numa_balancing # 总开关
kernel.numa_balancing_scan_period_min # 最小扫描间隔(默认 1000ms)
kernel.numa_balancing_scan_period_max # 最大扫描间隔(默认 60000ms)
kernel.numa_balancing_scan_size # 每次扫描大小(默认 256MB)
七、实战案例分析
7.1 Web 服务器 CPU 绑核优化
场景:高并发 Nginx + 多 worker 进程
# 1. 中断绑定到 CPU 0-3
echo "f" > /proc/irq/128/smp_affinity # CPU 0-3
# 2. Nginx worker 绑定到 CPU 4-7
worker_processes auto;
worker_cpu_affinity 00000001 00000010 00000100 00001000;
# 3. 监控系统上下文切换变化
perf stat -e cs -a sleep 10
# 结果:上下文切换减少 60%,P99 延迟下降 35%
7.2 容器化应用的 cgroup 隔离
K8s Pod 的 CPU 管理策略:
# Guaranteed Pod(一致性好)
resources:
requests:
cpu: "2"
limits:
cpu: "2"
# Burstable Pod(按比例分配)
resources:
requests:
cpu: "500m"
limits:
cpu: "2000m"
# cpu.max = 200000us / 100000us = 0.2 核 request
# cpu.max = 200000us / 100000us = 200000us/100000us = 2 核 limit
# PSI 压力 > 30% 触发告警
7.3 实时音视频处理
Pipeline:采集 → 编解码 → 网络发送,使用 SCHED_DEADLINE 保证端到端延迟
// 采集线程:每 33ms 完成 8ms 采集
struct sched_attr cap_attr = {
.sched_policy = SCHED_DEADLINE,
.sched_runtime = 8000000,
.sched_deadline = 33000000,
.sched_period = 33000000,
};
// 编码线程:每 33ms 完成 15ms 编码
struct sched_attr enc_attr = {
.sched_policy = SCHED_DEADLINE,
.sched_runtime = 15000000,
.sched_deadline = 33000000,
.sched_period = 33000000,
};
八、调优参数参考
# 调度参数
kernel.sched_min_granularity_ns = 1000000 # 1ms 最小时间片
kernel.sched_wakeup_granularity_ns = 15000000 # 15ms 唤醒抢占粒度
kernel.sched_migration_cost_ns = 5000000 # 5m3 迁移成本阈值
kernel.sched_autogroup_enabled = 1 # 自动分组(桌面优化)
# 实时调度
kernel.sched_rt_runtime_us = 950000 # RT 任务最多占用 95% CPU
kernel.sched_rt_period_us = 1000000
# NUMA
kernel.numa_balancing = 1 # 开启自动 NUMA 平衡
九、总结与展望
Linux 进程调度器从 CFS 的革命性设计到 SCHED_DEADLINE 的硬实时保证,再到 cgroup v2 的资源隔离和 NUMA 感知的多层次优化,构建了一套完善的 CPU 资源管理体系。
当前社区重点关注的方向包括:
- BPF 调度器扩展:允许用户通过 eBPF 实现自定义调度策略
- 异构计算调度:Intel Thread Director / ARM big.LITTLE 的硬件反馈调度
- 能效调度(ITMT/EEVDF):在 P-core/E-core 混合架构上的能效平衡
- EEVDF(Earliest Eligible Virtual Deadline First):替代 CFS 的新一代调度器(Linux 6.6+),解决 CFS 的唤醒抢占不公平问题
掌握这些调度机制,能够帮助工程师在高并发、低延迟、资源隔离等场景做出正确的架构决策。

发表评论 取消回复