0/4 已展开

LLM 分析

sched_ext:序列化并发 cpu.max 写入

系列概况

  • 标题: [PATCH] sched_ext: serialize concurrent cpu.max writers in scx_group_set_bandwidth()
  • 作者: Changwoo Min changwoo@igalia.com
  • 版本: 单封补丁(v1)
  • 规模: 1 个文件,+9 行
  • 修改文件: kernel/sched/ext/ext.c
  • 代码统计: 9 insertions(+), 0 deletions(-)
  • Message-ID: 20260821103519.535987-1-changwoo@igalia.com
  • 完整性: 完整,包含 commit message、SOB、Reported-by、AI 评审与维护者回复

补丁目的

scx_group_set_bandwidth() 是 sched_ext 在 cgroup 写 cpu.max 时被调用、用以更新 tg->scx.bw_quota_us/bw_burst_us 缓存的入口。

原本该路径只在 percpu_down_read(&scx_cgroup_ops_rwsem)(读锁)下运行:

  • 同一 cgroup 的两次写 cpu.max,因 cgroup/kernfs 不提供跨 fd 的互斥,可以并发进入;
  • CFS 一侧由 cfs_constraints_mutex 保护,但 SCX 一侧没有对应锁;
  • 结果 SCX_CALL_OP(cgroup_set_bandwidth)tg->scx.bw_* 的赋值会出现交错;
  • 64 位 bw_* 在 32 位平台上还可能被拆写(torn write)。

补丁新增一个全局 scx_cgroup_set_bw_mutex,把回调和缓存写入整体包住,与 CFS 一侧的 cfs_constraints_mutex 对齐。

旧流程的问题

  • 缺失写侧互斥: cft->write 不在 cgroup_mutex 下运行,kernfs 也只对同一 struct file 做串行。
  • 只持读锁: scx_group_set_bw_mutex 之前只有 percpu_down_read(&scx_cgroup_ops_rwsem),多写者并发更新 bw_*
  • 回调乱序 + 缓存撕裂: 两次写的 callback 与 store 可任意交错,64 位 store 在 32 位平台也可能撕裂。

新流程

  • 新增 static DEFINE_MUTEX(scx_cgroup_set_bw_mutex);
  • scx_group_set_bandwidth() 在调用 SCX_CALL_OPmutex_lock,完成 tg->scx.bw_* 赋值后 mutex_unlock
  • 锁域覆盖 callback + 缓存赋值,行为与 CFS 的 cfs_constraints_mutex 对称。
        writer A                          writer B
 percpu_down_read(scx_cgroup_ops_rwsem)
  mutex_lock(scx_cgroup_set_bw_mutex)  percpu_down_read(...)
      SCX_CALL_OP(cgroup_set_bandwidth)   ... wait mutex ...
 tg->scx.bw_quota_us = A_quota
      tg->scx.bw_burst_us = A_burst
  mutex_unlock(...) mutex_lock(...)
                                       SCX_CALL_OP(...) /* now serialized */
                                       bw_quota_us = B_quota
                                       bw_burst_us = B_burst mutex_unlock(...)
  percpu_up_read(...)

Patch 概览

/* Serialize concurrent cpu.max writers to the same cgroup so the
 * ops.cgroup_set_bandwidth() callback and the cached tg->scx.bw_*
 * values update atomically in one order -- the SCX-side counterpart
 * to cfs_constraints_mutex.
 */
static DEFINE_MUTEX(scx_cgroup_set_bw_mutex);

void scx_group_set_bandwidth(struct task_group *tg, ...)
{
    ...
    percpu_down_read(&scx_cgroup_ops_rwsem);
+ mutex_lock(&scx_cgroup_set_bw_mutex);
    sch = scx_tg_knob_sched(tg);
    if (scx_cgroup_enabled && sch && SCX_HAS_OP(sch, cgroup_set_bandwidth)
        && tg != &root_task_group) {
        ...
        SCX_CALL_OP(sch, cgroup_set_bandwidth, ...);
        tg->scx.bw_quota_us = quota_us;
        tg->scx.bw_burst_us = burst_us;
    }
 percpu_up_read(&scx_cgroup_ops_rwsem);
+   mutex_unlock(&scx_cgroup_set_bw_mutex);
}

关键实现

  • 锁顺序:percpu_down_read(scx_cgroup_ops_rwsem)mutex_lock(scx_cgroup_set_bw_mutex) → … → mutex_unlockpercpu_up_read
  • 临界区:SCX_CALL_OP(cgroup_set_bandwidth, ...) 与对 tg->scx.bw_quota_us/bw_burst_us 的两次 store。
  • 锁域与 CFS 端 cfs_constraints_mutex 等价,跨调度器读写次序保持一致。

类比

把 cgroup 的 cpu.max 看作小区水表:

  • 抄表员 A 和 B 同时抄表并各自修改“本区可用流量”记录(tg->scx.bw_*)。
  • 旧流程里只有“读门禁”(rwsem 读锁)允许双方同时进楼,B 抄完的数会被 A 后写覆盖,先写的 callback 又被 B 后跑掉,记录与实际顺序都对不上。
  • 新流程加上一把“写门禁”钥匙(mutex),一次只允许一个抄表员完整走完“抄表→登记”两步:水表读数与登记值天然一致,跨表不会撕裂。

Highlight:风险与注意点

  • 锁顺序风险:在 percpu_rwsem 读侧临界区内获取全局 mutex,若别处反过来持 mutex 抢 scx_cgroup_ops_rwsem 写锁,可能形成 AB-BA 死锁。需要全树确认无反向持有。
  • 写者持锁期间调度开销:所有 cpu.max 写者被串行化;高频写场景会形成争用热点,但 cgroup 文件写本身频率低,实际影响可控。
  • 未覆盖的属性:Sashiko 指出 cpu.weightcpu.idle 在 SCX 侧仍缺乏对应锁,存在类似并发风险,后续需独立补丁处理。
  • CFS/SCX 顺序一致:补丁让两边都先 CFS 后 SCX,但补丁的 mutex 在 tg_set_cfs_bandwidth 释放之后才获得,跨调度器的强一致仍依赖 CFS 端先落地,行为需谨慎推敲。
  • 64 位撕裂:补丁解决了 race,但 32 位平台下的 bw_* 赋值若被编译器拆开仍可能撕裂;考虑用 WRITE_ONCE 或64-bit 原子赋值收口。
  • AI 评审的语义判定:Sashiko标注的 [High] 项(如 CFS/SCX 永久发散、写锁未护住 RMW)描述的是“未实施的设计”,而本补丁正是走向该修复的第一步;解读时应区分“本补丁引入” vs “仍是遗留问题”。
  • 回复上下文不完整:Tejun Heo 与 Changwoo Min 的回复在 lore 抓取时被截断,下游需要重新拉取原文确认是否给出了进一步指导或要求改写。

版本变化

单封 v1,无后续版本。

与其他相关 patch 系列的关联

  • 同主题 CFS 端互斥 cfs_constraints_mutex 已存在于 kernel/sched/fair.c,本补丁是其 SCX 对应物。
  • Changwoo 在回复中提到 Michal 的相关补丁(20260821140818.1559100-1-michalblk@google.com),暗示同一时间段可能存在围绕 cgroup 带宽接口的另一组改动,需联动 review。
  • Sashiko 提示的 cpu.weight/cpu.idle 串行化问题尚未提交补丁,可能成为下一个 follow-up。

一句话总结

scx_group_set_bandwidth() 加一把 SCX 端 cgroup_set_bw_mutex,把 SCX_CALL_OPtg->scx.bw_* 的写入从只持读锁升级为互斥,与 CFS 一侧的 cfs_constraints_mutex 对齐,从而消除同 cgroup 并发 cpu.max 写导致的回调乱序与状态撕裂。