sched discussion
[PATCH 0/2] sched/fair: Use cfs_rq->h_curr in the bandwidth paths
LLM 分析
sched/fair:在带宽路径中使用 cfs_rq->h_curr
系列概况
- 标题:
[PATCH 0/2] sched/fair: Use cfs_rq->h_curr in the bandwidth paths - 作者:Wanwu Li liwanwu@kylinos.cn
- 版本:v1(只有一版,提交即被合入)
- 规模:2 个 patch,共 6 行改动(3 增 / 3 删)
- 修改文件:
kernel/sched/fair.c - 代码统计:
1 file changed, 3 insertions(+), 3 deletions(-) - Message-ID:
<20260831101141.391382-1-liwanwu@kylinos.cn> - 完整性:完整 thread。包含 0/2 封面说明、1/2 与 2/2 两个 patch、Aaron Lu 的 Reviewed-by 与 Tested-by 回复、作者致谢回信,以及 tip-bot2 在 9 月 2 日发布的两个
sched/urgent入树通知。
补丁目的
EEVDF 重构为单一 runqueue(85570f10a4c6 sched/eevdf: Move to a single runqueue)之后:
cfs_rq->curr只在根 cfs_rq 上维护,由set_next_task_fair()/put_prev_task_fair()设置和清除。cfs_rq->h_curr才是每一层级 cfs_rq 上"当前正在运行的实体",由set_next_entity()在每一层级设置。
带宽控制(throttle / distribute)路径仍然按层级遍历 cgroup。本 series 修正两个在迁移时漏改的、把 cfs_rq->curr 当成"本层级是否有任务在跑"来使用的旧代码点:
throttle_cfs_rq():本层级有任务在跑时,应当请求一整片sched_cfs_bandwidth_slice()的运行时间,并通过task_throttle_setup_work()注册延迟 throttle 的 task_work。原先的cfs_rq->curr在 cgroup 层级永远是 NULL,于是 cgroup 永远只拿到 1ns 运行时间,延迟 throttle 也不会被安装,导致运行中的任务可以跑超配额,直到下一次 pick 才补上 throttle。distribute_cfs_runtime():用来决定是否在分发带宽前刷新 rq clock 并调用update_curr()做运行时间记账。原本的判断条件cfs_rq->curr在 cgroup cfs_rq 上恒为假,整段刷新逻辑成了死代码。
旧流程的问题
[cgroup cfs_rq] [root cfs_rq]
curr == NULL (always) curr != NULL
| |
v v
throttle_cfs_rq() check throttle_cfs_rq() check
target_runtime = 1 (BUG) target_runtime = slice (OK)
no task_throttle_setup_work install task_throttle_setup_work
| |
v v
task overruns quota until proper throttling
next pick arms the work
distribute_cfs_runtime():
if (cfs_rq->curr) -> never fires for cgroup
-> update_rq_clock() + update_curr() become dead code
distribute_cfs_runtime() 一侧同理:cfs_rq->curr 在 cgroup 层级永远为假,update_rq_clock() + update_curr() 永远不执行。
新流程
[cgroup cfs_rq] [root cfs_rq]
h_curr == group entity h_curr == task / group entity
| |
v v
throttle_cfs_rq() check throttle_cfs_rq() check
target_runtime = slice (OK) target_runtime = slice (OK)
install task_throttle_setup_work install task_throttle_setup_work
distribute_cfs_runtime() distribute_cfs_runtime()
if (h_curr) -> update_rq_clock() if (h_curr) -> update_rq_clock()
-> update_curr() -> update_curr()
简单说:把带宽路径里"本层级是否有实体在跑"的判断,从根专属的 cfs_rq->curr 切到逐层级维护的 cfs_rq->h_curr。
关键实现
Patch 1/2 — throttle_cfs_rq()
static bool throttle_cfs_rq(struct cfs_rq *cfs_rq)
{
struct sched_entity *curr = cfs_rq->h_curr; /* was cfs_rq->curr */
/* If cfs_rq->h_curr is still runnable, we are here from an ... */
...
if (curr) {
/* request sysctl_sched_cfs_bandwidth_slice worth of bandwidth */
target_runtime = cfs_b->period;
...
task_throttle_setup_work(curr, cfs_rq, false);
} else {
target_runtime = 1;
}
}
要点:当本层级确实有在跑实体(h_curr 非空)时,把目标运行时间设成一个完整 slice 并安排延迟 throttle;否则只给 1ns 占位。
Patch 2/2 — distribute_cfs_runtime()
static bool distribute_cfs_runtime(struct cfs_bandwidth *cfs_b)
{
...
for_each_throttled_cfs_rq(cfs_b, cfs_rq) {
...
if (cfs_rq->h_curr) { /* was cfs_rq->curr */
update_rq_clock(rq);
update_curr(cfs_rq);
}
}
}
要点:只有在 throttled 层级当前还有实体真正在跑(处于延迟 throttle 窗口里)的时候,才刷新 rq clock 并做运行时间记账。
Patch 概览
+------+-------------------------------+--------------------------+--------------------------------+
| Patch | Location | Change | Effect |
+------+-------------------------------+--------------------------+--------------------------------+
| 1/2 | throttle_cfs_rq() | curr = cfs_rq->curr | cgroup quota exhausted: |
| | curr = cfs_rq->curr | -> cfs_rq->h_curr | correctly request a full |
| | comment updated | | slice and arm deferred |
| | | | throttle task_work |
+------+-------------------------------+--------------------------+--------------------------------+
| 2/2 | distribute_cfs_runtime() | if (cfs_rq->curr) | runtime consumed in the |
| | if (cfs_rq->curr) | -> if (cfs_rq->h_curr) | deferred-throttle window is |
| | | | docked before redistribution |
+------+-------------------------------+--------------------------+--------------------------------+
类比
把 cgroup 的层级 runqueue 想成一座办公楼:
cfs_rq->curr是"正门接待台显示的当前来访者",只有一楼(根 cfs_rq)有人值班,其它楼层永远是空的。cfs_rq->h_curr才是每层楼的楼层接待员,看到的才是"本层此刻正在开会的那个人"。
带宽路径里,旧代码只看正门显示(curr),于是二楼的会议室真在开会,却被误判为"没人",配额没按时扣掉,会议室用着用着就超支了。改成看楼层接待员(h_curr)后,二楼也能正确判断有人,节流和记账都恢复工作。
Highlight:风险与注意点
- cfs_rq->h_curr 的 TODO:commit
85570f10a4c6的注释里留了 TODO,希望以后彻底干掉cfs_rq->h_curr,统一回到单一 runqueue 模型。本系列是过渡方案:如果上游后续做了 h_curr 的整合重构,需要把这 6 行改动一并重新审视。 - 回归测试范围:Aaron Lu 用一个 affined 到单 CPU 的 nop 任务复现了周期性超配额;这是个相对薄的负载模型,建议补充多核、嵌套 cgroup 以及 CPU hotplug / cpufreq 切换场景下的带宽控制回归。
- 公平 vs 及时性:
distribute_cfs_runtime()的刷新只是在延迟 throttle 窗口里补登运行时间,作者明确指出当前靠28ad5427682b在 unthrottle 时无条件update_curr()才不至于出现正确性漏洞——属于"被另一个 patch 兜着"的隐含依赖,需要持续跟踪其有效性。 - 审计完整性:作者声明已经审计过
kernel/sched/fair.c中所有cfs_rq->curr引用,确认只有这两处仍按层级跑。其余读者要么只在根 cfs_rq,要么已经走h_curr。审阅者应抽查这条断言。
版本变化
只有一个 v1,无 v2。但 thread 里能直接看到一次"提交 -> 进入 tip/sched/urgent"的快路径:
- 8/31:作者发 v1;Aaron Lu 当晚给出 Reviewed-by,并补充 Tested-by(nop 单 CPU 亲和复现 + 验证修复后不再超配额)。
- 9/1:作者回信确认这就是出问题的"延迟 throttle 逃逸"路径。
- 9/2:Peter Zijlstra 通过 tip 把两个 patch 分别以 commit
f8610c57f4078c63d1d4e2f3d7134f3dc1768403(patch 1/2)和b038383526d8c7883ea0486dd1911102b6dda414(patch 2/2)推入sched/urgent分支。
一句话总结
EEVDF 单 runqueue 化后 cfs_rq->curr 只在根 cfs_rq 上有意义,作者把 bandwidth 路径里两处仍按层级跑的 cfs_rq->curr 换成 cfs_rq->h_curr,让 cgroup 配额耗尽能正确请求整片 slice 并安装延迟 throttle,同时恢复 distribute_cfs_runtime() 中本应执行的 clock refresh 与运行时间记账。