0/34 已展开

LLM 分析

sched: Flatten the pick - cgroup_mode +扁平 rq 分析

系列概况

  • 标题sched: Flatten the pick
  • 作者:Peter Zijlstra (Intel) peterz@infradead.org
  • 版本:v3,共 7 个 patch
  • 修改文件:include/linux/cpuset.h、include/linux/sched.h、kernel/cgroup/cpuset.c、kernel/sched/{core,debug,fair,pelt}.c、kernel/sched/sched.h
  • 代码统计:0/7 邮件给出 8 文件 +607/-488;fair.c 改动最重;patch 7/7 单条 803 增 / 475 删
  • Message-ID 起点20260605105513.354837583@infradead.org
  • 完整性:完整 v3 系列 + 6 条讨论回复 + Chen Yu u32 溢出 fix + 7 条 tip-bot2 自动合入通知 + 后续 s390 / wake_affine_weight 追讨

补丁目的

解决 cgroup 层级权重按 1/N^d 衰减导致的跨层级公平性问题;并在此基础上把 CFS 的 EEVDF 从多层 cfs_rq 折叠为 rq->cfs 单层队列,去掉 pick_next_task 路径上的中间 entity,提升 P99 延迟。

旧流程的问题

calc_group_shares() 按 CPU 比例分配 tg->shares,effective weight = W_g * F_g_n * (W_t/ΣW_tn),其中 average(F_g_n) ~ 1/N。深度 d 的 cgroup 树进一步出现 1/(N^d) 量级的偏差,加上 fixed-point 量化噪声,远端 cgroup 任务实际拿到的 CPU 远低于相对权重暗示;并且 EEVDF 在多层 rq 上挑选时,中间 group se 会遮挡真实任务,损害延迟。

新流程

  1. 引入 debugfs sched/debug/cgroup_mode,5 档可选:up / smp / max / concur / tasks
  2. calc_group_shares 改为 DEFINE_STATIC_CALL__sched_cgroup_mode_update() 运行时替换实现
  3. 新默认 concur,按 min(M, N) * shares(M=tasks、N=cpus)发放
  4. Patch 7/7 把 EEVDF rbtree 从 cgroup 内部挪到 rq->cfs,新增 se->h_load 表达层级有效 weight

Patch 概览

  • 1/7 Add cgroup_mode switch:debugfs 切换 + static_call 钩子
  • 2/7 Add cgroup_mode: up:F_g_n=1,全量下发
  • 3/7 Add cgroup_mode: max:N*shares,clip 到 nice -20;新增 cpuset_num_cpus
  • 4/7 Add cgroup_mode: concur:min(M,N)*shares;新增 tg_tasks / tg->runnable_avg
  • 5/7 Add cgroup_mode: tasks:M*shares
  • 6/7 Change default to concur:调整 switch 顺序让 case 2 默认
  • 7/7 sched/eevdf 单 rq:引入 se->h_loadcfs_rq->h_load,删除 find_matching_se

关键实现

struct sched_entity {
    struct load_weight load;
    struct load_weight h_load;   /* 层级有效 weight */
    ...
};
Layered cfs_rq (old)

  R
  |- G1(se)
  |   |- G2(se) -- T1(se)
  |               \- T2(se)
  |- G3(se)
      \- T3(se)

Flat rq->cfs (after Patch 7/7)

  rq->cfs (single EEVDF tree)
   |--- T1
   |--- T2
   |--- T3

cgroup_mode switch (debugfs)
   0: up      -- F_g_n' = 1
   1: smp     -- current formula
   2: concur  -- min(M,N) * shares   (new default)
   3: max     -- N * shares clip -20
   4: tasks   -- M * shares

__calc_smp_shares(cfs_rq, tg_shares, shares_max) 是各 mode 公用底座,结果夹在 [MIN_SHARES, shares_max]concurtg_tasks(tg)tg->runnable_avg >> SCHED_CAPACITY_SHIFT,与 tg_cpus(tg) 取小后乘 shares。Patch 7/7 在 __enqueue_entity 增加 WARN_ON_ONCE(&rq_of(cfs_rq)->cfs != cfs_rq) 强制所有 EEVDF 操作集中在 rq->cfsfind_matching_se 整段删除,pick_next_task 不再跨层调度。

类比

把 cgroup 想成"集团-分公司-部门"三级预算分配:

  • 老办法像按全集团人头数均分公司预算——部门越大越被摊薄,深处的部门几乎分不到钱
  • concur 模式按"实际在岗项目数和一台机器能装下的最大人数"双封顶发钱,项目少时按项目数、人多时按机器数,永远保证每一级拿到公平的筹码
  • 扁平 rq 像把部门排班表直接挂到集团总册,集团总裁只看一份排班表就决定谁先上工,不用层层审批 pick_next_task 的中间节点

Highlight:风险与注意点

  • update_tg_cfs_runnableu32 new_sum = se->avg.runnable_avg * divider 在大 weight 下溢出,Chen Yu C 以 u64 new_sum 修复,commit 4f166adb5cb0 已合入 sched/core
  • wake_affine_weight 改为读 task_h_load(p) 后,与 this_cpu/prev_cpu 的 cpu_load 比较更"诚实",s390 + WA_WEIGHT 跑 netperf 时 this_cpu 偏好下降、L2 miss% 上升、吞吐下滑(见 0/7后续回复链)
  • cpuset_num_cpus 仅覆盖 cgroup-v2 的 effective_cpus;v1、arm64 task_cpu_possible_mask() 受限子集暂不支持,Waiman Long 建议加注释明确边界
  • __sched_cgroup_mode_update()sched_dynamic_update() 同入口串联调用,运行中切换窗口须保证 static_call 写入顺序
  • Patch 7/7 遗留 TODO:拆分 struct cfs_rq / struct sched_entity、去掉 h_curr
  • s390 hackbench + stress-ng 并跑在低 thread 数下退化;Prateek 建议重测 68e3748781 "Fix flat hierarchy" 之后的 tip 才能下结论
  • 0day 机器人曾抓出 patch 6/7 引起的"任务变重、ksoftirqd 调度减少"的网络吞吐回归,Chen Yu 提议降低 ksoftirqd slice 但尚未落地

版本变化

v2 -> v3(0/7 邮件 changelog):

  • 合并 debug / prep patches
  • Vincent Guittot:修复 update_entity_lag() on dequeue
  • Prateek:修复 throttle vs tick
  • Prateek:修复 wakeup_preempt_fair()
  • rebase 到 tip/sched/core
  • 重写 cgroup_mode changelog
  • rework cgroup_mode concur
  • 新增 cgroup_mode tasks
  • 默认 cgroup_mode 改为 concur

一句话总结

Peter Zijlstra 用 5 档可切换的 cgroup_mode 把层级权重从 1/N^d 拉回 1,再用 sched/eevdf 单 rq 化去掉 pick 路径上的中间 entity,并修掉了 update_tg_cfs_runnable 的 u32 溢出与新权重下 wake_affine_weight 的回归。