0/6 已展开

LLM 分析

sched/topology: 给 partition_sched_domains() 加上 cpus_read_lock

系列概况

  • 标题: [PATCH] sched/topology: Add a cpus_read_lock to partition_sched_domains()
  • 作者: Sebastian Andrzej Siewior bigeasy@linutronix.de
  • 版本: 单补丁 v1(无版本号,无 cover letter,单 patch)
  • 规模: kernel/sched/topology.c 1 个文件,1 行新增
  • 修改文件: kernel/sched/topology.c
  • 代码统计: +1 / -0
  • Message-ID: 20260812095800.gl06ANul@linutronix.de
  • 完整性: 完整,包含 commit message、Fixes tag、Signed-off-by 和 diff

补丁目的

该 patch 修复一个真实的锁缺失问题:从 sysctl 接口读取 /proc/sys/kernel/sched_rt_runtime_us 时,会触发 sched_rt_handler() -> partition_sched_domains() -> sched_cache_set() -> static_key_enable_cpuslocked() 的调用链。

其中 _cpuslocked() 接口要求 CPU 热插拔锁已被持有,但 partition_sched_domains() 只拿了 sched_domains_mutex,没拿 cpus_read_lock(),因此会触发"missing cpu_hotplug_lock"的回溯告警。

修复方法是在 partition_sched_domains() 入口处加一个 guard(cpus_read_lock)(),与 sched_cache_active_set() 的锁获取顺序保持一致(hotplug lock 在 sched_domains_mutex 之前获取)。

旧流程的问题

userspace
   |
   v  read /proc/sys/kernel/sched_rt_runtime_us
sched_rt_handler()
   |
   v
partition_sched_domains()       <-- only sched_domains_mutex held
   |
   v
sched_cache_set()
   |
   v
static_key_enable_cpuslocked(&sched_cache_present)  <-- needs cpus_read_lock!
   |
   v
WARN_ON(!cpus_read_lock_held())  -> backtrace printed

调用链中至少存在两路进入 sched_cache_set()

  1. sched_init_domains()(boot 早期)—— 此时 user space 没起来、不会有 hotplug,所以没有告警。
  2. sched_rt_handler()(sysctl 写路径)—— 正常运行时会触发 hotplug 并发的告警。

新流程

userspace
   |
   v  read /proc/sys/kernel/sched_rt_runtime_us
sched_rt_handler()
   |
   v
partition_sched_domains()
   |   + guard(cpus_read_lock)();   <-- NEW: acquire hotplug lock first
   |     sched_domains_mutex_lock();     <-- then take domain mutex
   v
partition_sched_domains_locked()
   |
   v
sched_cache_set() -> static_key_enable_cpuslocked()   <-- lock held, OK

锁顺序变成 cpus_read_lock -> sched_domains_mutex -> ... -> _cpuslocked(),与 sched_cache_active_set() 一致。

Patch 概览

  • 单 patch,仅修改 kernel/sched/topology.c,1 行新增
  • 引用 Fixes tag:a7660ce1590fc ("sched/cache: Fix has_multi_llcs iff at least one partition has multiple LLCs"),指明引入该问题的 commit

关键实现

修改位于 kernel/sched/topology.cpartition_sched_domains() 函数(行 3498 附近):

void partition_sched_domains(int ndoms_new, cpumask_var_t doms_new[],
                             struct sched_domain_attr *dattr_new)
{
+       guard(cpus_read_lock)();
        sched_domains_mutex_lock();
        partition_sched_domains_locked(ndoms_new, doms_new, dattr_new);
        sched_domains_mutex_unlock();
}

要点:

  • 使用 guard() 宏做作用域清理,函数返回时自动释放 cpus_read_lock,无需手写 cpus_read_unlock()
  • 放在 sched_domains_mutex_lock() 之前,对齐"先 hotplug、再 domain mutex"的现有约定。
enter partition_sched_domains()
   |
   v  guard(cpus_read_lock)()   (RAII: acquire on enter)
partition_sched_domains_locked()
   |
   v
static_key_enable_cpuslocked() / static_key_disable_cpuslocked()
   |
   v  WARN disappears
function returns  ->  cpus_read_unlock() auto via guard

类比

把 CPU hotplug 锁想成"商场大门的门禁":系统启动后大门只允许单向通行(CPU 进入/离开),任何对 CPU 拓扑的"改装"都必须先在大门刷一下卡。sched_domains_mutex 则是店内的"仓库钥匙",用来保护货架上的调度域数据。

旧实现:店员(sched_rt_handler)一进门直接去仓库取货架(调 partition_sched_domains),结果走到 sched_cache_set 这个货架时,店里规定必须先刷门禁卡,否则报警。

新实现:店员进仓库前先在大门刷一下卡(guard(cpus_read_lock)),再到仓库开锁取货(sched_domains_mutex),这样走到 _cpuslocked 货架时门禁卡已经在身上,警报消失。

Highlight:风险与注意点

  • 放置位置的争议: Chen Yu 与 Tim Chen 都注意到 partition_sched_domains() 函数上方有注释写 "Call with hotplug lock held"。这意味着历史上调用方应自行持有 hotplug 锁。Tim 指出 CONFIG_CPUSETS=n 时,rebuild_sched_domains()cpuset_reset_sched_domains() 都被 stub 为 partition_sched_domains(1, NULL, NULL),不会主动拿 cpus_read_lock。Sebastian 选择在 partition_sched_domains() 内补锁,虽然技术正确,但与既有"调用方负责"约定有偏离,后续维护者需要警惕"既是调用方负责又是 callee 保护"的双重语义。
  • 锁嵌套顺序: cpus_read_locksched_domains_mutex 的相对顺序一旦确立,全栈都必须遵守,否则会有 AB-BA 死锁风险。当前 patch 选在 sched_domains_mutex 之前获取,需要检查所有调用 partition_sched_domains() 的栈是否仍持有更外层的锁。
  • boot 早期路径: sched_init_domains() 也会调到 partition_sched_domains_locked(),但走的是 _locked() 版本(已被外层函数保护),不会重复上锁,安全。
  • reviewer 一致性建议: Tim 提议为 CONFIG_CPUSETS=n 下的 cpuset_reset_sched_domains() 等 stub 同步加锁以保持一致;如果接受,会演变为多 patch 修复集。
  • 后续观察: 是否会引发 v2,引入 rebuild_sched_domains() / cpuset_reset_sched_domains() 锁补充;是否需要把 hotplug 锁下推到 callee 而不是调用方。

版本变化

  • v1(当前):在 partition_sched_domains() 入口直接 guard(cpus_read_lock)(),最小改动修复告警。
  • 后续潜在改动(讨论中):在 CONFIG_CPUSETS=n 的 stub rebuild_sched_domains() / cpuset_reset_sched_domains() 加锁;或在 callee 加锁的同时考虑移除"Call with hotplug lock held"的注释。

一句话总结

partition_sched_domains() 入口加 cpus_read_lock() 守卫,修复 sysctl 写路径触发"missing cpu_hotplug_lock"告警;reviewers 提示在 CONFIG_CPUSETS=n 的 stub 调用方也需保持锁顺序一致。

与其他相关 patch 系列的关联

本 patch 引用 Fixes: a7660ce1590fc —— sched/cache 的 multi-LLC 修复 commit,正是它让 sched_cache_set() 在 sysctl 路径上暴露了锁缺失;本 patch 是对该引入问题的针对性补丁。