sched-ext discussion
[PATCH RFC] sched_ext: warn when cpu.max is set but the BPF scheduler doesn't implement bandwidth control
LLM 分析
sched_ext:当 cpu.max 配置但 BPF 调度器未实现带宽控制时的告警
系列概况
- 标题:[PATCH RFC] sched_ext: warn when cpu.max is set but the BPF scheduler doesn't implement bandwidth control
- 作者:Tao Cui cuitao@kylinos.cn
- 版本:v1(RFC,单 patch)
- 规模:1 个文件,+6 行
- 修改文件:
kernel/sched/ext/ext.c(scx_group_set_bandwidth()函数内) - 代码统计:1 file changed, 6 insertions(+)
- Message-ID:
20260818135328.174152-1-cui.tao@linux.dev - 完整性:完整可读,含 diff 与 commit message
补丁目的
cgroup v2 的 cpu.max 接口在内核中被 sched_ext(scx)记录到 task_group,并通过 ops.cgroup_set_bandwidth() 回调和 scx_cgroup_init_args 推送给 BPF 调度器。
但内核本身并不强制执行该配额——它依赖 BPF 调度器去兑现。
实际问题是:树内几乎所有示例调度器(如 scx_simple)都没有实现 cgroup_set_bandwidth(),只有 scx_qmap 实现了(但只是 bpf_printk() 参数)。这意味着当用户给 cgroup 写 cpu.max = "50000 100000" 时,他们以为限制了 50% CPU,但实际却不受约束。
作者实测:单 busy任务 10 秒用掉 9946ms CPU,nr_throttled 仍为 0。
补丁目的:在 quota 为有限值、scx 已启用、但当前 BPF 调度器未实现该回调时,打印一次告警,提醒用户/编排器配额未被强制执行。
旧流程的问题
- 用户/编排器配置
cpu.max→ 内核存储到task_group→ 调用 BPF 回调- 如果 BPF 调度器未实现ops.cgroup_set_bandwidth()→ 内核SCX_CALL_OP什么也不做 - 用户看到的是「我已经设置了50% 配额」,但实际上 cgroup 仍能跑满 CPU——静默失败
user writes cpu.max=50000 100000
|
v
task_group stores period/quota/burst
|
v
SCX_CALL_OP(cgroup_set_bandwidth) --+--> if scheduler implements: printk only (scx_qmap)
|
+--> if missing: silently NO-OP <-- silent failure
新流程
补丁在原有 if 分支之外加了一条 else if:
else if (scx_cgroup_enabled && sch &&
!SCX_HAS_OP(sch, cgroup_set_bandwidth) &&
quota_us != RUNTIME_INF)
pr_warn_once("sched_ext: BPF scheduler \"%s\" does not implement "
"ops.cgroup_set_bandwidth(); cpu.max will not be enforced\n",
sch->ops.name);
scx_cgroup_enabled:scx 的 cgroup 路径已打开sch:当前有加载的 BPF 调度器!SCX_HAS_OP(...):该调度器没有实现cgroup_set_bandwidthquota_us != RUNTIME_INF:用户实际配置了有限配额(不是max)pr_warn_once:避免每次带宽变更都刷屏
Patch 概览
单 patch,单文件,定位明确:在 scx_group_set_bandwidth() 中已有 SCX_CALL_OP 调用旁加一个 else if 警告分支,不影响现有调用路径与状态更新。
关键实现
void scx_group_set_bandwidth(struct task_group *tg,
u64 period_us, u64 quota_us, u64 burst_us)
{
...
if (period_us != tg->scx.bw_period_us ||
quota_us != tg->scx.bw_quota_us ||
burst_us != tg->scx.bw_burst_us)
SCX_CALL_OP(sch, cgroup_set_bandwidth, NULL,
tg_cgrp(tg), period_us, quota_us, burst_us);
else if (scx_cgroup_enabled && sch &&
!SCX_HAS_OP(sch, cgroup_set_bandwidth) &&
quota_us != RUNTIME_INF)
pr_warn_once(...);
...
}
设计要点:
- 放在
else if而不是独立路径,避免在「真正实现了回调」的调度器上误报。 pr_warn_once保证只打一次,不污染dmesg。- 不修改 quota 在
task_group上的存储,仍由未来 BPF 调度器按需读取——零行为破坏。
类比
想象你去银行设置「每月 ATM 取款上限 5000 元」:
- 银行把你的限额登记到账户系统里 ✓(内核写入
task_group) - 但真正去 ATM 取钱时,机器要不要遵守这个限额,要看你刷卡时银行选择走哪个出纳模块(BPF 调度器)
- 大多数出纳模块压根没接「限额检查」逻辑,于是限额登记了,但没人去执行——钱照样能取出来
- 补丁相当于:出纳模块没接限额检查时,大堂喇叭播报一次「本窗口暂不执行 ATM限额,请注意」
- Tejun 的反馈相当于说:大堂喇叭太吵,应该在门口贴一张「本行部分窗口不执行限额」的告示(文档)
Highlight:风险与注意点
- 告警粒度过粗:Sashiko AI 指出,启动 BPF 调度器时已存在的 cgroup 配额不会触发新警告——警告只在用户运行时修改
cpu.max时出现。这会让「启动即生效」的容器配额在重启后静默。 - 维护者态度保留:Tejun 引用
cpu.weight的前例——之前的类似警告「制造的麻烦多于帮助」。他倾向于通过文档说明每个 BPF 调度器实现了哪些 cgroup 接口,而不是运行时打 warning。 - 告警覆盖范围有限:BPF 调度器还可以忽略
nice、cpu.idle、内存节流等,这些都不会有警告;如果走「运行时告警」路线,未来类似的告警会越加越多,可能刷屏。 - 可执行或可观测?:作者只打了 warning,但用户多半期望 cgroup 真的被节流。这条 patch 解决了「发现问题」,但没有解决「解决问题」(谁来节流)。
- 是否需要 caps 化:可以扩展成
sched_ext暴露一个 sysfs/ops 位图,声明「我实现了哪些 cgroup 接口」,让用户/编排器主动查询,而不是被动听警告。
+-----------------------+
| user sets cpu.max |
+----------+------------+
|
v
+----------+------------+
| does sch implement |
| cgroup_set_bandwidth?|
+----+-------------+----+
|yes |no
v v
SCX_CALL_OP pr_warn_once<-- new | |
v v silently printk user notified
(qmap only) but quota still
NOT enforced
版本变化
仅 v1(RFC),无 v2。
评审脉络
| 邮件 | 作者 | 关键观点 |
|---|---|---|
| #1 | Tao Cui | 提交 RFC,描述问题 + 测得9946ms/10s,提议 warning |
| #2 | Tao Cui | 自我转发/补发,主体内容一致 |
| #3 | sashiko-bot | AI 评审:警告不覆盖启动时已存在的 cgroup 配额(中风险) |
| #4 | Tejun Heo | 反对运行时 warning;类比 cpu.weight 旧案,建议文档化 |
| #5 | Tao Cui | 接受 Tejun 反馈,表示理解(信息被截断) |
一句话总结
Tao Cui 想给「BPF 调度器忽略 cpu.max」的场景加运行时告警,但 Tejun 倾向文档化,二者尚未达成一致——这条 RFC 的下一步取决于作者是否改成文档 patch,或扩展为「cgroup 能力位图」机制。