0/4 已展开

LLM 分析

sched/isolation: Defer freeing of the bootmem housekeeping cpumasks

系列基线信息

字段内容
标题[PATCH] sched/isolation: Defer freeing of the bootmem housekeeping cpumasks
作者Ionut Nechita (ionut.nechita@windriver.com)
版本v1(单 patch)
规模1 file, +23 / -1
Message-ID20260802115623.110513-1-ionut.nechita@windriver.com
来源Bugzilla #221804, 经 Andrew Morton 转发至邮件列表
相关 CommitFixes: 27c3a5967f05 (v7.0), 警告自 v7.1 的 59bd1d914bb5 起可见

明确目的

housekeeping_init()start_kernel() 中被调用,此时 slab 已可用但
page_alloc_init_late() 尚未完成延迟内存映射的初始化。当
CONFIG_DEFERRED_STRUCT_PAGE_INIT=y 时,memblock_free()
__free_reserved_area() 路径,后者拒绝操作未初始化的内存映射,触发
WARNING 并污染内核 (G W taint)。

本 patch 的目标:将 memblock_free() 的调用推迟到 core_initcall
确保在延迟内存映射初始化完成后再释放 bootmem cpumask,消除启动警告。

遍历代码

问题根因

  1. housekeeping_setup() 在命令行解析阶段通过 memblock 分配 cpumask
  2. housekeeping_init()kmalloc() 重新分配后,调用 memblock_free() 释放旧 mask
  3. memblock_free()memblock_phys_free()__free_reserved_area()
    此时 slab_is_available()=true,走了 buddy allocator 路径
  4. page_alloc_init_late() 尚未运行,延迟 struct page 未初始化
  5. __free_reserved_area() 拒绝操作,打印 WARNING

修复逻辑

// 新增:记录待释放的 bootmem mask 指针
static struct cpumask *housekeeping_bootmem_masks[HK_TYPE_MAX] __initdata;

// housekeeping_init() 中:不再直接 memblock_free()
// 改为:
housekeeping_bootmem_masks[type] = omask;

// 新增 core_initcall:延迟释放
static int __init housekeeping_free_bootmem_masks(void)
{
    enum hk_type type;
    for (type = 0; type < HK_TYPE_MAX; type++)
        memblock_free(housekeeping_bootmem_masks[type], cpumask_size());
    return 0;
}
core_initcall(housekeeping_free_bootmem_masks);

关键设计决策:

  • 使用 core_initcall 而非 early_initcall,因为后者在
    page_alloc_init_late() 之前运行
  • kmalloc() 失败,旧 mask 不被记录,保持原有错误行为不变
  • __initdata 标记确保数组在 init 阶段后回收

ASCII 流程图

Before fix:
  start_kernel()
    |-- housekeeping_init()
    |     |-- kmalloc(new mask)
    |     |-- memblock_free(old mask)  <-- BOOM: deferred map not ready
    |           |-- __free_reserved_area()  --> WARNING!
    |
    |-- ... (much later) ...
    |-- page_alloc_init_late()  <-- deferred map initialized here

After fix:
  start_kernel()
    |-- housekeeping_init()
    |     |-- kmalloc(new mask)
    |     |-- housekeeping_bootmem_masks[type] = old mask  <-- just record
    |
    |-- page_alloc_init_late()  <-- deferred map initialized
    |
    |-- do_initcalls()
          |-- core_initcall(housekeeping_free_bootmem_masks)
                |-- memblock_free(recorded masks)  <-- safe now!

Initcall ordering:
  early_initcall  <-- too early (before page_alloc_init_late)
  core_initcall   <-- just right
  late_initcall   <-- would work but unnecessarily late

概念类比

想象一个图书馆搬家的场景:

  • 旧书架(bootmem mask):搬家前用临时木架存放图书
  • 新书架(kmalloc mask):正式的铁质书架
  • 搬书(kmalloc + copy):把书从木架搬到铁架
  • 拆除木架(memblock_free):拆掉旧木架回收木材

问题是:拆除木架时需要记录木材去向(__free_reserved_area 更新内存映射),
但负责记录的账本(deferred memory map)还没写好——会计还没上班。

修复方案:不急着拆木架,先记下"哪些木架需要拆除",
等会计上班后再统一拆除。

Highlight 突出问题

  1. 实际内存无泄漏:cpumask 远小于一个 page,__free_reserved_area()
    本就没有整页可还给 buddy allocator,所以即使 WARNING 触发,也不会真正丢失内存。
    但 taint 标记 (G W) 会影响生产环境判断。

  2. early_initcall 仍太早kernel_init_freeable() 中的
    do_pre_smp_initcalls() 运行在 page_alloc_init_late() 之前,
    必须用 core_initcall 或更晚的级别。

  3. kmalloc 失败路径:patch 保留了原有行为——如果 kmalloc() 失败,
    旧 mask 不被记录,因此不会被释放。这是正确的保守策略,但值得注意。

  4. 未 Cc stable:作者认为这只是 WARNING + taint,没有实际内存泄漏,
    因此未主动回溯到 stable。如果下游发行版对 taint 敏感,可能需要自行 backport。

  5. Mike 的替代方案:Mike Rapoport 曾建议将所有 HK_TYPE_MAX 个 mask
    合并为一次 memblock 分配/释放,但作者认为改动更大且无额外收益,选择了更简单的方案。

版本演进

本 thread 为 v1 patch,无前序版本。讨论线索:Bugzilla 报告 → Andrew Morton 转发并
用 Gemini 分析 → Mike Rapoport 提出建议 → Ionut Nechita 提交 patch。

与其他相关 patch 系列的关联

  • 27c3a5967f05 ("sched/isolation: Convert housekeeping cpumasks to rcu pointers"):
    引入问题的 commit,自 v7.0 起存在
  • 59bd1d914bb5 ("memblock: warn when freeing reserved memory before memory map is initialized"):
    v7.1 新增的 WARN,使问题变得可见

一句话总结

housekeeping_init() 中对 bootmem cpumask 的 memblock_free() 推迟到
core_initcall 执行,避免在延迟内存映射未初始化时触发 WARNING。