0/3 已展开

LLM 分析

sched_ext backport:保留 rq 追踪并铺垫 helper 迁移

系列概况

字段内容
系列标题1/2 sched_ext: Move shared helpers from ext.c into internal.h and cid.h / 2/2 sched_ext: Preserve rq tracking across local DSQ dispatch
作者Sasha Levin sashal@kernel.org;patch 内容来自 Tejun Heo(1/2)与 Andrea Righi(2/2)
版本7.1.y backport 系列,2/2(共两 patch)
规模1/2:删 29 / 增 30;2/2:删 16 / 增 20;共 2 文件被改动
修改文件kernel/sched/ext.c、kernel/sched/ext_internal.h
代码统计1/2 +30/-29(针对 ext_internal.h);2/2 +20/-16(针对 ext.c)
Message-ID20260729162433.3523095-1-sashal@kernel.org20260729162433.3523095-2-sashal@kernel.org(触发线索 2026072116-antivirus-displace-b981@gregkh)
完整性系列由 Greg KH 的 stable 树 apply 失败通知触发;Sasha Levin 回 2-patch 重写,邮件正文含 diff、上游 commit 引文、SOB 链与 Stable-dep-of 链

补丁目的

把 upstream 18d62044cda7(Andrea Righi)backport 到 7.1-stable:在嵌套 SCX op 路径中切换 rq 锁时,同步更新 per-CPU 的 scx_locked_rq_state,避免 lockdep 的 WARNING: kernel/sched/sched.h:1641 at call_task_dequeue+... 告警。补丁 1/2 是为它铺路的前置 helper 迁移,让 2/2 能在 7.1.y 上干净 cherry-pick。

旧流程的问题

dispatch_to_local_dsq() 在嵌套 SCX op 中会切换 rq 锁,但只有裸的 raw_spin_rq_unlock + raw_spin_rq_lock,per-CPU 的 scx_locked_rq_state 没有跟着切:

SCX_CALL_OP(dispatch, rq)
  ops.dispatch()
    scx_bpf_dsq_move_to_local()
      scx_flush_dispatch_buf()
        finish_dispatch()
          dispatch_to_local_dsq()
            scx_dispatch_enqueue()
              local_dsq_post_enq()
                call_task_dequeue()
                  SCX_CALL_OP_TASK(dequeue, locked_rq, ...)

实际持锁已是 to-rq,但 scx_locked_rq_state 仍指向 from-rq。嵌套 op 返回时 update_locked_rq() 想 "恢复 from-rq",触发 Restoring a rq which is no longer held 告警。

新流程

新增辅助函数 switch_rq_lock(from, to),把 "释放旧锁 / 取新锁" 与 "更新 rq 追踪" 绑定:

static void switch_rq_lock(struct rq *from, struct rq *to)
{
    bool tracked = scx_locked_rq() == from;

    if (tracked)
        update_locked_rq(NULL);
    raw_spin_rq_unlock(from);
    raw_spin_rq_lock(to);
    if (tracked)
        update_locked_rq(to);
}

仅当追踪的就是 from 时同步更新;否则只做锁切换,不碰追踪。原 ext.c 中的 5 处裸 unlock+lock 站点全部替换为本函数。

        old:                            new:
    +----------+                    +----------+
    | lock(fr) |                    | lock(fr) |
    +----+-----+                    +----+-----+
         |                               |
         | unlock(fr) -> lock(to)        | switch_rq_lock(fr, to)
         | (state still fr)              |   update_locked_rq(NULL)
         v                               |   unlock(fr)
    +----------+                         |   lock(to)
    | lock(to) |                         |   update_locked_rq(to)
    +----+-----+                         v
         |                          +----------+
    update_locked_rq(to)            | lock(to) |
         |  警告:to已经不被持     +----+-----+
         v                               |
    WARNING lockdep                     | state == to  -> 一致

Patch 概览

  • 1/2 (Tejun Heo → Sasha Levin):helper 迁移,upstream 4437ad129cf5。无功能改动:ext.c 中的 op-dispatch 宏、SCX_HAS_OPupdate_locked_rqscx_parent 被搬到 ext_internal.h。背景:idle.c / cid.c 与 ext.c 一同参与 build_policy.c,helpers 在 ext.c 内会让 clangd 解析失败;迁移后两者能独立 parse。Stable-dep-of: 18d62044cda7
  • 2/2 (Andrea Righi → Tejun Heo → Sasha Levin):上游 18d62044cda7 的修复。新增 switch_rq_lock(),替换 ext.c 中 5 处裸 unlock+lock:move_remote_task_to_local_dsqdispatch_to_local_dsq 中的两处、以及 scx_dsq_move 的 in-balance 路径两处。

关键实现

  1. 位置:switch_rq_lock 紧跟 rq_is_open,便于与 ext.c 其它 helper 集中阅读。
  2. 判断条件:先 scx_locked_rq() == from,避免无谓写 per-CPU;要求调用方约定 "调用前追踪的就是当前持有锁"。
  3. 更新顺序update_locked_rq(NULL) → unlock(from) → lock(to) → update_locked_rq(to),保证 lockdep 看到的 rq 与实际持有锁严格对齐。
  4. 依赖链:1/2 是 2/2 在 stable 上能干净应用的必要前提;单 cherry-pick 2/2 时 ext_internal.h 没有 update_locked_rq,旧版仍留在 ext.c——这正是 Greg KH 报告的失败原因。

类比

把 rq 锁当作 "某层楼某间房的钥匙卡":手里那张卡写有你的房间号,前台读卡器登记本上也写有你的房间号(per-CPU rq 追踪)。换房间时旧实现只换卡不更新登记本,读卡器回头比对发现 "你声称在 A 房,可卡已经发到 B 房",于是报警;switch_rq_lock 的做法是 "先到前台撤销登记 → 换钥匙 → 到新房间重新登记",让卡面与登记本任何时刻都保持一致。

Highlight:风险与注意点

  1. stable 顺序强依赖:1/2 必须先于 2/2,否则 2/2 会因 ext_internal.h 缺少 update_locked_rq 而 fail。
  2. upstream 顺序:4437ad129cf5(helper 迁移)必须先于 18d62044cda7(追踪修复),与 stable 系列顺序一致。
  3. scx_dsq_move 双入口this_rq↔src_rqlocked_rq↔this_rq 两次切换都走 switch_rq_lock;测试覆盖应同时压两条路径,包括非 in_balance 分支保持原语义的回退验证。
  4. Fixes 链Fixes: 7fb39e4eb4c3 提示这是之前 "Save/restore scx_locked_rq across SCX_CALL_OP" 引入的回归;后续若再引入 SCX op 的新嵌套要审视是否还要继续扩展 switch_rq_lock 站点。
  5. 观察 lockdep:修复后仍可能在新的 SCX op 嵌套路径上再次告警;调用栈变化是关键定位线索。
  6. upstream 引用:stable backport 通常省略 Link 标签;如需检索完整 lore 信息,应关注 upstream 18d62044cda7 与 4437ad129cf5 的 commit message。

版本变化

upstream linus tree 中只有 18d62044cda7 这一个修复 patch。在 7.1-stable 上被拆成 1/2 + 2/2 两段,原因是 stable 树的 ext_internal.h 内容布局与 linus tree 不同,需要先做 helper 迁移铺路。后段才是真正 backport 的逻辑修复。

一句话总结

为了让 Andrea Righi 的 rq 追踪修复在 7.1-stable 上能干净 cherry-pick,Sasha Levin 先将 ext.c 共享 helper 迁入 ext_internal.h,再用 switch_rq_lock() 把 rq 锁切换与 per-CPU 追踪同步,消除嵌套 SCX op 路径上的 lockdep Restoring a rq which is no longer held 告警。