sched-ext discussion
[PATCH] sched_ext: serialize concurrent cpu.max writers in scx_group_set_bandwidth()
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_OP前mutex_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_unlock→percpu_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.weight与cpu.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_OP 与 tg->scx.bw_* 的写入从只持读锁升级为互斥,与 CFS 一侧的 cfs_constraints_mutex 对齐,从而消除同 cgroup 并发 cpu.max 写导致的回调乱序与状态撕裂。