pte_alloc_one是 Linux 内核中用于为一个用户进程分配一个 PTE(页表项)页表页的架构相关函数。它位于 x86 架构的页表管理代码中,是进程页表构建的基础操作之一。
核心作用:分配用户页表页
当进程需要建立新的 4KB 页映射时,如果对应的 PTE 页表页还不存在,内核就会调用pte_alloc_one来分配一个新的页表页。这个页表页将用于存放该虚拟地址范围内所有 4KB 页的 PTE 条目。
函数封装逻辑
pgtable_t pte_alloc_one(struct mm_struct *mm) { return __pte_alloc_one(mm, GFP_PGTABLE_USER); }这个函数非常简洁,它只是将工作委托给__pte_alloc_one,并传入固定的分配标志GFP_PGTABLE_USER。
GFP_PGTABLE_USER:这是专为用户页表页设计的分配标志。它组合了GFP_KERNEL_ACCOUNT(将分配计入进程的内存 cgroup 统计)和__GFP_ZERO(分配后自动清零),并隐含了GFP_KERNEL的基本语义。
__pte_alloc_one的工作
__pte_alloc_one是实际的分配函数,它的核心逻辑是:
分配页表页:调用
alloc_pages()分配一个物理页,用于存放 512 个 PTE 条目。清零页表:由于
GFP_PGTABLE_USER包含__GFP_ZERO,分配器会返回一个已清零的页面。统计与记账:通过
pgtable_pte_page_ctor()等函数,将页表页纳入页表统计,并可能进行 cgroup 内存记账。返回句柄:返回
pgtable_t类型(在 x86 上就是struct page *),供上层代码进一步操作。
与pmd_alloc等的协作
在页表构建流程中,pte_alloc_one通常由pte_alloc()或__pte_alloc()调用。当内核需要为某个地址建立 PTE 映射,而发现 PMD 条目为空时,就会触发这一分配。整个过程是:
pmd_alloc() → __pte_alloc() → pte_alloc_one() → __pte_alloc_one()
pte_alloc_one是架构相关的封装层,它确保了用户页表页的分配带有正确的标志(计入用户内存、自动清零),而将实际的分配细节留给通用的__pte_alloc_one。
pte_alloc_one被调用时,伙伴系统已经启动完成了。
伙伴系统的启动时机
伙伴系统(Buddy System)的初始化,发生在内核启动流程的mem_init()阶段。这个函数会调用memblock_free_all(),将启动早期由 memblock 管理的所有空闲内存,通过__free_pages()系列函数逐页释放并注册到伙伴系统中。
这个过程完成后,伙伴系统才正式“上线”,开始能够处理内存分配请求。
pte_alloc_one的调用时机
pte_alloc_one是用于为用户进程分配 PTE 页表页的架构相关函数。它的调用场景主要有两个:
用户进程创建时:当
fork()创建新进程,或者exec()加载新程序时,内核需要为新进程构建页表。缺页异常处理时:当用户进程访问一个尚未建立映射的地址,触发缺页异常,内核在
handle_mm_fault()中可能需要分配新的 PTE 页表页。
为什么必然在伙伴系统之后
关键在于,pte_alloc_one内部最终会调用alloc_pages()来获取物理页,而alloc_pages()正是伙伴系统的核心分配接口。
从启动流程来看,用户进程的创建和缺页异常处理,都发生在mem_init()完成之后。此时,memblock已经把内存管理权完全交接给了伙伴系统,after_bootmem标志也被置位。因此,当pte_alloc_one被调用时,伙伴系统早已准备就绪,可以安全地为其分配页表页。