0/2 已展开

LLM 分析

sched/topology:避免覆盖 sd->shared 分配

系列概况

  • 标题[PATCH] sched/topology: Do not override sd->shared allocation
  • 作者:K Prateek Nayak kprateek.nayak@amd.com
  • 版本:v1(单封 patch,无 series index/total)
  • 规模:1 个文件,+9 / -1
  • 修改文件kernel/sched/topology.c
  • 代码统计:9 行新增,1 行删除
  • Message-ID20260713065625.3572-1-kprateek.nayak@amd.com
  • 完整性:完整(包含 commit message、diff hunk、签名行、base-commit 04998aa54848f15332202d0bea008d2ca1ed1713);线程中第二封为同一作者对原 patch 的回复,仅引用前文,无新内容

补丁目的

修复 Breno Leitao 在一台「非对称拓扑 + 单一 LLC」平台上用 kmemleak 抓到的 sd->shared 内存泄漏。

根因是上游 commit 9e005ed21152d("sched/topology: Allow multiple domains to claim sched_domain_shared")。该 commit 让 init_sched_domain_shared()sd_llc 路径上无条件分配并赋值 sd->shared。当 SD_ASYM_CPUCAPACITYSD_SHARE_LLC 这两个 flag 落到 同一个 sched_domain 上时,第二次进入函数会把第一次分配好的指针直接覆盖掉,导致第一份 sched_domain_shared 结构丢失唯一引用,kmemleak 在系统关闭/拓扑退化阶段找不到配对的释放。

顺手修一处注释拼写错误:"avaialable" -> "available"

旧流程的问题

init_sched_domain_shared() 在同一 sched_domain 上被两个不同 flag 调用时,第二次调用会改写 sd->shared

[BEFORE PATCH - LEAK SCENARIO]

flag = SD_ASYM_CPUCAPACITY  -->  enters init_sched_domain_shared()
                                    |
                                    v
                                 sds_A = alloc_sched_domain_shared()
                                 sd->shared = sds_A          (refcount=1)

flag = SD_SHARE_LLC          -->  enters init_sched_domain_shared() again (same sched_domain)               |
                                    v
                                 sds_B = alloc_sched_domain_shared()
                                 sd->shared = sds_B          (refcount=1)

degenerate path:
   refcount_dec(&sds_A->ref)   --> only -1, but sd->shared now points to sds_B
   refcount_dec(&sds_B->ref)   -->  reaches0, freed   sds_A NEVER FREED  -->  kmemleak hits

关键不对称点:degenerate(退化)路径只对 当前 sd->shared 指向的那一份 refcount 减 1,前面被覆盖掉的那一份既无任何引用计数能配对,也不在退化列表里。系统越复杂、flag 越多,重叠命中的概率越高,泄漏规模按 num_overlaps - 1 累加。

新流程

入口加一段 short-circuit:若 sd->shared 已经被本 domain level 记过账(无论来自哪个 flag),直接 return,不再分配也不动 refcount。

[AFTER PATCH - FIXED FLOW]

init_sched_domain_shared(d, sd, flags)
        |
        v
   +----+----+
   |  | sd->shared != NULL ? |
   +---+------+
       |       \
      yes      no
       |         \
       v          v
    return     compute sds (llc / asym path)
                   |
                   v
             if (WARN_ON_ONCE(!sd->shared))
                 sd->shared = sds; (defensive, normal path won't hit)
                   |
                   v
             refcount_set(&sd->shared->ref, refs)

degenerate path:
   refcount_dec(&sd->shared->ref)   --> -1, paired with the single inc no leak

语义约束:每个 sched_domain level 只允许一次 sched_domain_shared 分配 + 一次 refcount inc。即使多个 flag 都命中同一 domain 也只记一次账,degenerate 路径的 -1 即可平衡。

关键实现

补丁只动 kernel/sched/topology.cinit_sched_domain_shared() 入口与一处注释:

@@ -2942,6 +2942,14 @@ init_sched_domain_shared(struct s_data *d,
                          struct sched_domain *sd, int flags)
+        /*
+         * sd->shared is already assigned for this domain level for a
+         * different flag. Nothing to do since this allocation is
+         * already accounted for.
+         */
+        if (sd->shared)
+                return;
+
@@ -2970,7 +2978,7 @@ init_sched_domain_shared(struct s_data *d,
 struct sched_domain *sd, int flags)
- * CPU in the span if none are avaialable.
+ * CPU in the span if none are available.

注意点:

  • 早返回放在函数最前面,不影响后续 __find_idlest_groupbuild_sched_domain 等无关路径。
  • 不分配 sds也不动 refcount,与「每个 domain level 记一次账」语义一致。
  • 保留了原有的 if (WARN_ON_ONCE(!sd->shared)) sd->shared = sds; 兜底,作为 defensive 检查,正常路径不会再触发。

类比

sched_domain_shared 想象成公寓楼里的 电梯控制箱钥匙。每层楼(sched_domain level)共用一个控制箱,但可能有多种「住户类型」(SD_ASYM_CPUCAPACITY、SD_SHARE_LLC …)都要登记使用权。

-第一次登记(SD_ASYM_CPUCAPACITY):物业去仓库领一把钥匙 k1,登记到该层台账 sd->shared = k1,refcount 记 1。

  • 第二次登记(SD_SHARE_LLC):物业 没查台账 又跑去仓库领了 k2,把台账覆盖成 sd->shared = k2k1 没人领走也无人归还。退房时物业只对台账上的钥匙减一次借用次数,k1 永远压在仓库角落 —— kmemleak 就是巡逻保安捡到的那把没人领的钥匙。

加 patch 之后,物业在领钥匙之前先扫一眼台账:发现已经有登记,直接把新住户绑定到同一条锁名下,不再去仓库多领一把。这样退房时只需还一次,台账和仓库始终对得上。

Highlight:风险与注意点

  • 触发条件罕见。常规对称 SMP 服务器上 SD_ASYM_CPUCAPACITY 不会与 SD_SHARE_LLC 重叠,复现链路需要 ARM big.LITTLE / AMD CCD-asym 等非对称 + 单 LLC 拓扑,普通 CI 容易漏覆盖,建议在 LKP/QA 上挂长期回归。
  • 不变式收紧。本 patch 维持「每 domain level 一次 alloc、一次 ref inc」语义。若后续 init_sched_domain_shared 之外(如 CPU hotplug 重新构建拓扑)出现新的 sd->shared 赋值点,必须保证仍走 early-return 路径或显式复用,否则泄漏会以更隐蔽的形式回归。
  • 冗余 defensive 检查 WARN_ON_ONCE(!sd->shared) 仍保留,但正常路径已经被早返回吃掉,仅作为代码重构时的安全网;review 时可以讨论是否需要进一步删减。
  • 顺手修了拼写,与 bugfix 无关。常规 maintainer 会一并接受;若偏好严格 1-改动-1-目的,可以拆成两个 commit。
  • 后续可观察点:复跑 Breno 的拓扑用例,确认 kmemleak 报告归零;并检查 sched_domain_shared 的 refcount 在 degenerate 前后始终匹配。

版本变化

仅 v1,无 v2→vN 演进。线程第二封邮件是同一作者的回复,仅引用原 commit message 与 diff 开头,不含新评审意见或代码改动。

与其他相关 patch 系列的关联

  • 根因 patch:9e005ed21152d("sched/topology: Allow multiple domains to claim sched_domain_shared")放宽了 init_sched_domain_shared 的复用规则,本次 patch 是该特性落地后的 correctness 二次手术,典型「先放开、再补护栏」的两段式落地。
  • 相关结构:sched_domain_shared共享模型同时影响 SDTL/SDSZ 对齐计算与 sched_group 的退化逻辑。后续如发现 refcountsched_domain_build_siblings / build_sched_domain 中失衡,可对照本 patch 的「一次 inc、一次 dec」语义排查。
  • 与 Reviewed-by标签协作:本 patch 已带 Tested-by: Breno LeitaoTested-by: Dietmar Eggemann,maintainer(如 Ingo / Peter / Vincent)通常会要求至少一个 sched维护者的 Reviewed-by 才合入 v6.x。

一句话总结

init_sched_domain_shared() 入口加 sd->shared 非空判断早返回,避免 SD_ASYM_CPUCAPACITYSD_SHARE_LLC 落到同一 sched_domain 时后一次覆盖前一次的 sched_domain_shared 造成 kmemleak,并顺手修一处注释拼写。