一、CFS的设计哲学:颠覆传统的O(1)调度器
2.6.23内核引入的完全公平调度器(Completely Fair Scheduler, CFS)是Linux调度史上的里程碑。它摒弃了传统的时间片分配,转而采用一个极其优雅的抽象:每个进程按虚拟运行时间(vruntime)推进,红黑树最左侧节点即为最饥饿的进程。
// 核心调度实体 struct sched_entity
struct sched_entity {
struct load_weight load; // 权重,优先级映射
struct rb_node run_node; // 红黑树节点
u64 exec_start;
u64 sum_exec_runtime; // 实际运行时间
u64 vruntime; // 虚拟运行时间
u64 prev_sum_exec_runtime;
};
核心公式简而言之:vruntime += (实际运行时间 × NICE_0_LOAD) / 权重。优先级越高(weight越大),vruntime增长越慢,获得越多CPU时间。
二、调度核心数据结构
2.1 红黑树与cfs_rq
CFS在每个CPU运行队列上维护一棵红黑树,以vruntime为key。最左侧叶子节点拥有最小vruntime,即被选中执行。为加速访问,内核缓存rb_leftmost指针。
struct cfs_rq {
struct load_weight load;
unsigned int nr_running;
u64 min_vruntime; // 队列最小vruntime,用于新进程初始化
struct rb_root_cached tasks_timeline; // 红黑树根
struct sched_entity *curr; // 当前运行实体
};
2.2 优先级与权重的映射
内核使用PRI与nice值的线性映射,每级nice值相差约10%的CPU配额。sched_prio_to_weight[]数组存储每级nice值对应的权重,NICE_0_LOAD = 1024。
三、从schedule()到pick_next_task()的完整调度路径
触发调度的入口__schedule()比较关键。它调用pick_next_task()遍历调度类,优先级依次为stop → dl → rt → fair → idle。
static inline struct task_struct *
pick_next_task(struct rq *rq)
{
if (likely(!sched_class_above(&fair_sched_class)))
return pick_next_fair(rq); // CFS快速路径
// 否则遍历所有调度类
for_each_class(class) {
p = class->pick_next_task(rq);
if (p) return p;
}
}
选中最左侧vruntime节点后,set_next_task()更新exec_start和prev_sum_exec_runtime。注意vruntime只在sched_tick()的周期性更新。
四、多核负载均衡与HMP/EAS
4.1 Sched Domain层次
内核组织NUMA、SMT、MC等多级调度域。负载均衡自底向上执行。load_balance()检查域内CPU负载不均衡时,拉取迁移任务。迁移成本预存于sched_domain.level_cost避免在高开销域间迁移。
4.2 heterogeneous多核(ARM big.LITTLE)的EAS
Energy-Aware Scheduling根据OPP(Operating Performance Point)能量模型计算能效最优的CPU,避免将所有高功耗任务放在小核上反而延迟高。
五、cgroup带宽控制:cpu.cfs_quota_us的精准配额
// CFS bandwidth控制
struct cfs_bandwidth {
ktime_t period; // 默认100ms
u64 quota; // cgroup内所有进程在period内的总配额
struct hlist_head throttled_cfs_rq;
};
创建子cgroup,写入/sys/fs/cgroup/cpu/yourgroup/cpu.cfs_quota_us即可限制该组的总CPU配额。
六、性能调优黄金参数
| 参数 | 含义 | 建议 |
|---|---|---|
| sched_latency_ns | 调度周期 | 默认6ms,受最小粒度grain保护 |
| sched_migration_cost | 迁移成本判定阈值 | 0.5ms,影响负载均衡敏感性 |
| sched_min_granularity_ns | 最小执行时间 | 防止过度抢占 |
| sched_wakeup_granularity_ns | 唤醒抢占粒度 | 控制唤醒抢占激进性 |
| sched_nr_migrate | 单次load_balance迁移数量 | NUMA敏感场景可调低 |
七、perf与ftrace实战:调度延迟追踪
# 跟踪调度延迟
perf sched record -a sleep 10
perf sched latency --sort max
# events
echo 1 > /sys/kernel/debug/tracing/events/sched/sched_switch/enable
echo 1 > /sys/kernel/debug/tracing/events/sched/sched_wakeup/enable
八、常见问题排查菜单
- 负载高但CPU空闲:查看D state进程不可中断睡眠,iostat排查I/O阻塞
- vruntime急剧漂移:检查跨CPU的vruntime补偿机制
place_entity() - NUMA远程访问延迟:
numastat观察命中率 - 实时任务抢占失效:确认RT throttle未关闭导致CFS饥饿
九、结语
从O(1)到CFS,Linux调度器完成了从硬件时间vruntimenian的哲学转变。EAS让能效调度智能化,cgroup带宽让资源隔离精细化。理解vruntime本质就是理解Linux如何"真实"公平地分配最珍贵的资源——时间。

发表评论 取消回复