vm_normal_page() 函数:作用与实现流程
核心作用
vm_normal_page() 是 Linux 内存管理的关键底层函数,将有效的页表项(PTE)安全转换为对应的物理页描述符(struct page*)。
它不增加引用计数,仅负责从 PTE 中解析出页帧信息,并处理特殊内存映射(如零页、设备内存)。
核心任务:当 PTE 已存在(pte_present() == true)时,将 pte_t 映射到 “标准”物理页帧的 struct page*,同时过滤内核特殊页(如零页)。
实现流程详解
函数定义如下:
struct page *vm_normal_page(struct vm_area_struct *vma,
unsigned long addr,
pte_t pte);
输入:
vma: 虚拟地址所属的 VMA(内存区域描述符)。
addr: 目标虚拟地址(用于检测特殊页)。
pte: 有效的页表项(需满足 pte_present(pte) == true)。
输出:
正常页:返回对应的 struct page*。
特殊页:处理零页等场景后返回其 struct page*。
无效页:返回 NULL(如设备私有内存)。
具体流程:
检查 PTE 是否为特殊页:
if (pte_special(pte)) // PTE 标记为特殊项?
return NULL; // 不处理(如设备内存)
处理零页(Zero Page):
if (pte_zero(pte)) { // PTE 指向零页?
return pte_zero_page(addr); // 返回全局零页的 page*
}
零页优化:所有全零物理页共享单个全局页(ZERO_PAGE(0)),避免重复分配。
提取物理页帧号(PFN):
unsigned long pfn = pte_pfn(pte); // 从 PTE 中提取 PFN
验证 PFN 有效性:
if (unlikely(!pfn_valid(pfn))) { // PFN 超出物理内存范围?
return NULL;
}
过滤无效 PFN(如空洞地址、未映射设备内存)。
处理设备直接映射内存(如 GPU VRAM):
if (is_pci_p2pdma_page(pfn_to_page(pfn))) { // PCI P2PDMA 内存?
return NULL; // 需专用 API 访问
}
此类内存由设备驱动管理,标准内存路径无法访问。
透明大页(THP)子页处理:
if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE)) {
if (pmd_trans_unstable(pmd)) { // THP 正在分裂?
return NULL; // 需上层重试
}
}
返回标准物理页:
return pfn_to_page(pfn); // 转换 PFN → struct page*
pfn_to_page() 通过全局数组 mem_map[] 定位物理页描述符。
关键特性总结
前置条件严格:
必须保证输入的 PTE 有效(pte_present(pte) == true)。若 PTE 无效(如页面被换出),应由上层(如 follow_page())先触发缺页中断。
零页透明处理:
将零页(pte_zero())映射到全局共享的物理零页,减少内存占用。
设备内存隔离:
显式拒绝 PCI P2PDMA 等特殊内存的转换(返回 NULL),强制驱动使用专用接口(如 get_dev_pagemap())。
无副作用:
不操作页面引用计数,纯查询函数。页面生命期由调用者管理(如 get_user_pages() 负责固定)。
透明大页支持:
检测 THP 分裂状态,避免返回中间态的不稳定页。
在内存访问流程中的角色
在典型的用户空间内存访问路径中:
get_user_pages()
└─> follow_page_mask() // 遍历页表获取 PTE
└─> vm_normal_page(pte) // 转换 PTE → struct page*
follow_page_mask() 在获得有效 PTE 后调用 vm_normal_page() 转换物理页。
若 vm_normal_page() 返回非 NULL,follow_page_mask() 将其结果(struct page*)返回给 get_user_pages(),后者再固定页面。
典型调用场景
// 示例:在 follow_page_mask() 中调用
pte_t pte = *pte_offset_map(...);
if (pte_present(pte)) {
struct page *page = vm_normal_page(vma, addr, pte);
if (page)
return page; // 返回物理页描述符
}
return NULL; // PTE 无效或特殊页
总结:vm_normal_page() 是内存管理基础设施中的精确定向转换器,专注解决 “有效的 PTE 对应哪个物理页?” 的问题,为上层提供标准物理页描述符,同时屏蔽零页/设备内存等特殊细节。