ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

page_alloc free_reserved_page

page_alloc free_reserved_page

free_reserved_page()是内核中用于释放单个保留页(reserved page)的底层函数。它是free_reserved_area()的核心执行单元,负责把一页从“保留”状态转为“可分配”状态并归还给伙伴系统。

核心实现

void free_reserved_page(struct page *page) { ClearPageReserved(page); init_page_count(page); page_mapcount_reset(page); page->mapping = NULL; adjust_managed_page_count(page, 1); __free_reserved_page(page); } EXPORT_SYMBOL(free_reserved_page);

逐步解析

1. 清除PageReserved标志

ClearPageReserved(page);
  • PG_reserved标志表示该页不参与常规内存管理——不能被分配、回收或迁移。

  • 保留页(如内核镜像、initrd、设备内存)都带这个标志。

  • 清除它,意味着这页现在可以进入伙伴系统了。

2. 初始化引用计数

init_page_count(page);
  • 把页的引用计数设为 1。

  • 这是页进入伙伴系统前的必要状态——伙伴系统要求空闲页的引用计数为 1(表示“仅被 buddy 引用”)。

3. 重置映射计数和 mapping

page_mapcount_reset(page); page->mapping = NULL;
  • page_mapcount_reset():把_mapcount重置为 -1(表示未被任何页表映射)。

  • page->mapping = NULL:清除可能残留的 address_space 关联。

  • 这两步确保页没有遗留的映射状态,否则free_pages_prepare()的校验会失败。

4. 调整管理页计数

adjust_managed_page_count(page, 1);

前面讲过的adjust_managed_page_count():把该页计入 zone 的managed_pages,同时更新totalram_pages(和可能的totalhigh_pages)。这是关键一步——保留页原本不计入managed_pages,释放后必须加入,否则水位计算、lowmem reserve 等会失准。

5. 归还伙伴系统

__free_reserved_page(page);

实际把页释放给伙伴系统。这个内部函数通常做:

static inline void __free_reserved_page(struct page *page) { __free_page(page); /* 或 free_frozen_pages */ }

即调用__free_page()(等价于__free_pages(page, 0))把页作为 order-0 释放。

与free_reserved_area()的关系

free_reserved_area(start, end, poison, s) └── for each page in range: ├── (可选) memset 毒化 └── free_reserved_page(page) // ← 这里 ├── ClearPageReserved ├── init_page_count ├── page_mapcount_reset ├── page->mapping = NULL ├── adjust_managed_page_count(page, 1) └── __free_reserved_page(page)

free_reserved_area()负责遍历范围、对齐、毒化、打印日志;free_reserved_page()负责单页的状态转换和释放。

为什么需要这么复杂?

保留页和普通页的“生命周期起点”不同:

状态保留页普通空闲页
PG_reserved置位清除
引用计数0 或特殊值1(仅 buddy 引用)
_mapcount可能残留-1
mapping可能非 NULLNULL
计入managed_pages否是

直接调用__free_page()会因为这些状态不一致而触发bad_page()或破坏伙伴系统。free_reserved_page()逐项修正这些状态,确保页“干净”地进入伙伴系统。

典型使用场景

1.free_reserved_area()的循环体

释放__init段、initrd、设备保留内存等。

2. 驱动释放预留内存

某些驱动在初始化时用memblock_reserve()保留内存,用完通过free_reserved_page()逐页归还。

3. CMA 区域初始化

CMA 预留区域建立后,页从保留状态转为可管理。

总结

free_reserved_page()是保留页的“净化与释放”函数:它清除PG_reserved、初始化引用计数、重置映射状态、更新managed_pages计数,最后通过__free_reserved_page()把页归还伙伴系统。它与free_reserved_area()配合,是内核把启动专用内存或设备预留内存转化为通用可分配内存的底层执行单元,确保页以“干净、一致”的状态进入伙伴系统。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进