【Kernel】Linux CFS(完全公平调度器)实现原理与机制
Linux CFS(完全公平调度器)实现原理与机制
目标:系统讲解 CFS 的核心理念、关键数据结构、主要函数路径与抢占/选取规则,结合源码位置与图示帮助快速理解与复核。
代码树:
kernel-4.4.94。
1. 设计理念与核心概念
- 目标:以“虚拟运行时间”(
vruntime)度量公平,将每个可运行实体按权重在时间上趋于平均。 - 核心思想:
- 按任务权重(
nice与组权重)累积vruntime:运行越久,vruntime增长越多;权重越大,单位真实时间贡献到vruntime的增量越小(体现更高权重的任务更“慢”积累,因而更频繁被选中,以保持公平)。 - 在每 CPU 的
cfs_rq红黑树上,选择vruntime最小的实体运行(红黑树最左节点)。
- 按任务权重(
- 重要参数与影响:
sched_latency:期望在一个窗口内让所有任务都至少运行一次。min_granularity:最小运行粒度,防止任务过短导致调度开销过高。wakeup_granularity:唤醒抢占的“跨越阈值”,防止过度抢占。
1.1 权重与 vruntime 的关系(为什么“权重高更慢积累”会更公平)
- CFS 用
vruntime作为排序依据:vruntime越小越容易被选中(红黑树最左)。 - 运行时给
vruntime的增量与权重成“反比”,典型近似公式:delta_vruntime = delta_exec * NICE_0_LOAD / se->load.weightdelta_exec:本次实际运行的真实时间NICE_0_LOAD:nice=0 的基准权重(典型为 1024)se->load.weight:该任务权重(由 nice 映射;nice 越小权重越大)
- 解释:权重高(nice 小)的任务在相同真实时间内,
vruntime增量更小;因此它更长时间停留在“更左”的位置,更频繁被选中运行,最终实现“按权重分配”真实 CPU 时间的公平。
示例(两任务 A/B):
- A:nice=0 →
weight_A = 1024 - B:nice=+5 →
weight_B ≈ 335 - 每次运行 1ms 的
vruntime增量:- A:
delta_vruntime_A = 1ms * 1024 / 1024 = 1ms - B:
delta_vruntime_B = 1ms * 1024 / 335 ≈ 3.06ms
- A:
- 直觉:A 增长慢(更常在左边被选),B 增长快(更快“被推到右边”),长期真实 CPU 时间比例约为
1024 : 335(约 75% : 25%)。
补充:
- CFS 不用固定时间片;而是用
min_granularity保证最小运行时间,并用sched_latency控制一个窗口内的“轮转期望”。 - 在一个窗口中,每任务“应得运行量”近似于
sched_period * (weight_i / total_weight);实际选取则由vruntime排序推动,二者一致性通过“慢积累→更常被选”的机制实现。
2. 数据结构与关键字段(kernel/sched/fair.c)
struct cfs_rq(每 CPU 的 CFS 运行队列):- 红黑树:存放
struct sched_entity(简称se)集合,按se->vruntime排序。 min_vruntime:队列的最小vruntime基线,用于放置新实体与归一化比较。- 统计字段:负载、权重总和等,用于计算 slice 与迁移决策。
- 红黑树:存放
struct sched_entity(任务或组的调度实体):vruntime:虚拟运行时间,调度公平性的核心度量。- 链到红黑树的
rb_entry。 - 与
task_struct关联(或与调度组cgroup的层级实体关联)。
3. 主要函数路径与行为
- 入队:
enqueue_task_fair(rq, p, flags)(约 4160–4260)- 更新统计、调用层级的
enqueue_entity(),将se插入红黑树;按flags(如ENQUEUE_WAKEUP)标记唤醒场景。
- 更新统计、调用层级的
- 出队:
dequeue_task_fair(rq, p, flags)- 从红黑树移除实体,更新队列统计。
- 放置:
place_entity(cfs_rq, se, initial)(约 2950–3010)- 以
cfs_rq->min_vruntime为基线;首次入队可能应用START_DEBIT的“预借”,避免新任务过度领先;唤醒场景按sched_latency做温和奖励(GENTLE_FAIR_SLEEPERS)。
- 以
- 选择:
pick_next_task_fair(rq)(约 5280–之后)- 选取红黑树的最左节点(最小
vruntime的实体),并设置为当前运行实体(set_next_entity())。
- 选取红黑树的最左节点(最小
- 抢占:
check_preempt_wakeup(rq, p, wake_flags)(约 5120–5360)- 若当前为
SCHED_IDLE而新任务非 idle,必抢占; - 比较
vdiff = curr->se.vruntime - p->se.vruntime与wakeup_granularity(经wakeup_preempt_entity),差距足够则resched_curr(rq); - 启发式
NEXT_BUDDY/LAST_BUDDY促进交互响应与局部性。
- 若当前为
- 更新:
update_curr(cfs_rq)/entity_tick(cfs_rq, se)/task_tick_fair(rq, p, current)- 在时钟 tick 中为当前实体累积
vruntime;必要时触发重插入或切换。
- 在时钟 tick 中为当前实体累积
- 选核:
select_task_rq_fair(p, ...)(kernel/sched/fair.c与kernel/sched/core.c协同)- 在 SMP 下依据均衡域选择合适 CPU,考虑负载与亲和性;
wake_up_new_task()中也会进行SD_BALANCE_FORK路径的选核。
- 在 SMP 下依据均衡域选择合适 CPU,考虑负载与亲和性;
4. CFS 的抢占与响应性机制
- 唤醒抢占:
- 新任务被唤醒时,与当前运行任务比较
vruntime差值,若跨越wakeup_granularity则触发立刻抢占,保障交互式任务响应。
- 新任务被唤醒时,与当前运行任务比较
- 轮转与时间片:
- CFS不使用固定时间片;以
min_granularity控制最小运行时间,配合sched_latency与权重确保在一个窗口内各任务获得运行机会。
- CFS不使用固定时间片;以
- 组与权重:
- 任务所属组按照权重分配时间份额;组内再按成员权重分配,形成层级公平。
5. 机制图(SVG)与树形示意
- 队列与选取(红黑树最左):

- 唤醒抢占与
wakeup_granularity:

6. 关键参数与调优提示
sched_latency_ns:窗口长度;任务数多时会自动扩展以保证每任务至少一次运行机会。sched_min_granularity_ns:最小粒度,避免过短运行导致调度开销偏高。sched_wakeup_granularity_ns:唤醒抢占阈值;越大越保守,越小越激进(更快抢占)。sched_child_runs_first与START_DEBIT:影响新任务首次放置与响应性。- 交互工作负载优化:适度降低
wakeup_granularity,启用LAST/NEXT_BUDDY有助于提升响应。
7. 源码索引(4.4.94)
kernel/sched/fair.cplace_entity()(~2994)enqueue_task_fair()(~4184) /dequeue_task_fair()check_preempt_wakeup()(~5168)pick_next_task_fair()(~5280+)update_curr()/entity_tick()/task_tick_fair()
kernel/sched/core.ccheck_preempt_curr()(~1027)wake_up_new_task()(~2390–2460)select_task_rq()(~1638)
include/linux/sched.h/kernel/sched/sched.h- 调度类定义、
struct rq/cfs_rq/sched_entity等结构。
- 调度类定义、
8. 小结
- CFS 以
vruntime作为公平性核心,围绕红黑树最左选取与权重归一,在sched_latency窗口内实现相对公平的 CPU 时间分配; - 唤醒抢占与最小粒度共同平衡响应性与吞吐;
- 理解关键函数与参数能帮助调试“任务响应慢”和“调度抖动”等问题。
