sched-ext discussion
[PATCHSET v2 sched_ext/for-7.3] sched_ext: Follow-up fixes and missing cap enforcement
LLM 分析
sched_ext: Follow-up fixes and missing cap enforcement — 技术分析
系列基线信息
| 字段 | 值 |
|---|---|
| 标题 | sched_ext: Follow-up fixes and missing cap enforcement |
| 作者 | Tejun Heo |
| 版本 | v2 |
| 规模 | 5 patches,12 文件,+149 −65 |
| Message-ID | 20260724191651.1040227-1-tj@kernel.org |
| 来源 | sched-ext |
| 基线分支 | sched_ext/for-7.3 (c509d8107026) |
| 状态 | 已合入(Tejun 确认 applied 1-5,附 Andrea 的 Reviewed-by) |
明确目的
本系列解决两类问题:
- 用户态工具的 restart 逻辑缺陷:当调度器 exit 和 restart 条件同时存在时,
exit_req被无视,工具陷入无限重启循环。 - 子调度器(sub-scheduler)cap enforcement 缺口:cid-addressed 操作(reenq、kick、cpuperf)没有检查调用方是否拥有必要权限,导致一个无任何 cap 的 sub-sched 可以:
- 向他人 local DSQ 重新入队,触发目标 CPU 的 IPI 和 rq lock 操作
- 调整他人 cid 的 cpufreq 目标,控制其硬件频率
整体目标:让 sub-scheduler 的权限边界从"声明有 cap 就能用"变成"缺少 cap 就被拒并被计数"。
遍历代码
Patch 1/5 — 工具层 restart 循环修复
7 个工具的 restart 判断从:
if (UEI_ECODE_RESTART(ecode))
改为:
if (!exit_req && UEI_ECODE_RESTART(ecode))
逻辑:用户敲 Ctrl+C 设了 exit_req=1,但内核退出码同时带 RESTART 标志时,旧代码无视 exit_req 直接重启;新代码优先退出。
scx_userland 更复杂:
- 原来用
exit_req同时控制主循环退出和 stats 打印线程停止,重启时exit_req被清零——exit 信号被吞掉 - 新增
stats_stop专供 stats 打印线程,exit_req只管退出、不再清零 - 主循环加
while (!exit_req && !UEI_EXITED(skel, uei))检测内核侧退出
Patch 2/5 — local DSQ reenq 加 SCX_CAP_BASE 门控
在 schedule_dsq_reenq() 入口处插入:
if (unlikely(scx_missing_caps(sch, cpu_of(rq), SCX_CAP_BASE))) {
__scx_add_event(sch, SCX_EV_SUB_REENQ_DENIED, 1);
return;
}
检查逻辑:lockless 读取 cap 状态;scx_missing_caps() 读取 ecaps bitmap。安全性论证:
- 假阳性:cap 刚被 revoke 但 caller 还没看到——reenq 漏过一次无害
- 假阴性不可能:caller 看到 ownership 意味着 check 也看到了(可见性传递)
新增 SCX_EV_SUB_REENQ_DENIED 事件计数被拒绝的 reenq。
Patch 3/5 — kick 拒绝计数
kick_one_cpu() 原来把 cap check 嵌在 kickable 条件内部,跳过时静默无声。重构为:
kickable = (cpu_online(cpu) || ...) && !sched_class_above(...);
if (kickable && !scx_missing_caps(..., SCX_CAP_BASE)) {
/* do kick */
} else if (kickable) {
__scx_add_event(sch, SCX_EV_SUB_KICK_DENIED, 1);
}
kick_one_cpu_if_idle() 同理。新增 SCX_EV_SUB_KICK_DENIED 与已有的 SCX_EV_SUB_PREEMPT_DENIED(kick 送到了但 preempt 部分被降级)区分。
Patch 4/5 — 抽取 scx_cpuperf_set()
纯重构:将 scx_bpf_cpuperf_set() 内的验证+写入逻辑抽到 scx_cpuperf_set(),返回 s32(0 或 -errno)。验证改为早返回风格,消除嵌套。无功能变化,为下一个 patch 的 cap gating 做准备。
Patch 5/5 — scx_bpf_cidperf_set() 加 SCX_CAP_PERF 门控
新增 SCX_CAP_PERF(bit 3),与 SCX_CAP_BASE/ENQ/PREEMPT 独立。设计理由:频率控制是硬件管理轴,与队列访问是两个维度——父调度器可以委托调度权但不交出频率权。
scx_cpuperf_set() 内加权威检查:
if (likely(!scx_missing_caps(sch, cpu, SCX_CAP_PERF))) {
rq->scx.cpuperf_target = perf;
cpufreq_update_util(rq, 0);
ret = 0;
} else {
__scx_add_event(sch, SCX_EV_SUB_CIDPERF_DENIED, 1);
ret = -EACCES;
}
权威性:检查在目标 rq lock 下执行,ecaps 更新也在此锁下折叠,因此 revoke 生效后不可能再有写入漏过。
scx_bpf_cidperf_set() 签名从 void 改为 s32,返回 0 / -EACCES / -ENODEV。因为 cid-form 接口仍在早期开发,签名就地修改不做版本兼容。
scx_qmap 的 sub-sched grant/revoke 更新:所有 grant 处追加 SCX_CAP_PERF,保证 cpufreq demo 继续可用。
ASCII 流程图
+-------------------+ +-------------------+ +-------------------+
| scx_bpf_dsq_ | | kick_one_cpu() | | scx_bpf_cidperf |
| reenq() | | kick_one_cpu_ | | _set() |
| LOCAL_ON target | | if_idle() | | |
+--------+----------+ +--------+----------+ +--------+----------+
| | |
v v v
[SCX_CAP_BASE?] [SCX_CAP_BASE?] [SCX_CAP_PERF?]
| | |
+----+----+ +----+----+ +----+----+
| | | | | |
PASS DENY PASS DENY PASS DENY
| | | | | |
v v v v v v
IPI/ EV_SUB_ resched EV_SUB_ write EV_SUB_
reenq REENQ_ curr() KICK_ cpuperf CIDPERF_
goes DENIED DENIED target DENIED
through +--> set +--> ret
| -EACCES
Cap enforcement chain for cid-addressed operations
User tool main() restart loop (Patch 1/5):
kernel exit
|
v
UEI_REPORT(ecode)
|
v
+---- exit_req? ----+
| YES | NO
v v
return 0 +-- RESTART flag? --+
| YES | NO
v v
goto restart return 0
Old logic skipped exit_req check → tight restart loop
概念类比
类比一(cap enforcement):把 sub-scheduler 想象成公司各部门的实习生。之前实习生拿到了工卡就能随意调用任何部门的会议室(reenq)、喊任何人过来(kick)、甚至调大楼空调温度(cpuperf)。现在公司加了权限门禁——实习生只有被明确授权的会议室才能用,没权限的请求会被拒绝并在安保日志里记一笔。频率控制尤其特殊:调度权≠空调权,实习生可以安排谁坐哪,但不等于能调温度。
类比二(restart loop):你反复按电梯"上行"按钮想重启运行,但同时有人按了"停止"按钮。旧逻辑只看上行标志,无视停止信号,电梯就不停重启;新逻辑先检查停止信号,有停止就不再上行。
Highlight 突出问题
-
lockless cap check 的假阳性窗口:Patch 2 的 reenq gate 是 lockless 的。虽然 commit message 论证"假阳性无害、假阴性不可能",但在高频 revoke/grant 旋转场景下,短暂的误拒可能导致 sub-sched 的 reenq 被丢弃、任务滞留在错误 DSQ。需观察
SCX_EV_SUB_REENQ_DENIED事件计数是否在生产环境中有异常峰值。 -
scx_bpf_cidperf_set()签名就地修改的兼容风险:因为 cid-form 接口在早期开发,签名从void改为s32不走版本兼容。任何已编译使用旧 void 签名的 BPF 程序在新内核上会直接失败(kfunc 签名不匹配)。虽说是"早期开发可接受",但下游用户需要注意。 -
SCX_CAP_PERF 默认只 root 持有:普通 sub-sched 默认没有 PERF 权限,意味着所有 sub-sched 的 cpuperf 设置都会被拒——除非父调度器显式 grant。这是设计意图,但容易被遗忘;
SCX_EV_SUB_CIDPERF_DENIED计数是排查线索。 -
scx_userland 的 stats_stop 与 exit_req 分离是行为变化:原来 stats 打印线程随 exit_req 停止并在重启时清零;现在
stats_stop仅在 teardown 设 1,重启时不清。需验证 stats 打印线程在 restart 场景下不会漏跑或残留。
版本演进
v1 → v2 关键改动:
- Patch 4/5 的 kernel-doc 注释从抽取后的 helper 移回
scx_bpf_cpuperf_set()(sashiko 报告) - Patch 5/5 删除了 versioned
scx_bpf_cidperf_set___v2()接口和 tools compat wrapper;改为就地修改签名返回s32,因为 cid-form 接口仍在早期开发
与其他相关 patch 系列的关联
本系列是近期 sub-scheduler 工作(cgroup-based 子调度器分层与 cap 体系)的直接后续。cap 体系引入了 SCX_CAP_ENQ_IMMED/ENQ/PREEMPT 但最初遗漏了 reenq 路径和 cpuperf 的权限门控;本系列补上这些缺口。v1 链接 20260724182125.985061-1-tj@kernel.org 指向同一系列的上一版本。
一句话总结
补上 sub-scheduler 的 restart-loop bug 和 cap enforcement 缺口:reenq/kick 加 SCX_CAP_BASE 门控并计数拒绝,cpuperf 加独立 SCX_CAP_PERF 门控并同步返回结果。