检查页的当前状态是否符合“预期状态”。它是页面释放/分配路径上调试检查的一部分,用于发现页状态异常(“坏页”)。
一、函数签名
static inline bool page_expected_state(struct page *page, unsigned long check_flags)
参数
| 参数 | 类型 | 含义 |
|---|---|---|
page | struct page * | 要检查的页 |
check_flags | unsigned long | 调用方期望页应具有的额外标志位 |
返回值
bool:true表示页状态符合预期(正常);false表示不符合预期(异常/坏页)。
static inline
文件内私有 + 强制/建议内联。
在
check_new_page()、free_pages_prepare()等调试检查路径上调用。
二、背景:为什么需要“期望状态”检查?
1. 页状态异常的危害
struct page携带大量状态信息:
引用计数
_refcount;映射计数
_mapcount;标志位
flags:PG_locked、PG_dirty、PG_slab、PG_buddy、PG_hwpoison等;私有字段
private。
如果这些状态不符合预期,说明:
页被重复释放(double free);
页被释放时仍被引用(refcount 不为 0);
页的标志位异常(如同时
PG_slab和PG_buddy);页被内存损坏(位翻转、野指针写入);
页是硬件毒页(
PG_hwpoison)。
这些错误若不及时发现,会扩散成难以调试的崩溃或数据损坏。
2. 调试配置下的主动检查
在CONFIG_DEBUG_VM等配置下,内核会在分配新页和释放页时做一系列一致性检查:
check_new_page():分配新页后检查页是否处于“干净可用的初始状态”;free_pages_prepare():释放页前检查页是否处于“可安全释放的状态”。
page_expected_state()就是这些检查中的核心判断函数。
三、典型实现
static inline bool page_expected_state(struct page *page, unsigned long check_flags) { /* * 1. 引用计数必须为 0(页没有被使用) */ if (atomic_read(&page->_refcount) != 0) return false; /* * 2. 映射计数必须为 -1 或 0(页没有被映射) * (不同版本约定不同,有的用 page_mapcount()) */ if (page_mapcount(page) != -1 && page_mapcount(page) != 0) return false; /* * 3. 检查标志位: * - 不能有 PG_slab / PG_buddy / PG_hwpoison 等互斥标志; * - 不能有 check_flags 中未允许的位。 */ if (unlikely(page->flags & check_flags)) return false; /* * 4. 某些版本还检查 page->mapping、page->private 等 */ ... return true; }核心检查项
1. 引用计数_refcount
if (atomic_read(&page->_refcount) != 0) return false;
页要被释放/复用,引用计数必须归零。
不为 0 说明还有人在用它 → 释放是非法的(可能 double free 或提前释放)。
2. 映射计数_mapcount
if (page_mapcount(page) != -1 && page_mapcount(page) != 0) return false;
页不应被任何页表映射。
-1或0表示无映射(具体约定随版本)。
3. 标志位检查
if (unlikely(page->flags & check_flags)) return false;
check_flags是调用方给出的“不应出现的标志位掩码”。如果页带有这些位之一 → 状态异常。
典型
check_flags包括:PG_slab:页不应属于 slab;PG_buddy:页不应已经是空闲 Buddy(否则是 double free);PG_hwpoison:页不应是硬件毒页;PG_owner_priv_1等;具体组合由调用方决定。
4. 其他字段
某些版本还检查
page->mapping是否为空、page->private是否异常等。视内核版本和配置而定。
四、check_flags参数的意义
check_flags让不同调用场景可以指定各自关心的标志位:
分配新页时(check_new_page())
if (!page_expected_state(page, PAGE_FLAGS_CHECK_AT_PREP)) { bad_page(page, "unexpected page state at alloc"); return false; }PAGE_FLAGS_CHECK_AT_PREP:分配准备阶段不应出现的标志位掩码。分配到的页应该是干净、未被使用的。
释放页时(free_pages_prepare())
if (!page_expected_state(page, PAGE_FLAGS_CHECK_AT_FREE)) { bad_page(page, "unexpected page state at free"); ... }PAGE_FLAGS_CHECK_AT_FREE:释放阶段不应出现的标志位掩码。被释放的页应该是已不再被引用的。