引言
进程调度是操作系统最核心的组件之一,它决定了哪个进程在何时获得 CPU 时间片。Linux 内核调度器经过二十余年的演进,从早期的 O(n) 调度器、O(1) 调度器,到 2007 年引入的 CFS(完全公平调度器),再到 2023 年 Linux 6.6 版本中被 EEVDF(Earliest Eligible Virtual Deadline First)取代,每一次变革都在解决实际生产环境中的调度延迟与公平性问题。
本文将从调度器理论基础出发,深入剖析 CFS 的实现原理、实时调度类的工作机制、EEVDF 的核心创新,并提供从内核参数调优到应用程序绑核的全链路实战指南。
一、Linux 调度器架构总览
1.1 调度策略与优先级模型
Linux 内核定义了五种调度策略,按优先级从高到低排列如下:
| 调度策略 | 类型 | 说明 |
|---|---|---|
| SCHED_DEADLINE | 实时 - 截止时间调度 | 基于 EDF(最早截止时间优先),Linux 3.14 |
| SCHED_FIFO | 实时 - 先进先出 | 高优先级可抢占低优先级,无时间片限制 |
| SCHED_RR | 实时 - 轮转 | 类似 FIFO 但同优先级按时间片轮转 |
| SCHED_OTHER / SCHED_NORMAL | 普通 - CFS | 默认策略,CFS 完全公平调度 |
| SCHED_IDLE | 普通 - 空闲 | 仅当无其他进程时才运行 |
内核中调度类的优先级通过链表串联:stop_sched_class → dl_sched_class → rt_sched_class → fair_sched_class → idle_sched_class。每个 CPU 的 runqueue 按此顺序遍历,确保高优先级调度类先获得调度机会。
1.2 调度器的核心数据结构
在 6.x 内核中,每个 CPU 的运行队列 struct rq 包含多个子队列:
struct rq {
struct cfs_rq cfs; // CFS 运行队列
struct rt_rq rt; // 实时运行队列
struct dl_rq dl; // DEADLINE 运行队列
struct task_struct *curr; // 当前运行进程
struct task_struct *idle; // idle 进程
unsigned int nr_running; // 可运行进程数
// ...
};
二、CFS 完全公平调度器深度解析
2.1 vruntime 与红黑树机制
CFS 的核心思想是维护每个进程的虚拟运行时间(vruntime),选择 vruntime 最小的进程运行。所有可运行进程按照 vruntime 值存储在一棵红黑树(rbtree)中,最左端节点即为最应该被调度的进程。
// 内核中 vruntime 的计算公式
void update_curr(struct cfs_rq *cfs_rq) {
struct sched_entity *curr = cfs_rq->curr;
u64 now = rq_clock_task(rq_of(cfs_rq));
u64 delta_exec;
delta_exec = now - curr->exec_start; // 实际执行时间
curr->exec_start = now;
curr->sum_exec_runtime = delta_exec;
// 根据进程权重将实际时间转换为虚拟时间
curr->vruntime = calc_delta_fair(delta_exec, curr);
update_min_vruntime(cfs_rq);
}
关键设计:nice 值每降低 1 级(优先级提高),获得 1.25 倍的 CPU 时间权重。这是因为内核使用 1.25^n 的权重比例表(sched_prio_to_weight[]),nice 0 的权重为 1024,nice -1 的权重为 1277,比值恰好约为 1.25。
2.2 调度粒度与延迟控制
CFS 通过三个 sysctl 参数控制调度行为:
# 调度最小粒度(每次切换的最小时间片)
kernel.sched_latency_ns = 24000000 # 24ms(默认)
kernel.sched_min_granularity_ns = 3000000 # 3ms
kernel.sched_wakeup_granularity_ns = 4000000 # 4ms
# 调度周期 = max(latency_ns, min_granularity * nr_running)
// 当进程数过多时,周期自动扩展,避免频繁切换
wakeup_granularity 是新进程唤醒抢占的重要参数:只有当新进程的 vruntime 比当前进程小超过该阈值时才发生抢占,防止过于频繁的上下文切换。
2.3 CFS 组调度(Group Scheduling)
CFS 通过 CONFIG_FAIR_GROUP_SCHED 支持组调度,允许将 CPU 时间按比例分配给进程组。组调度的核心数据结构是嵌套的 sched_entity:每个进程是一个 sched_entity,cgroup 本身也是一个 sched_entity,形成多层级的调度树。
cgroup v1 中通过 cpu.shares 控制组的 CPU 权重比例,cpu.cfs_quota_us 限制组的 CPU 使用上限:
# 创建一个 cgroup,分配 50% CPU 时间
mkdir /sys/fs/cgroup/cpu/mygroup
echo 512 > /sys/fs/cgroup/cpu/mygroup/cpu.shares # 512/1024 = 50%
echo 50000 > /sys/fs/cgroup/cpu/mygroup/cpu.cfs_quota_us # 每 100ms 周期内最多用 50ms
echo 100000 > /sys/fs/cgroup/cpu/mygroup/cpu.cfs_period_us
三、实时调度类详解
3.1 SCHED_FIFO 与 SCHED_RR
实时进程的优先级数值为 0-99(数值越大优先级越高),永远优先于普通进程。使用 chrt 命令可以设置进程的实时调度策略:
# 将进程 1234 设为 SCHED_FIFO,优先级 80
sudo chrt -f 80 -p 1234
# 启动新进程为 SCHED_RR,优先级 50
sudo chrt -r 50 ./my_rt_program
# 查看进程调度策略
chrt -p 1234
SCHED_FIFO 的特点:一旦获得 CPU,除非主动阻塞(如等待 I/O、睡眠)、更高优先级实时进程抢占、或主动调用 sched_yield(),否则永远运行下去。这也是为什么编写不当时可能导致系统锁死。
3.2 SCHED_DEADLINE — 基于 EDF 的硬实时调度
Linux 3.14 引入的 SCHED_DEADLINE 是最精确的实时调度策略,每个进程声明三个参数:
- runtime (Q):每次激活最多运行的时间
- deadline (D):每次激活必须完成的截止时间
- period (P):激活周期的长度(Q ≤ D ≤ P)
// 使用 SCHED_DEADLINE 的代码示例
#include

发表评论 取消回复