☰
page_alloc deferred_pages_enabled
2026/10/6 16:12:43 网站建设 项目流程

涉及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代码,但动态保证只在启动早期调用"的场景。

  • 包装的作用:

    1. 避免 section mismatch 警告:引用__init的是__ref函数,检查工具认可。

    2. 保证函数体被卸载:真正的deferred_grow_zone()仍是__init,初始化后其代码被释放,节省内存。

    3. 间接层: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()转调,既避免链接警告又允许其函数体被回收,而正确性由静态键在初始化完成后永久关闭来保证。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询