0/1 已展开

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 == truepolicy < 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 的并发语义。