p4d_populate、pgd_populate、pud_populate、pmd_populate_kernel
2026/8/30 20:39:24 网站建设 项目流程

Linux 内核用来逐层构建页表(Page Table)的“建筑工人”,负责将更底层的内存页,挂载到上一级页表的目录项中。根据处理层级的不同,它们会接收不同“零件”(PMD表或PTE页表)并完成“安装”工作。

页表层级构建
pgd_populate() 处理PGD层级挂载PUD/PWD页表->p4d_populate() 处理P4D层级挂载PUD页表 (仅当启用了五级页表时存在)->pud_populate() 处理PUD层级挂载PMD页表->pmd_populate_kernel() 处理PMD层级挂载PTE页表

它们的核心区别体现在所在的页表层级操作的数据类型上:

函数所属层级主要参数核心作用
p4d_populateP4D (Page 4th Directory)p4d_t *p4d,pud_t *pud在启用了5级页表的系统中,将PUD页表挂载到P4D目录项中。
pgd_populatePGD (Page Global Directory)pgd_t *pgd,pud_t *pud在未启用5级页表的系统(即4级页表)中,将PUD页表直接挂载到PGD目录项。
pud_populatePUD (Page Upper Directory)pud_t *pud,pmd_t *pmd将PMD页表挂载到PUD目录项中。
pmd_populate_kernelPMD (Page Middle Directory)pmd_t *pmd,pte_t *pte内核空间的PTE页表挂载到PMD目录项中。

函数的两种变体

pmd_populate_kernel和其兄弟函数pmd_populate的主要区别在于处理的数据类型:

  • pmd_populate_kernel:用于将内核空间分配的PTE页表(一个连续的物理页,pte_t *类型)挂载到PMD项中。

  • pmd_populate:通常用于用户空间进程,它接收的是一个struct page *类型的数据,会先获取该页的物理地址再挂载。

设计考量:统一接口与地址传递

内核社区曾有过一个重要的补丁,为pmd_populate_kernel函数添加了一个vaddr(虚拟地址)参数。这个修改的意义在于:

  1. 为架构提供灵活性:某些架构(如ARM64)在填充PMD页表时,需要知道目标虚拟地址(vaddr)来做出正确决策。

  2. 避免逆向页表遍历:如果不传递vaddr,内核可能需要在运行时通过复杂的计算来反查这个地址,这会消耗性能。

因此,这个看似微小的参数传递,实际上是为了让页表的构建过程更高效、更稳健。

pgd_populate 实现原理:

pgd_populate的实现原理可以概括为:获取下级页表(如PUD或P4D)的物理地址,将其与页表项属性标志组合,然后写入到对应的PGD目录项中

核心实现:封装底层硬件操作

在 x86_64 架构的源码中,pgd_populate通常被实现为一个宏或内联函数,其核心逻辑非常简洁:

static inline void pgd_populate(struct mm_struct *mm, pgd_t *pgd, p4d_t *p4d) { if (!pgtable_l5_enabled()) return; paravirt_alloc_p4d(mm, __pa(p4d) >> PAGE_SHIFT); set_pgd(pgd, __pgd(_PAGE_TABLE | __pa(p4d))); }

这个实现可以拆解为三个关键步骤:

  1. 获取物理地址 (__pa(pud)):将指向下级页表(这里以PUD为例)的虚拟地址指针pud转换为物理地址。这是硬件最终需要的地址形式。

  2. 组合页表项属性 (_PAGE_TABLE | __pa(pud)):将物理地址与_PAGE_TABLE标志位进行按位或运算。_PAGE_TABLE是一个关键的属性标志,它告诉CPU这个页表项不仅指向物理内存,还包含了“存在”(Present)、“读/写”(Read/Write)等基本权限位,确保这个PGD条目是有效的。

  3. 写入PGD条目 (set_pgd):将组合好的完整页表项值写入到pgd指针所指向的PGD目录项中。这是一个体系结构相关的操作,用于将数据写入到指定的内存地址。

实现变体:多层级页表支持

pgd_populate的具体实现会因内核配置的页表层级数而有所不同,这是为了支持从4级到5级页表的平滑过渡。

  • 4级页表 (4-Level Paging):在标准的4级页表中(CONFIG_PGTABLE_LEVELS=4),PGD直接指向PUD。pgd_populate的实现正如上面定义所示,将一个PUD页表挂载到PGD项中。

  • 5级页表 (5-Level Paging):当启用5级页表时(CONFIG_PGTABLE_LEVELS=5),PGD与PUD之间插入了一个P4D层级。在这种情况下,pgd_populate的实现会相应地变为将一个P4D页表挂载到PGD项中。相关的补丁和讨论也说明了,为了让代码能正确处理这种层级变化,内核引入了如pgd_populate_kernel()这样的封装,并通过宏来确保这些调用在编译时能被正确优化或展开。

设计演进:统一接口与同步保障

pgd_populate的演进,特别是pgd_populate_kernel封装函数的引入,体现了内核开发中统一接口保障同步的设计思想。

  • 避免“忘记同步”:在早期实现中,每次修改顶层页表(如PGD)后,开发者需要手动调用架构特定的同步函数(如sync_global_pgds())来将改动同步到其他页表副本。这个过程容易遗漏,曾导致过系统崩溃。

  • 封装同步逻辑:为了解决这个问题,内核引入了统一的pgd_populate_kernel函数。它将实际的pgd_populate操作和必要的页表同步检查打包在一起。其核心逻辑可表示为:

    #define pgd_populate_kernel(addr, pgd, p4d) \ do { \ pgd_populate(&init_mm, pgd, p4d); \ if (ARCH_PAGE_TABLE_SYNC_MASK & PGTBL_PGD_MODIFIED) \ arch_sync_kernel_mappings(addr, addr); \ } while (0)

    通过这个封装,顶层页表更新的“填充”和“同步”两个步骤被组合成一个原子操作,极大地降低了因疏忽而引入bug的风险。

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

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

立即咨询