线上 CPU 打满、延迟抖动、D 状态误判,根因常在调度路径。本文沿 schedule() → __schedule() → CFS pick_next_entity() 走通一次选路,用真实文件定位 vruntime 与红黑树,方便对照 perf sched / schedstat 验证。
源码锚点
| 路径 | 作用 |
|---|---|
kernel/sched/core.c |
schedule()、__schedule()、上下文切换入口 |
kernel/sched/fair.c |
CFS:entity_tick、update_curr、pick_next_entity |
include/linux/sched.h |
task_struct、sched_entity |
include/linux/sched/sysctl.h |
调度相关 sysctl |
Documentation/scheduler/sched-design-CFS.rst |
CFS 设计说明 |
核心结构(节选概念):
c
/* include/linux/sched.h --- sched_entity */
struct sched_entity {
struct load_weight load;
struct rb_node run_node;
u64 vruntime;
/* ... */
};
struct task_struct {
struct sched_entity se;
const struct sched_class *sched_class;
/* ... */
};
选路骨架:
c
/* kernel/sched/core.c --- 概念路径 */
asmlinkage __visible void __sched schedule(void)
{
struct task_struct *tsk = current;
sched_submit_work(tsk);
__schedule(SM_NONE);
sched_update_worker(tsk);
}
调用链
text
用户态阻塞 / 时间片耗尽 / 抢占点
→ schedule() /* core.c */
→ __schedule(sched_mode)
→ pick_next_task(rq, prev, &rf)
→ 按优先级扫 sched_class
→ fair_sched_class.pick_next_task
→ pick_next_entity(cfs_rq)
→ 红黑树最左 = 最小 vruntime
→ context_switch(prev, next)
→ switch_mm / switch_to
时钟滴答侧:
text
scheduler_tick
→ task_tick_fair
→ entity_tick → update_curr
→ 累加 delta_exec,换算进 se->vruntime
重点知识
1. vruntime 是公平的尺子
CFS 不按固定时间片轮转,而按 虚拟运行时间 排序。nice 通过权重放大/缩小同一墙上时间对应的 vruntime 增量:nice 越小,同等墙钟时间里 vruntime 涨得越慢,树中越靠左,越容易被再次选中。
2. 红黑树只存可运行实体
cfs_rq->tasks_timeline 上每个可运行 sched_entity 一个节点;pick_next_entity 取最左。入队 enqueue_entity、出队 dequeue_entity 必须与状态机一致,否则会出现「看起来 RUN 却不在树上」。
3. 调度类分层
stop > dl > rt > fair > idle。普通业务几乎都在 fair;实时任务走 rt/dl,不会与 CFS 争同一把 vruntime 尺。排障时先分清任务是否被绑到 RT。
4. 配置与观测
bash
# 调度统计(需 CONFIG_SCHEDSTATS)
grep -E 'sched_' /proc/self/sched
cat /proc/schedstat | head
# 延迟剖析
perf sched record -a -- sleep 5
perf sched latency
# nice / 亲和
chrt -m
taskset -pc <pid>
常见 sysctl(名称随内核略有差异,以本机 sysctl -a | grep sched 为准):kernel.sched_latency_ns、kernel.sched_min_granularity_ns。
5. 设计意图与踩坑
- 意图:多任务长期公平,短任务延迟可控。
- 坑 :把
D(TASK_UNINTERRUPTIBLE)当 CPU 忙------它常在等 I/O,加核无效。 - 坑 :容器未设
cpu.max/ cpuset,与宿主机抢同一 CFS 池。 - 坑:持锁过久导致调度延迟,表现为尾延迟而非平均 CPU。
Checklist
- 能指出
schedule→__schedule→pick_next_task→ CFS 的文件位置 - 解释 vruntime 与 nice 权重的关系,并用
/proc/<pid>/sched对照 - 区分 fair / rt 调度类,避免用 CFS 参数调实时任务
- 用
perf sched latency或schedstat看到排队延迟 - 排查高 load 时先区分 RUN 排队 vs D 状态等盘
- 容器场景检查 cpu / cpuset 限额,避免无界争抢