引言

进程调度器是操作系统的核心组件之一,它决定了哪个进程获得 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_DEADLINEEDFdl_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, &param);

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.pressureCPU 压力信息(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 的唤醒抢占不公平问题

掌握这些调度机制,能够帮助工程师在高并发、低延迟、资源隔离等场景做出正确的架构决策。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部