sched discussion
[PATCH] sched: Preserve reset-on-fork across concurrent sched_setparam()
LLM 分析
sched/syscalls: 在并发 sched_setparam() 下保留 SCHED_RESET_ON_FORK
系列概况
- 标题: [PATCH] sched: Preserve reset-on-fork across concurrent sched_setparam()
- 作者: Andrea Righi arighi@nvidia.com
- 版本: 单封 PATCH(无版本号、无 series 编号)
- 规模: 1 个文件,5 行新增
- 修改文件: kernel/sched/syscalls.c
- 代码统计: +5 / -0
- Message-ID: 20260807082236.1076967-1-arighi@nvidia.com
- 完整性: 完整(含 commit message、diff、Fixes、Reported-by、Link、Signed-off-by、diffstat)
补丁目的
修复 commit ca94c442535a("sched: Introduce SCHED_RESET_ON_FORK scheduling policy flag")引入的回归。SCHED_RESET_ON_FORK 标志保存在 p->sched_reset_on_fork,用于在 fork 时重置子进程继承的 DL/RT policy、负 nice、自定义 slice 与 utilization clamp。sched_setparam() 调用 __sched_setscheduler() 时理应在保留当前 policy 与 reset-on-fork 设置的前提下更新调度参数;但实现里 __sched_setscheduler() 在取得 rq lock 之前就采样 p->sched_reset_on_fork,并发 sched_setscheduler() 在采样之后、写入之前修改该标志时,迟到的 sched_setparam() 会用陈旧值回写并覆盖最新设置,造成丢失更新。
旧流程的问题
旧代码在持锁前完成 reset_on_fork 的本地 snapshot,迟到的并发 sched_setparam() 仍按 snapshot 回写:
T0 A: lock rq -> modify p->sched_reset_on_fork -> unlock rq
T1 B: snapshot = p->sched_reset_on_fork (stale value)
T2 B: lock rq
T3 B: write snapshot back to p->sched_reset_on_fork
T4 B: unlock rq
Result: A's most recent change is overwritten by B's stale copy
新流程
新增 keep_policy 标记,仅当 keep_policy == true(policy < 0,即 SETPARAM_POLICY 路径)时,在 reset_on_fork 被回写到 p->sched_reset_on_fork 之前重新读取该字段,吸收 rq lock 期间其他并发 sched_setscheduler() 的修改。sched_setscheduler() 显式开关 reset_on_fork 的路径 keep_policy=false,行为保持原状。
@@ -503,6 +503,7 @@ int __sched_setscheduler(struct task_struct *p,
+ bool keep_policy = policy < 0;
@@ -571,6 +572,10 @@ int __sched_setscheduler(struct task_struct *p,
+ /* Preserve reset_on_fork changes made while the rq lock was not held. */
+ if (keep_policy)
+ reset_on_fork = p->sched_reset_on_fork;
+
int queue_flags = DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK;
Patch 概览
kernel/sched/syscalls.c__sched_setscheduler()第 503 行新增bool keep_policy = policy < 0;- 同函数第 571-575 行新增 4 行
if (keep_policy) reset_on_fork = p->sched_reset_on_fork;,让keep_policy路径在回写前刷新 snapshot
关键实现
bool keep_policy = policy < 0; /* 识别 SETPARAM_POLICY 路径 */
/* 在 reset_on_fork 真正用于回写 p->sched_reset_on_fork 之前,
* 重新读取最新值,吸收持锁期间其他并发 sched_setscheduler() 的修改。 */
if (keep_policy)
reset_on_fork = p->sched_reset_on_fork;
要点:
keep_policy只在policy < 0(即 SETPARAM_POLICY 路径)时为真,覆盖sched_setparam()与早期退出分支- 显式
sched_setscheduler()设置reset_on_fork的调用方keep_policy=false,不会读到"半路"的标志 - 注释明确写出意图:"Preserve reset_on_fork changes made while the rq lock was not held"
类比
把 p->sched_reset_on_fork 想象成会议室白板上"自动转账"的开关:
- 只有拿到白板笔(rq lock)的人才能改开关
sched_setparam()这位柜员走向会议室时在走廊里就把开关的复印件夹进文件夹(snapshot)- 路上另一位柜员(
sched_setscheduler())拿到笔,把开关改成"关" - 第一位柜员进会议室后,照抄旧复印件写回白板,于是用户的真实意图被悄悄吞掉
补丁相当于提醒第一位柜员:"进会议室后亲眼看白板当前状态再写"。keep_policy 就是柜员手里的"我是 SETPARAM 路径"标签,决定要不要再看白板一眼。
Highlight:风险与注意点
- 正确性修复:丢失
SCHED_RESET_ON_FORK会让本应隔离的子进程意外继承 RT/DL policy 或负 nice 值,破坏实时性隔离语义 - 触发场景在 AMD 平台被复现(Reported-by 来自 AMD),推测与多线程密集修改调度属性的 workload 相关
- 仅修复了
keep_policy路径;显式sched_setscheduler()设置reset_on_fork的路径走keep_policy=false,行为保持原状 - 补丁未提供 reproducer 或 selftest,回归测试需要手动构造"一个
sched_setscheduler()+ 一个sched_setparam()"并发场景 - diffstat 仅 +5 行,影响面小、风险低,适合 stable backport
- 提交说明里说"after acquiring the rq lock",但实际 diff hunk 中
+行的位置紧邻局部变量声明段,建议审阅 upstream 实际版本,确认reset_on_fork真正回写到p->sched_reset_on_fork的位置在加锁之后,验证修复真正生效
一句话总结
通过新增 keep_policy 标志并在 reset_on_fork 回写前重新读取 p->sched_reset_on_fork,让 sched_setparam() 不会被并发的 sched_setscheduler() 用陈旧 snapshot 覆盖,从而保留 SCHED_RESET_ON_FORK 的并发语义。