phys_pud_init
phys_pud_init是 x86 架构 Linux 内核中,负责在 PUD(Page Upper Directory,页上级目录)层级填充页表项的核心函数,其实现原理是一套精密的内存映射“流水线”。
它的工作,可以看作是在__kernel_physical_mapping_init的统筹下,作为“分包商”承接并处理一个具体的物理地址区间,为其建立或更新内存映射。
核心实现逻辑:分层填充
该函数从虚拟地址的角度出发,遍历指定 PUD 页表中的每一个条目,为每一个需要映射的物理地址范围选择最高效的方式建立映射。
关键设计演进与细节
引入
init参数:phys_pud_init新增了init参数,用于在启动早期与内存热插拔等运行时场景之间进行区分。这允许内核在修改页表时,根据场景选择不同的同步策略,例如避免在启动初期进行耗时操作或改变锁的使用方式。1GB 大页拆分时的 Bug 修复:当函数需要将一个已存在的 1GB 大页拆分为 2MB 小页时,会触发一个警告。原因是拆分前未清除旧的 PUD 条目,而后续的
set_pud_safe()函数会检测到冲突并报出WARN_ON_ONCE。修复方法是在拆分前增加pud_clear(pud)调用,清除旧的大页映射条目,使新的 PMD 页表能顺利链接。KASLR 支持:在内核地址空间随机化(KASLR)引入时,
phys_pud_init曾被修正。由于 KASLR 使得PAGE_OFFSET不再是 PUD 对齐的,原先直接对物理地址使用pud_index的逻辑会失效。修正方法是在计算 PUD 索引前,先将物理地址通过__va转换为虚拟地址,以确保pud_index总能得到正确的结果。
异常处理与内存空洞
函数内部还会检查区间内是否存在内存空洞(非 RAM 区域)。若检测到,它会清除对应的 PUD 条目,保证映射的精确性,防止内核访问到不应映射的物理地址。
phys_pmd_init
phys_pmd_init是 x86 架构 Linux 内核在启动阶段,负责在PMD(页中间目录)层级建立物理内存直接映射的核心函数。它的作用可以看作是前序phys_pud_init工作的“分包商”,专门处理一个具体的物理地址区间,并决策使用2MB 大页还是4KB 标准页来完成映射,以平衡性能与内存效率。
核心实现逻辑:PMD 层级的映射决策
phys_pmd_init的实现原理是一个高度结构化的遍历过程。它在给定的 PMD 页表中,从当前物理地址paddr对应的条目开始,逐项处理,直到超出目标区间paddr_end。
其核心决策树如下:
遍历与边界检查:函数遍历 PMD 页表。如果当前地址
paddr已经达到或超过终点paddr_end,则停止映射,并清除该条目(set_pmd_init(pmd, __pmd(0), init))。一个早期的补丁确保了这项清理操作只在after_bootmem之前执行,避免破坏已建立的映射。处理已存在的映射:如果 PMD 条目已经存在(
!pmd_none(*pmd)):若已是大页:检查
page_size_mask。如果允许 2MB 大页,则直接复用该映射,统计页面数量并继续。若不是大页:意味着这是更精细的 4KB 页表。此时会调用
phys_pte_init递归处理下一级 PTE,以处理已有映射。
建立新映射:对于空的 PMD 条目,根据条件选择映射方式:
使用 2MB 大页映射:如果地址按 2MB 对齐且
page_size_mask允许,则设置 PMD 条目为 2MB 大页映射。注:该条目不经过 PTE,直接指向物理内存。降级为 4KB 小页映射:如果条件不满足(例如地址不对齐,或调试配置禁用大页),则调用
alloc_low_page分配一个 PTE 页表,然后调用phys_pte_init填充该表,最后将 PMD 条目指向这个新分配的 PTE 页表。
关键特性与演进
init参数:init参数用于区分启动早期和内存热插拔等运行时场景,允许在不同场景下采取不同的同步或策略行为。例如,在热插拔场景下,可能不需要像启动时那样强制清除越界的 PMD 条目。TLB 一致性保证:当需要将一个已有的 2MB 大页拆分为 4KB 小页时,为了避免违反 Intel 的 TLB 应用规范(即改变页面大小时,新旧转换在页框和属性上不能有差异),代码会使用新页面的保护属性,并继承原大页的保护位。
phys_pmd_init通过这套决策机制,将上层(PUD)传递下来的、粒度更粗的映射请求(1GB 或 2MB),在 PMD 层级进一步细化为更精确的 2MB 大页或 4KB 小页映射,是构建高效、精确的内核直接映射的关键一环。
phys_pte_init
phys_pte_init是 x86 内核启动过程中,负责在PTE(页表项)层级建立物理内存直接映射的最终执行者。它处理的是最细粒度的4KB 标准页的映射,是整个页表填充流水线的最后一环。
实现原理
phys_pte_init的实现原理是一个简单的遍历和填充过程。它接收一个 PTE 页表(pte_page),将其覆盖的物理地址区间[paddr, paddr_end)内的每一个页表项逐个映射到对应的物理页。
关键设计细节
返回值的一致性:
phys_pte_init返回的paddr_last表示已映射的最后一个物理地址。这个返回值被上层函数用于追踪映射进度。一个重要的补丁修正了该函数返回值的一致性:在遇到已存在映射时,它也会更新paddr_last,使其行为与上层phys_*_init函数保持一致。这确保了在混合使用新映射和复用已有映射时,kernel_physical_mapping_init能够获得正确的进度信息。对已有映射的处理:当函数发现某个 PTE 条目已经被映射时,它会选择复用该条目,而不是覆盖或重建。代码注释提到,Xen 虚拟化环境对此有特殊要求,例如可能需要特殊的映射属性。
页表计数:函数在映射完成后会调用
update_page_count(PG_LEVEL_4K, pages)更新内核维护的 4KB 页表项计数,便于后续统计和调试。
phys_pte_init作为页面映射流水线的最后一级,通过逐个填充 PTE 条目,完成了从物理地址到虚拟地址的 4KB 粒度映射,是内核直接映射区的基石。