sched discussion
[PATCH v4] sched/isolation: Defer freeing of cpumask memblock memory to initcall
LLM 分析
sched/isolation:把 memblock cpumask 的释放推迟到 initcall
系列概况
- 标题: [PATCH v4] sched/isolation: Defer freeing of cpumask memblock memory to initcall
- 作者: Waiman Long longman@redhat.com
- 版本: v4(v4 的改动为「Collect tags and update commit log」)
- 规模: 单补丁(非系列),线程共 5 封邮件(1 封 patch + 3 封作者 ping/重发回复 + 1 封 tip-bot 合并通知)
- 修改文件:
kernel/sched/isolation.c - 代码统计: 1 file changed, 15 insertions(+), 1 deletion(-)
- Message-ID:
20260701195810.477326-1-longman@redhat.com - 完整性: 完整。已被 Peter Zijlstra 合入
tip:sched/core,Commit-ID2b58c749b8c5244e259a0230bc57b10b010dc545(CommitterDate 2026-08-13)
补丁目的
在 linux-next 上带 nohz_full= 启动参数时,housekeeping_init() 里对 memblock 内存调用 memblock_free() 触发了新加的告警(来自 commit 59bd1d914bb5,"memblock: warn when freeing reserved memory before memory map is initialized"):
Cannot free reserved memory because of deferred initialization of the memory map
WARNING: mm/memblock.c:904 at __free_reserved_area+0xde/0xf0, CPU#0: swapper/0/0
memblock_phys_free+0xcb/0x100
housekeeping_init+0x14c/0x170
start_kernel+0x207/0x450
根因:housekeeping_init() 在 start_kernel() 早期执行,此时 memory map 还没被 deferred_init_memmap() 完全初始化,把 reserved 内存还回去是不安全的。
目标:保留原有「用完就释放临时 cpumask」的语义,但把释放动作推迟到 initcall 阶段。
旧流程的问题
27c3a5967f05("sched/isolation: Convert housekeeping cpumasks to rcu pointers")之后,housekeeping_init() 会把早期用 memblock 分配的旧 cpumask(omask)复制到新 mask 并挂成 RCU 指针,然后立刻 memblock_free(omask, cpumask_size())。这个「立刻」发生在 memory map 尚未就绪的窗口内,于是踩中新告警。
新流程
不改分配路径,只把待释放的 omask 串成一个链表,等到 pure_initcall 时一次性 free。
start_kernel()
|
v
housekeeping_init() [memory map 未就绪]
|
| copy omask -> nmask, RCU_INIT_POINTER
|
+--> __llist_add(omask) --> memblock_freelist
| omask1 -> omask2 -> ... -> NULL
|
... 内核继续启动 ... |
deferred_init_memmap() 完成 |
| |
v v
pure_initcall 阶段 ----> housekeeping_late_init()
__llist_del_all()
for each node: memblock_free(node, cpumask_size())
关键实现
#include <linux/llist.h>
static __initdata LLIST_HEAD(memblock_freelist);
/* housekeeping_init(): 不再立刻释放 */
- memblock_free(omask, cpumask_size());
+ __llist_add((struct llist_node *)omask, &memblock_freelist);
static int __init housekeeping_late_init(void)
{
struct llist_node *llnode, *pos, *t;
/* Free allocated memblock memory, if any */
llnode = __llist_del_all(&memblock_freelist);
llist_for_each_safe(pos, t, llnode)
memblock_free(pos, cpumask_size());
return 0;
}
pure_initcall(housekeeping_late_init);
三个设计要点:
- 复用待释放内存本身当链表节点:
omask已经不再被读,直接把它 cast 成struct llist_node。llist_node只有一个next指针(一个long),而 cpumask 的最小分配尺寸也正好是一个long,所以不会越界。 - 用非原子版 llist API(
__llist_add/__llist_del_all):启动早期单线程,没有并发争用,省掉 cmpxchg。 pure_initcall+__initdata:链表头本身是 initdata,跑完 initcall 后连表头都被回收;pure_initcall是最早一档 initcall,此时 memory map 已就绪,但又尽早还内存。
依赖说明:commit message 明确写了本补丁依赖 7c2eee9c1367("memblock: don't touch memblock arrays when memblock_free() is called late"),否则晚调用 memblock_free() 会被 KASAN 报 UAF。
Fixes: 指向 27c3a5967f05,Reviewed-by 来自 Frederic Weisbecker 和 Phil Auld。
类比
想象你在装修房子的第一天(早期启动),工人临时用几块木板搭了个操作台(memblock 分配的 cpumask)。活干完了,你想立刻把木板扔进小区垃圾站——但垃圾站还没建好、门牌号还没登记(memory map 未初始化),保安会拦下你并贴一张警告条。
这个补丁的做法是:把这几块木板先靠墙码在门口,用一根绳子串起来(llist,绳子直接绑在木板上,不额外占地方),等垃圾站正式开张的第一天(pure_initcall)再一次性推过去扔掉。既没提前违规,也没把木板永远留在屋里。
线程本身的另一层「类比」:作者在 7/13、7/29、8/13 三次 ping 这封补丁——就像把包裹放在收发室后每两周去问一次,直到 8/13 维护者终于签收(tip-bot 合并通知)。
Highlight:风险与注意点
- 强依赖
7c2eee9c1367:如果补丁被 backport 到没有该 memblock 修复的树上,延后的memblock_free()会触碰已经失效的 memblock 数组,KASAN 会报 use-after-free。合并顺序必须保证。 - cast 的隐含假设:
(struct llist_node *)omask依赖「cpumask 最小分配 ≥ sizeof(long)」。如果将来 memblock 分配路径或 cpumask 尺寸约定变化,这个 cast 会静默越界写。commit log 已解释,但代码里没有BUILD_BUG_ON之类的静态检查,是可以跟进加固的点。 - 非原子 llist 的前提:
__llist_add/__llist_del_all无锁保护,正确性完全建立在「housekeeping_init 与 late_init 都是单线程、无并发」上。若以后有人在 SMP 上下文里复用这条链表,会静默出错。 - 只是把窗口移后,不是不释放:
pure_initcall是最早一档 initcall,需要确认 deferred memmap 初始化确实已完成;这也是选pure_initcall而非更晚 initcall 时值得复核的地方(当前无人在线程中提出异议)。 - 触发条件窄:只有带
nohz_full/isolcpus之类导致 housekeeping cpumask 被分配的启动参数才会命中,默认配置下看不到告警,容易被误认为「无关紧要」。
版本变化
- v4:收集 Reviewed-by tags(Frederic Weisbecker、Phil Auld)并更新 commit log;实现相对 v3 无功能改动。
- 线程中 v1(
20260505051821.1107133-1-longman@redhat.com)被反复引用作为上下文来源。
与其他相关 patch 的关联
59bd1d914bb5(memblock: warn when freeing reserved memory...)——告警的引入者,是本补丁的触发源。7c2eee9c1367(memblock: don't touch memblock arrays when memblock_free() is called late)——硬依赖。27c3a5967f05(sched/isolation: Convert housekeeping cpumasks to rcu pointers)——Fixes:目标,引入了这段早期 free 逻辑。
一句话总结
把 housekeeping_init() 里过早的 memblock_free() 改成「用待释放内存自身做 llist 节点先挂起、到 pure_initcall 再统一释放」,以 15 行代码消除了 nohz_full 启动下的 memblock 告警,已合入 tip:sched/core。