涉及deferred pages(延迟初始化页)机制,以及为它设计的一个__ref包装函数。逐块拆解。
一、deferred_pages_enabled()—— 延迟页机制是否启用
static inline bool deferred_pages_enabled(void) { return static_branch_unlikely(&deferred_pages); }1.deferred_pages是什么?
一个静态键(static key),定义在别处(通常是
DEFINE_STATIC_KEY_TRUE/FALSE(deferred_pages))。它标记"deferred pages 机制当前是否处于启用状态"。
初始通常是true(启用),因为启动早期伙伴系统还没完全初始化好。
2. 背景:为什么需要 "deferred pages"(延迟页)?
核心矛盾:启动早期,内存还没全部初始化完,但已经有代码需要分配内存。
伙伴系统初始化是分阶段的:先初始化一部分内存,让系统能跑起来,再逐步初始化剩下的。
在完全初始化之前,如果某次分配发现某个 zone 的空闲内存不够,正常路径会:
唤醒 kswapd → 但 kswapd 还没就绪;
直接回收 → 但 vmscan 还没就绪;
OOM → 显然不能。
所以需要一种机制:"这个 zone 还有没初始化完的内存,我先把它们初始化,再重试分配"。
这就是deferred pages(延迟初始化页):把"尚未加入伙伴系统的内存"延迟到真正需要时再加入。
deferred_grow_zone(zone, order)就是干这个的:
给 zone 补充(grow)尚未初始化的内存,使其可用。
3. 为什么用static_branch_unlikely?
静态键 +
unlikely:deferred_pages只在启动早期为 true,之后永久关闭(所有内存初始化完毕)。运行期绝大多数时间它是 false,所以分支预测应偏向"不启用"。
static_branch_unlikely让编译器生成偏向 false 的代码;当键被关闭时,分支变成 NOP 或跳转,几乎零开销。
用静态键而非普通
if (deferred_pages):这是长期不变、启动后固定的状态,最适合静态键。
关闭后,
deferred_grow_zone调用点被优化掉,不浪费热路径性能。
4. 调用点(示意)
static struct page * get_page_from_freelist(gfp_t gfp, unsigned int order, ...) { ... if (deferred_pages_enabled()) if (_deferred_grow_zone(zone, order)) goto try_this_zone; // 重试分配 ... }即在get_page_from_freelist()里,如果正常分配失败且 deferred pages 还启用,就尝试"补充 zone 内存"然后重试。
二、_deferred_grow_zone()——__ref包装函数
/* * deferred_grow_zone() is __init, but it is called from * get_page_from_freelist() during early boot until deferred_pages permanently * disables this call. This is why we have refdata wrapper to avoid warning, * and to ensure that the function body gets unloaded. */ static bool __ref _deferred_grow_zone(struct zone *zone, unsigned int order) { return deferred_grow_zone(zone, order); }1. 问题:__init函数被非__init代码调用
deferred_grow_zone()被标记为__init:__init表示"只在初始化阶段使用"。链接器会把它放到
.init.text段,初始化完成后整段内存被释放(函数体被卸载)。
但它被
get_page_from_freelist()调用,而后者是运行期常驻函数,不是__init。这会产生两个问题:
问题 1:编译/链接警告
内核有检查机制(如
modpost、section mismatch检查),会警告"非__init函数引用了__init函数"。因为运行期调用一个已被卸载的函数是未定义行为。
问题 2:
__init段被卸载后,调用就悬空虽然实际上
deferred_pages关闭后这个调用不会再发生,但代码路径依然存在,静态检查不认这个"动态保证"。
2. 解决:__ref包装
static bool __ref _deferred_grow_zone(struct zone *zone, unsigned int order) { return deferred_grow_zone(zone, order); }__ref属性:__ref是内核提供的一个标记,告诉链接器/检查工具:"这个函数是有意引用__init代码的,不要报警告"。它把函数放到
.ref.text段(而非.init.text),不会被卸载。用于这种"运行期代码需要引用
__init代码,但动态保证只在启动早期调用"的场景。
包装的作用:
避免 section mismatch 警告:引用
__init的是__ref函数,检查工具认可。保证函数体被卸载:真正的
deferred_grow_zone()仍是__init,初始化后其代码被释放,节省内存。间接层:
get_page_from_freelist()调用_deferred_grow_zone()(常驻),后者转调deferred_grow_zone()(__init)。
3. 注释里的 "refdata wrapper"
注释说 "we have refdata wrapper":
__ref相关的还有__refdata(用于数据),这里用的是函数版__ref。"refdata" 是泛指这套 "reference to init section" 的机制。
4. 安全性依赖
这个包装本身不保证安全,安全性依赖:
deferred_pages_enabled()在初始化完成后返回 false;get_page_from_freelist()只在deferred_pages_enabled()为 true 时才调用_deferred_grow_zone();所以运行期不会真正调用到已被卸载的
deferred_grow_zone()。
即:动态条件保证正确性,__ref只是让静态检查通过并允许段卸载。
三、完整调用链与生命周期
启动早期: deferred_pages = true get_page_from_freelist() └── if (deferred_pages_enabled()) // true └── _deferred_grow_zone() // __ref,常驻 └── deferred_grow_zone() // __init,补充 zone 内存 ... 内存全部初始化完成 ... static_branch_disable(&deferred_pages) // 永久关闭 运行期: deferred_pages = false get_page_from_freelist() └── if (deferred_pages_enabled()) // false,静态键跳过 // 不会调用 _deferred_grow_zone() deferred_grow_zone() 所在 .init.text 段已被释放
四、与前面内容的呼应
| 前面出现过的 | 这里的关联 |
|---|---|
vm_numa_stat_key(DEFINE_STATIC_KEY_TRUE) | 同样是静态键,deferred_pages也是 |
static_branch_likely/unlikely | 这里用static_branch_unlikely,因运行期几乎总是 false |
__read_mostly | 类似优化思路:区分冷热数据/代码 |
__init段卸载 | 内核启动内存回收的一部分,deferred_grow_zone是__init |
五、小结表
| 代码 | 作用 |
|---|---|
deferred_pages(静态键) | 标记延迟页机制是否启用,启动早期 true,之后永久 false |
deferred_pages_enabled() | 用static_branch_unlikely零开销地检查该键 |
deferred_grow_zone() | __init函数,补充 zone 尚未初始化的内存 |
_deferred_grow_zone() | __ref包装,避免 section mismatch 警告,允许__init函数体被卸载 |
| 调用点 | get_page_from_freelist()分配失败时,若机制启用则补充后重试 |
一句话总结
deferred_pages是一个静态键,标记"延迟初始化页"机制是否启用——启动早期为 true,分配失败时可通过deferred_grow_zone()补充 zone 内存并重试;该函数是__init,运行期会被卸载,所以用__ref包装函数_deferred_grow_zone()转调,既避免链接警告又允许其函数体被回收,而正确性由静态键在初始化完成后永久关闭来保证。