0/8 已展开

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-ID20260724191651.1040227-1-tj@kernel.org
来源sched-ext
基线分支sched_ext/for-7.3 (c509d8107026)
状态已合入(Tejun 确认 applied 1-5,附 Andrea 的 Reviewed-by)

明确目的

本系列解决两类问题:

  1. 用户态工具的 restart 逻辑缺陷:当调度器 exit 和 restart 条件同时存在时,exit_req 被无视,工具陷入无限重启循环。
  2. 子调度器(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 突出问题

  1. lockless cap check 的假阳性窗口:Patch 2 的 reenq gate 是 lockless 的。虽然 commit message 论证"假阳性无害、假阴性不可能",但在高频 revoke/grant 旋转场景下,短暂的误拒可能导致 sub-sched 的 reenq 被丢弃、任务滞留在错误 DSQ。需观察 SCX_EV_SUB_REENQ_DENIED 事件计数是否在生产环境中有异常峰值。

  2. scx_bpf_cidperf_set() 签名就地修改的兼容风险:因为 cid-form 接口在早期开发,签名从 void 改为 s32 不走版本兼容。任何已编译使用旧 void 签名的 BPF 程序在新内核上会直接失败(kfunc 签名不匹配)。虽说是"早期开发可接受",但下游用户需要注意。

  3. SCX_CAP_PERF 默认只 root 持有:普通 sub-sched 默认没有 PERF 权限,意味着所有 sub-sched 的 cpuperf 设置都会被拒——除非父调度器显式 grant。这是设计意图,但容易被遗忘;SCX_EV_SUB_CIDPERF_DENIED 计数是排查线索。

  4. 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 门控并同步返回结果。