sched discussion
Re: [Bug 221804] New: WARNING at mm/memblock.c:904 __free_reserved_area(): "Cannot free reserved memory because of deferred initialization of the memory map" during housekeeping_init() at boot
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-ID | 20260802115623.110513-1-ionut.nechita@windriver.com |
| 来源 | Bugzilla #221804, 经 Andrew Morton 转发至邮件列表 |
| 相关 Commit | Fixes: 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,消除启动警告。
遍历代码
问题根因
housekeeping_setup()在命令行解析阶段通过 memblock 分配 cpumaskhousekeeping_init()用kmalloc()重新分配后,调用memblock_free()释放旧 maskmemblock_free()→memblock_phys_free()→__free_reserved_area(),
此时slab_is_available()=true,走了 buddy allocator 路径- 但
page_alloc_init_late()尚未运行,延迟 struct page 未初始化 __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 突出问题
-
实际内存无泄漏:cpumask 远小于一个 page,
__free_reserved_area()
本就没有整页可还给 buddy allocator,所以即使 WARNING 触发,也不会真正丢失内存。
但 taint 标记 (G W) 会影响生产环境判断。 -
early_initcall仍太早:kernel_init_freeable()中的
do_pre_smp_initcalls()运行在page_alloc_init_late()之前,
必须用core_initcall或更晚的级别。 -
kmalloc失败路径:patch 保留了原有行为——如果kmalloc()失败,
旧 mask 不被记录,因此不会被释放。这是正确的保守策略,但值得注意。 -
未 Cc stable:作者认为这只是 WARNING + taint,没有实际内存泄漏,
因此未主动回溯到 stable。如果下游发行版对 taint 敏感,可能需要自行 backport。 -
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。