Linux 调度器底层原理深度解析
引言
调度是操作系统最核心的功能之一,它决定了哪个进程/线程在什么时候获得 CPU 执行权。本文将深入剖析 Linux 调度器的底层原理,从基础概念到内核实现,帮助你全面理解调度机制。
文章目录
- [Linux 调度器底层原理深度解析](#Linux 调度器底层原理深度解析)
-
- 引言
- [1. 调度基础概念](#1. 调度基础概念)
-
- [1.1 什么是调度](#1.1 什么是调度)
- [1.2 调度目标](#1.2 调度目标)
- [1.3 进程状态](#1.3 进程状态)
- [1.4 调度时机](#1.4 调度时机)
- [1.5 上下文切换](#1.5 上下文切换)
- [1.6 调度器发展历史](#1.6 调度器发展历史)
- [2. Linux 调度策略](#2. Linux 调度策略)
-
- [2.1 调度策略概览](#2.1 调度策略概览)
- [2.2 SCHED_OTHER (CFS)](#2.2 SCHED_OTHER (CFS))
- [2.3 SCHED_FIFO](#2.3 SCHED_FIFO)
- [2.4 SCHED_RR](#2.4 SCHED_RR)
- [2.5 SCHED_DEADLINE](#2.5 SCHED_DEADLINE)
- [2.6 命令行工具](#2.6 命令行工具)
- [3. CFS 调度器详解](#3. CFS 调度器详解)
-
- [3.1 核心思想](#3.1 核心思想)
- [3.2 虚拟运行时间 (vruntime)](#3.2 虚拟运行时间 (vruntime))
- [3.3 红黑树](#3.3 红黑树)
- [3.4 权重计算](#3.4 权重计算)
- [3.5 时间片计算](#3.5 时间片计算)
- [3.6 CFS 参数调优](#3.6 CFS 参数调优)
- [4. 实时调度](#4. 实时调度)
-
- [4.1 实时系统分类](#4.1 实时系统分类)
- [4.2 SCHED_FIFO vs SCHED_RR](#4.2 SCHED_FIFO vs SCHED_RR)
- [4.3 实时带宽限制](#4.3 实时带宽限制)
- [4.4 设置实时调度](#4.4 设置实时调度)
- [4.5 实时调度注意事项](#4.5 实时调度注意事项)
- [5. 优先级与权重](#5. 优先级与权重)
-
- [5.1 优先级体系](#5.1 优先级体系)
- [5.2 nice 值](#5.2 nice 值)
- [5.3 优先级反转](#5.3 优先级反转)
- [6. CPU 亲和性](#6. CPU 亲和性)
-
- [6.1 概念](#6.1 概念)
- [6.2 API](#6.2 API)
- [6.3 CPU 集合操作](#6.3 CPU 集合操作)
- [6.4 线程亲和性](#6.4 线程亲和性)
- [6.5 命令行工具](#6.5 命令行工具)
- [6.6 isolcpus](#6.6 isolcpus)
- [7. 多核调度](#7. 多核调度)
-
- [7.1 SMP 调度](#7.1 SMP 调度)
- [7.2 负载均衡](#7.2 负载均衡)
- [7.3 调度域](#7.3 调度域)
- [7.4 NUMA 调度](#7.4 NUMA 调度)
- [8. cgroups 调度控制](#8. cgroups 调度控制)
-
- [8.1 cgroups 概念](#8.1 cgroups 概念)
- [8.2 cpu.shares](#8.2 cpu.shares)
- [8.3 CFS 配额](#8.3 CFS 配额)
- [8.4 cpuset](#8.4 cpuset)
- [8.5 容器中的调度](#8.5 容器中的调度)
- [9. 调度性能分析](#9. 调度性能分析)
-
- [9.1 perf sched](#9.1 perf sched)
- [9.2 /proc 信息](#9.2 /proc 信息)
- [9.3 上下文切换监控](#9.3 上下文切换监控)
- [9.4 运行队列监控](#9.4 运行队列监控)
- [10. 内核调度器内部](#10. 内核调度器内部)
-
- [10.1 task_struct 调度字段](#10.1 task_struct 调度字段)
- [10.2 调度类](#10.2 调度类)
- [10.3 调度类接口](#10.3 调度类接口)
- [10.4 schedule() 流程](#10.4 schedule() 流程)
- [11. 调度优化实践](#11. 调度优化实践)
-
- [11.1 CPU 密集型优化](#11.1 CPU 密集型优化)
- [11.2 I/O 密集型优化](#11.2 I/O 密集型优化)
- [11.3 延迟敏感应用](#11.3 延迟敏感应用)
- [11.4 实时应用清单](#11.4 实时应用清单)
- [11.5 常见问题诊断](#11.5 常见问题诊断)
- 总结
- 配套资源
1. 调度基础概念
1.1 什么是调度
调度 (Scheduling) 是操作系统决定哪个进程/线程获得 CPU 执行权的过程。在任一时刻,单核 CPU 只能运行一个进程,调度器的任务就是在多个可运行进程之间分配 CPU 时间。
就绪队列
┌───┐ ┌───┐ ┌───┐ ┌───┐
│ P1│ │ P2│ │ P3│ │ P4│ ──→ 调度器 ──→ CPU
└───┘ └───┘ └───┘ └───┘
1.2 调度目标
调度器需要平衡多个目标:
| 目标 | 说明 |
|---|---|
| CPU 利用率 | 尽量让 CPU 保持忙碌 |
| 吞吐量 | 单位时间完成的进程数 |
| 响应时间 | 从提交到首次响应的时间 |
| 等待时间 | 在就绪队列中等待的时间 |
| 公平性 | 每个进程都能获得 CPU |
1.3 进程状态
Linux 进程状态与调度密切相关:
| 状态 | 符号 | 说明 |
|---|---|---|
| TASK_RUNNING | R | 运行中或在运行队列中等待 |
| TASK_INTERRUPTIBLE | S | 可中断睡眠,等待事件 |
| TASK_UNINTERRUPTIBLE | D | 不可中断睡眠,等待 I/O |
| TASK_STOPPED | T | 被信号停止 |
| TASK_TRACED | t | 被调试器停止 |
| EXIT_ZOMBIE | Z | 僵尸状态 |
创建 ──────────→ 就绪 (R) ←──────┐
│ 调度 │ 时间片用完
↓ │ 或被抢占
运行 (R) ─────────┘
│
┌─────────────┼─────────────┐
↓ ↓ ↓
睡眠 (S) 阻塞 (D) 终止
1.4 调度时机
调度发生在以下时机:
主动让出 CPU:
- 调用
sleep(),wait(),pause() - 调用
sched_yield() - 等待 I/O 完成
- 等待锁或信号量
被动让出 CPU:
- 时间片用完(时钟中断)
- 更高优先级进程就绪
- 被信号中断
1.5 上下文切换
上下文切换是保存当前进程状态、恢复另一个进程状态的过程:
c
// 简化的上下文切换流程
void context_switch(struct task_struct *prev, struct task_struct *next) {
// 1. 保存 prev 的 CPU 寄存器
save_registers(prev);
// 2. 切换内存映射 (页表)
switch_mm(prev->mm, next->mm);
// 3. 恢复 next 的 CPU 寄存器
restore_registers(next);
}
上下文切换开销:
- 直接开销:保存/恢复寄存器、切换页表
- 间接开销:CPU 缓存失效、TLB 刷新
- 典型耗时:几微秒到几十微秒
1.6 调度器发展历史
| 版本 | 调度器 | 特点 |
|---|---|---|
| Linux 2.4 | O(n) | 遍历所有进程,进程多时性能差 |
| Linux 2.6.0 | O(1) | 常数时间选择,但交互性不够好 |
| Linux 2.6.23+ | CFS | 基于虚拟运行时间,更公平 |
| Linux 6.6+ | EEVDF | CFS 改进版,更好的延迟保证 |
2. Linux 调度策略
Linux 提供多种调度策略,适用于不同场景:
2.1 调度策略概览
| 策略 | 类型 | 优先级 | 时间片 | 适用场景 |
|---|---|---|---|---|
| SCHED_DEADLINE | 实时 | 最高 | CBS | 周期实时任务 |
| SCHED_FIFO | 实时 | 1-99 | 无 | 硬实时 |
| SCHED_RR | 实时 | 1-99 | 有 | 软实时 |
| SCHED_OTHER | 普通 | nice | 动态 | 普通进程 |
| SCHED_BATCH | 普通 | nice | 动态 | 批处理 |
| SCHED_IDLE | 普通 | 最低 | 动态 | 空闲时运行 |
优先级顺序:
SCHED_DEADLINE > SCHED_FIFO/RR > SCHED_OTHER > SCHED_BATCH > SCHED_IDLE
2.2 SCHED_OTHER (CFS)
默认调度策略,由 CFS(完全公平调度器)实现:
c
// 查看当前调度策略
int policy = sched_getscheduler(0);
// 返回 SCHED_OTHER (0)
特点:
- 基于虚拟运行时间的公平调度
- 通过 nice 值调整优先级(-20 到 19)
- 适用于大多数普通应用
2.3 SCHED_FIFO
先进先出实时调度:
c
struct sched_param param;
param.sched_priority = 50; // 1-99
sched_setscheduler(0, SCHED_FIFO, ¶m);
特点:
- 无时间片限制
- 一直运行直到阻塞、让出或被更高优先级抢占
- ⚠️ 可能导致系统无响应
2.4 SCHED_RR
实时轮转调度:
c
struct sched_param param;
param.sched_priority = 30;
sched_setscheduler(0, SCHED_RR, ¶m);
// 获取时间片
struct timespec ts;
sched_rr_get_interval(0, &ts);
特点:
- 类似 FIFO,但有时间片
- 时间片用完后移到同优先级队列末尾
2.5 SCHED_DEADLINE
基于截止时间的调度(Linux 3.14+):
c
struct sched_attr attr;
attr.size = sizeof(attr);
attr.sched_policy = SCHED_DEADLINE;
attr.sched_runtime = 10000000; // 10ms
attr.sched_deadline = 50000000; // 50ms
attr.sched_period = 100000000; // 100ms
syscall(SYS_sched_setattr, 0, &attr, 0);
参数含义:
|<──────── period ────────>|
| |
|<─ runtime ─>|<─deadline─>|
┌─────────────┐ │
│ 执行时间 │ │
└─────────────┘ │
↑ ↑
开始 截止
2.6 命令行工具
bash
# 查看进程调度策略
chrt -p <pid>
# 设置调度策略
sudo chrt -f 50 ./program # SCHED_FIFO
sudo chrt -r 30 ./program # SCHED_RR
sudo chrt -o 0 ./program # SCHED_OTHER
sudo chrt -b 0 ./program # SCHED_BATCH
sudo chrt -i 0 ./program # SCHED_IDLE
3. CFS 调度器详解
CFS(Completely Fair Scheduler)是 Linux 2.6.23 引入的默认调度器。
3.1 核心思想
理想情况: N 个进程同时运行,每个获得 1/N 的 CPU。
现实: 单核一次只能运行一个进程。
解决方案: 跟踪每个进程的"虚拟运行时间"(vruntime),总是选择 vruntime 最小的进程运行。
3.2 虚拟运行时间 (vruntime)
vruntime = 实际运行时间 × (基准权重 / 进程权重)
计算示例:
| 进程 | nice | 权重 | 实际运行 | vruntime |
|---|---|---|---|---|
| A | 0 | 1024 | 10ms | 10ms |
| B | -5 | 3121 | 10ms | 3.3ms |
| C | 5 | 335 | 10ms | 30.6ms |
意义:
- 高优先级进程:vruntime 增长慢 → 更多运行机会
- 低优先级进程:vruntime 增长快 → 更少运行机会
3.3 红黑树
CFS 使用红黑树管理就绪进程,按 vruntime 排序:
┌─────────────┐
│ vruntime=50 │
│ (黑色) │
└──────┬──────┘
┌───────────┴───────────┐
┌──────┴──────┐ ┌──────┴──────┐
│ vruntime=30 │ │ vruntime=70 │
│ (红色) │ │ (红色) │
└─────────────┘ └─────────────┘
操作复杂度:
- 插入/删除:O(log n)
- 查找最小值:O(1)(缓存最左节点)
3.4 权重计算
nice 值到权重的映射:
| nice | 权重 | 相对 CPU 时间 |
|---|---|---|
| -20 | 88761 | ×87 |
| -10 | 9548 | ×9 |
| 0 | 1024 | ×1 (基准) |
| 10 | 110 | ×1/9 |
| 19 | 15 | ×1/68 |
每相差 1 个 nice 值,权重约差 1.25 倍。
3.5 时间片计算
时间片 = 调度周期 × (进程权重 / 运行队列总权重)
关键参数:
sched_latency_ns: 调度周期(默认 6ms)sched_min_granularity_ns: 最小时间片(默认 0.75ms)
3.6 CFS 参数调优
bash
# 查看 CFS 参数
cat /proc/sys/kernel/sched_latency_ns
cat /proc/sys/kernel/sched_min_granularity_ns
cat /proc/sys/kernel/sched_wakeup_granularity_ns
# 修改参数(临时)
echo 3000000 > /proc/sys/kernel/sched_latency_ns
# 永久修改
echo "kernel.sched_latency_ns = 3000000" >> /etc/sysctl.conf
sysctl -p
调优建议:
| 场景 | 建议 |
|---|---|
| 桌面/交互 | 减小 latency 和 granularity |
| 服务器/吞吐 | 增大 latency 和 granularity |
| 低延迟 | 减小 wakeup_granularity |
4. 实时调度
4.1 实时系统分类
- 硬实时: 必须在截止时间前完成,否则系统失败
- 软实时: 尽量在截止时间前完成,偶尔超时可接受
4.2 SCHED_FIFO vs SCHED_RR
| 特性 | SCHED_FIFO | SCHED_RR |
|---|---|---|
| 时间片 | 无 | 有(约100ms) |
| 同优先级调度 | FIFO | 轮转 |
| 运行时间 | 直到阻塞/让出 | 时间片用完后轮转 |
4.3 实时带宽限制
bash
# 查看限制
cat /proc/sys/kernel/sched_rt_period_us # 1000000 (1秒)
cat /proc/sys/kernel/sched_rt_runtime_us # 950000 (0.95秒)
# 含义:实时进程最多占用 95% CPU
4.4 设置实时调度
c
#include <sched.h>
struct sched_param param;
param.sched_priority = 50; // 1-99
if (sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) {
perror("sched_setscheduler");
}
4.5 实时调度注意事项
⚠️ 警告:
- SCHED_FIFO 进程死循环会独占 CPU
- 高优先级实时进程会抢占所有低优先级进程
- 需要 root 权限
安全措施:
- 保留紧急恢复手段(SSH 会话)
- 使用看门狗定时器
- 先在虚拟机测试
- 不使用优先级 99(留给内核)
5. 优先级与权重
5.1 优先级体系
Linux 使用 0-139 的内部优先级:
0 ──────────── 99 ──────────── 139
│ │ │
│ 实时进程 │ 普通进程 │
│ (RT_PRIO) │ (nice) │
映射关系:
- 实时优先级 1-99 → 内核优先级 98-0(反转)
- nice -20 到 19 → 内核优先级 100-139
5.2 nice 值
c
#include <sys/resource.h>
// 获取 nice 值
int nice_val = getpriority(PRIO_PROCESS, 0);
// 设置 nice 值
setpriority(PRIO_PROCESS, 0, 10);
// 增加 nice 值
nice(5);
bash
# 命令行
nice -n 10 ./program # 以 nice=10 启动
renice -n 5 -p <pid> # 修改运行中进程
5.3 优先级反转
问题场景:
- 低优先级进程持有锁
- 高优先级进程等待锁
- 中优先级进程抢占低优先级进程
解决方案:
c
// 优先级继承
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);
pthread_mutex_init(&mutex, &attr);
6. CPU 亲和性
6.1 概念
CPU 亲和性指定进程/线程可以在哪些 CPU 上运行:
- 软亲和性: 调度器尽量让进程在同一 CPU 运行(默认)
- 硬亲和性: 强制进程只能在指定 CPU 运行
6.2 API
c
#include <sched.h>
// 获取亲和性
cpu_set_t mask;
CPU_ZERO(&mask);
sched_getaffinity(0, sizeof(mask), &mask);
// 设置亲和性
CPU_ZERO(&mask);
CPU_SET(0, &mask); // 绑定到 CPU 0
CPU_SET(1, &mask); // 也允许 CPU 1
sched_setaffinity(0, sizeof(mask), &mask);
6.3 CPU 集合操作
c
CPU_ZERO(&mask) // 清空
CPU_SET(cpu, &mask) // 添加
CPU_CLR(cpu, &mask) // 移除
CPU_ISSET(cpu, &mask) // 检查
CPU_COUNT(&mask) // 计数
6.4 线程亲和性
c
#include <pthread.h>
pthread_t thread = pthread_self();
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(0, &mask);
pthread_setaffinity_np(thread, sizeof(mask), &mask);
6.5 命令行工具
bash
# 查看亲和性
taskset -p <pid>
# 设置亲和性
taskset -c 0,1 ./program # 绑定到 CPU 0,1
taskset 0x3 ./program # 掩码方式
taskset -cp 0,1 <pid> # 修改运行中进程
6.6 isolcpus
隔离 CPU,使其不参与常规调度:
bash
# 内核启动参数
isolcpus=2,3
# 使用隔离的 CPU
taskset -c 2,3 ./realtime_app
7. 多核调度
7.1 SMP 调度
每个 CPU 有独立的运行队列:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ CPU 0 │ │ CPU 1 │ │ CPU 2 │
├─────────┤ ├─────────┤ ├─────────┤
│ P1, P4 │ │ P2, P5 │ │ P3, P6 │
└─────────┘ └─────────┘ └─────────┘
7.2 负载均衡
调度器定期检查各 CPU 负载,进行进程迁移:
【负载均衡前】 【负载均衡后】
CPU0: ████████ (8) CPU0: ████ (4)
CPU1: ██ (2) CPU1: ████ (4)
CPU2: ██ (2) CPU2: ████ (4)
负载均衡时机:
- 周期性均衡
- CPU 空闲时拉取任务
- 进程唤醒时
- fork 时
7.3 调度域
按 CPU 拓扑结构分层:
Level 0: SMT (超线程) - 共享 L1/L2
Level 1: MC (多核) - 共享 L3
Level 2: NUMA - 本地内存
Level 3: 系统 - 所有 CPU
7.4 NUMA 调度
┌──────────────┐ ┌──────────────┐
│ NUMA 节点 0 │ │ NUMA 节点 1 │
│ CPU 0-3 │ ←──────────→│ CPU 4-7 │
│ 本地内存 A │ 互联(慢) │ 本地内存 B │
└──────────────┘ └──────────────┘
NUMA 感知调度:
- 尽量在本地节点运行进程
- 考虑内存位置进行迁移决策
8. cgroups 调度控制
8.1 cgroups 概念
cgroups 用于限制、记录和隔离进程组的资源使用:
/sys/fs/cgroup/
├── cpu/
│ ├── tasks
│ ├── cpu.shares
│ └── mygroup/
│ ├── tasks
│ └── cpu.shares
└── cpuset/
8.2 cpu.shares
相对权重,用于 CPU 资源分配:
bash
# 创建 cgroup
mkdir /sys/fs/cgroup/cpu/mygroup
# 设置权重
echo 1024 > /sys/fs/cgroup/cpu/mygroup/cpu.shares
# 添加进程
echo <pid> > /sys/fs/cgroup/cpu/mygroup/tasks
8.3 CFS 配额
bash
# 限制为 1 个 CPU
echo 100000 > cpu.cfs_period_us
echo 100000 > cpu.cfs_quota_us
# 限制为 0.5 个 CPU
echo 100000 > cpu.cfs_period_us
echo 50000 > cpu.cfs_quota_us
# 限制为 2 个 CPU
echo 100000 > cpu.cfs_period_us
echo 200000 > cpu.cfs_quota_us
8.4 cpuset
bash
# 绑定 CPU
echo 0-1 > /sys/fs/cgroup/cpuset/mygroup/cpuset.cpus
echo 0 > /sys/fs/cgroup/cpuset/mygroup/cpuset.mems
8.5 容器中的调度
bash
# Docker
docker run --cpus=2 --cpu-shares=512 --cpuset-cpus=0,1 myimage
# Kubernetes
resources:
requests:
cpu: "500m"
limits:
cpu: "1"
9. 调度性能分析
9.1 perf sched
bash
# 记录调度事件
perf sched record -- sleep 1
# 查看延迟统计
perf sched latency
# 查看时间线
perf sched map
perf sched timehist
9.2 /proc 信息
bash
# 进程调度统计
cat /proc/<pid>/sched
cat /proc/<pid>/schedstat
# 系统调度统计
cat /proc/schedstat
# 调度参数
ls /proc/sys/kernel/sched_*
9.3 上下文切换监控
bash
# 系统级
vmstat 1 # cs 列
# 进程级
pidstat -w 1
# 性能计数
perf stat -e context-switches ./program
9.4 运行队列监控
bash
# 负载平均
uptime
cat /proc/loadavg
# 实时监控
sar -q 1
10. 内核调度器内部
10.1 task_struct 调度字段
c
struct task_struct {
volatile long state; // 进程状态
int prio, static_prio; // 优先级
unsigned int rt_priority; // 实时优先级
const struct sched_class *sched_class;
struct sched_entity se; // CFS 调度实体
struct sched_rt_entity rt; // RT 调度实体
unsigned int policy; // 调度策略
cpumask_t cpus_allowed; // CPU 亲和性
};
10.2 调度类
c
// 调度类层次(优先级从高到低)
stop_sched_class // 停止 CPU
↓
dl_sched_class // DEADLINE
↓
rt_sched_class // FIFO/RR
↓
fair_sched_class // CFS
↓
idle_sched_class // IDLE
10.3 调度类接口
c
struct sched_class {
void (*enqueue_task)(rq, task, flags);
void (*dequeue_task)(rq, task, flags);
void (*yield_task)(rq);
struct task_struct *(*pick_next_task)(rq);
void (*put_prev_task)(rq, task);
void (*task_tick)(rq, task, queued);
};
10.4 schedule() 流程
c
void schedule(void) {
preempt_disable();
__schedule(false);
preempt_enable();
}
void __schedule(bool preempt) {
// 1. 获取当前 CPU 运行队列
rq = this_rq();
prev = rq->curr;
// 2. 选择下一个任务
next = pick_next_task(rq, prev);
// 3. 上下文切换
if (prev != next) {
context_switch(rq, prev, next);
}
}
11. 调度优化实践
11.1 CPU 密集型优化
bash
# 使用 SCHED_BATCH
chrt -b 0 ./cpu_bound_program
# 增大 nice 值(后台任务)
nice -n 10 ./program
# CPU 绑定
taskset -c 0-3 ./program
11.2 I/O 密集型优化
- 保持默认 SCHED_OTHER
- CFS 自动给予 I/O 密集型进程优先
- 不要绑定到单个 CPU
11.3 延迟敏感应用
bash
# 减小调度周期
echo 1000000 > /proc/sys/kernel/sched_latency_ns
# 隔离 CPU
# 内核参数: isolcpus=2,3
taskset -c 2,3 ./latency_app
# 禁用频率调节
cpupower frequency-set -g performance
11.4 实时应用清单
- 使用实时调度策略
- 锁定内存:
mlockall(MCL_CURRENT | MCL_FUTURE) - 隔离 CPU:
isolcpus=N nohz_full=N - 禁用中断
- 使用 PREEMPT_RT 内核
- 避免动态内存分配和非必要系统调用
11.5 常见问题诊断
| 问题 | 诊断 | 解决 |
|---|---|---|
| 高调度延迟 | perf sched latency |
减少进程数、提高优先级 |
| 频繁上下文切换 | vmstat 1, pidstat -w 1 |
优化锁、减少进程数 |
| CPU 负载不均 | mpstat -P ALL 1 |
调整亲和性 |
| 实时任务节流 | `dmesg | grep throttl` |
总结
Linux 调度器是一个复杂而精巧的系统,理解其底层原理对于系统性能优化至关重要。关键要点:
- CFS 是默认调度器,基于虚拟运行时间实现公平
- 实时调度 适用于时间敏感任务,但需要谨慎使用
- CPU 亲和性 可以优化缓存利用和 NUMA 访问
- cgroups 提供灵活的资源控制机制
- 性能分析 工具帮助定位调度问题
配套资源
https://download.csdn.net/download/weixin_43912621/92755817
