0/10 已展开

LLM 分析

sched: SCHED_FLAG_KEEP_PARAMS 副作用修复

系列概况

  • 标题: [PATCH 0/2] sched: Fix SCHED_FLAG_KEEP_PARAMS side effects
  • 作者: Andrea Righi arighi@nvidia.com
  • 版本: v1(无明确版本号)
  • 规模: 2 个 patch
  • 修改文件: kernel/sched/syscalls.c
  • 代码统计: 1 file changed, 4 insertions(+), 2 deletions(-)
  • Message-ID: 20260730055011.2267333-1-arighi@nvidia.com
  • 完整性: 完整(cover letter + 2 patches + 7 封维护者/审阅者回复,合计 10 封)

补丁目的

SCHED_FLAG_KEEP_PARAMS 允许 sched_setattr() 只更新 task 的通用属性(例如 uclamp),保留原有的调度参数和 class。但 __sched_setscheduler() 中仍有两条路径在受保护的参数更新之前对 requested policy 产生副作用:

  1. prev_class != next_class 当作 class change,给 DEQUEUE_CLASS 置位并触发 switching_from() / switched_from() / switching_to() / switched_to() 四个 callback,但 p->sched_class 实际不会变。
  2. 跑 deadline admission control sched_dl_overflow(),把一个永远不会真正进入 deadline class 的任务的带宽占用记入 root domain。

本系列在两个分支前增加 SCHED_FLAG_KEEP_PARAMS 短路条件,从运行时消除这两类副作用。

旧流程的问题

sched_setattr(p, flags=KEEP_PARAMS, policy=X)
        |
        v
__sched_setscheduler(p, attr)
  [step 1] if (prev_class != next_class)
            -> queue_flags |= DEQUEUE_CLASS
            -> switching_*()/switched_*() callbacks fire   [WRONG]
  [step 2] if (dl_policy(policy) || dl_task(p))
            && sched_dl_overflow(...)
            -> root_domain reserves dl_bw                 [WRONG]
  [step 3] scoped_guard(sched_change) -- real param/class update
            (KEEP_PARAMS makes this a no-op for class/params)

副作用路径在 step 3 之前执行,但 KEEP_PARAMS 又让 step 3 不修改 class / 参数,于是出现「没有 class change,却跑了 class change 回调」与「task 不是 deadline,却消耗了 dl 带宽」两类不一致。

新流程

两处都加上 !(attr->sched_flags & SCHED_FLAG_KEEP_PARAMS) 短路:

/* Patch 1/2 */
- if (prev_class != next_class)
+ if (!(attr->sched_flags & SCHED_FLAG_KEEP_PARAMS) &&
+     prev_class != next_class)
        queue_flags |= DEQUEUE_CLASS;

/* Patch 2/2 */
- if ((dl_policy(policy) || dl_task(p)) && sched_dl_overflow(p, policy, attr)) {
+ if (!(attr->sched_flags & SCHED_FLAG_KEEP_PARAMS) &&
+     (dl_policy(policy) || dl_task(p)) && sched_dl_overflow(p, policy, attr)) {

KEEP_PARAMS 模式下直接跳过这两个分支:class 不变、带宽不记账;参数更新路径在 scoped_guard 内部仍然按通用属性处理。

Patch 概览

  • Patch 1/2 sched: Skip class callbacks with SCHED_FLAG_KEEP_PARAMS
    • 修复点: __sched_setscheduler()DEQUEUE_CLASS 置位判断。
    • 关联 commit: Fixes: 637b0682821b ("sched: Fold sched_class::switch{ing,ed}_{to,from}() into the change pattern")
  • Patch 2/2 sched/deadline: Skip bandwidth accounting with SCHED_FLAG_KEEP_PARAMS
    • 修复点: 同一函数里 deadline 准入与带宽记账。
    • 关联 commit: Fixes: a509a7cd7974 ("sched/uclamp: Extend sched_setattr() to support utilization clamping")

关键实现

__sched_setscheduler(p, attr)
          |
          +-------------------------+-------------------------+
          | (Patch 1/2)             | (Patch 2/2)             |
          v                         v                         |
  if (!KEEP_PARAMS             if (!KEEP_PARAMS              |
   && prev != next)             && (dl_policy(policy)        |
   -> DEQUEUE_CLASS              || dl_task(p))               |
   -> class callbacks            && sched_dl_overflow(...))   |
                                 -> dl_bw accounting          |
          |                         |                         |
          +-------------------------+-------------------------+
                                      |
                                      v
                scoped_guard(sched_change, p, queue_flags)
                -> guarded real update (already respects KEEP_PARAMS)

Peter 在回复中提出另一种「入口拒绝」的修法:

SYSCALL_DEFINE3(sched_setattr, ...)
        |
        v
  if (attr.sched_flags & SCHED_FLAG_KEEP_PARAMS) {
      if (attr.sched_policy != SETPARAM_POLICY
          && attr.sched_policy != p->policy)
          return -EINVAL;
      get_params(p, &attr, 0);
  }
        |
        v
  sched_setattr(p, &attr);

也就是说,与其到处打补丁短路副作用,不如让 sched_setattr() 直接拒绝 KEEP_PARAMS 与策略变更的组合,从源头上消除矛盾。

类比

sched_setattr() 想象成去物业办理「只换门牌号、不动房子结构」的申请:SCHED_FLAG_KEEP_PARAMS 就是那张不动结构的申请单。

旧流程里,物业一看申请单上写了别的房型,就先把承重墙敲一下(switching_*() 回调),再回头告诉你「其实没动」;同时物业的电量配额管理员,看到「DEADLINE」字样就先从公共池里扣一块配额到你账上,可你根本没装 DEADLINE 设备,配额永远释放不掉。

新流程里,物业先看一眼申请单有没有 KEEP_PARAMS 标记,有的话这两步直接跳过——只换门牌号,不动结构、不占配额。

Peter 的进一步建议则是:在窗口直接拒收「KEEP_PARAMS + 想换房型」这种自相矛盾的申请单(返回 -EINVAL),从源头就不让这种单子进入流水线。

Highlight:风险与注意点

  • 设计层面的争议: Peter 在多封回复里主张「KEEP_PARAMS 应当在 sched_setattr() syscall 入口就要求 attr.sched_policy == p->policy || attr.sched_policy == SETPARAM_POLICY,否则 -EINVAL」,而不是在 __sched_setscheduler() 里到处打补丁。如果按他的思路,这两处 patch 实际上是「症状级」修复。
  • KEEP_PARAMS vs KEEP_POLICY 语义: Prateek 提到还存在 SCHED_FLAG_KEEP_POLICY,两条 flag 的语义边界要厘清,避免后续 syscall 层清理时互相打架。
  • 是否还有第三条路径: 目前 fix 只覆盖了 class callback 和 dl 带宽两个分支,是否还有其他「在 guarded update 之前消耗 requested policy」的路径(例如 SCHED_FLAG_UTIL_CLAMP_MIN/MAX 的极端组合)需要逐一确认。
  • 可重现性: dl 带宽被反复消耗而永不释放这条路径,要确认能否在没有这个 fix 时用一个简单 test 程序反复 sched_setattr() 触发,以验证 fix 有效。
  • 历史 Fixes: 两个 Fixes: commit 一个在 sched core 一个在 sched/uclamp,需要在 CI 上跑相关 test 确保没有引入回归。

版本变化

本帖只有 v1;从回复走向看,作者预计会出 v2:

  • 把 Patch 1/2 的 if (!(attr->sched_flags & SCHED_FLAG_KEEP_PARAMS)) 改写成更紧凑的判断(Prateek 给出了一个备选 hunk)。
  • 可能采纳 Peter 的建议,在 syscall 入口加 policy 一致性判断;那样 Patch 2/2 的核心改动可能就不需要了,行为也由「运行时短路副作用」变成「入口拒绝非法组合」。

一句话总结

Andrea 通过两个一行补丁在 __sched_setscheduler() 内补齐 SCHED_FLAG_KEEP_PARAMS 遗漏的两条副作用路径(class transition 回调与 deadline 带宽记账),而 Peter 倾向于在 sched_setattr() syscall 入口就拒绝 KEEP_PARAMS 与 policy 变更的组合,从源头消除矛盾。