2. 页表机制:四级页表结构(PGD/PUD/PMD/PTE)、TLB缓存与刷新策略
页表是操作系统实现虚拟地址到物理地址转换的核心数据结构。在64位Linux内核中,为了覆盖巨大的虚拟地址空间(通常为48位或57位),采用了四级页表(甚至五级页表)的分级映射机制。理解PGD、PUD、PMD、PTE这四级页表的职责,以及TLB的缓存与刷新策略,是进行内存管理裁剪和性能调优的基础。
2.1 四级页表结构
Linux内核将虚拟地址划分为多个位段,每个位段作为索引,依次查找各级页表,最终找到物理页框。这种分级设计有效减少了单级页表所需的内存占用,并支持按需分配页表项。
2.1.1 四级页表层级与索引
以典型的 48 位虚拟地址空间为例,虚拟地址被划分为 5 个部分:PGD索引、PUD索引、PMD索引、PTE索引 和 页内偏移。各级页表项指向下一级页表的基地址,PTE 则直接指向物理页框。
2.1.2 各级页表职责与数据结构
在 Linux 内核源码中,页表项通过 pgd_t、pud_t、pmd_t、pte_t 等类型表示。内核提供了一系列宏和函数来操作这些表项,例如 pgd_offset()、pud_offset()、pmd_offset()、pte_offset_map()。
| 层级 | 类型 | 职责 | 典型大小(x86_64) |
|---|---|---|---|
| PGD | pgd_t |
顶级页目录,每个进程一个,指向 PUD 表 | 512 项(4KB) |
| PUD | pud_t |
上级页目录,指向 PMD 表 | 512 项(4KB) |
| PMD | pmd_t |
中间页目录,指向 PTE 表 | 512 项(4KB) |
| PTE | pte_t |
页表项,直接映射到物理页框 | 512 项(4KB) |
CONFIG_PGTABLE_LEVELS 将页表层级减少为 3 级或 2 级,从而节省页表本身占用的内存并加速地址转换。
2.2 TLB 缓存与刷新策略
TLB(Translation Lookaside Buffer)是 MMU 内部的一个硬件缓存,用于缓存最近使用的虚拟地址到物理地址的转换结果。由于 TLB 的访问速度远快于内存中的页表遍历,其命中率直接影响程序性能。
2.2.1 TLB 缓存结构
TLB 通常分为指令 TLB(ITLB)和数据 TLB(DTLB),并可能支持多级 TLB 缓存。在上下文切换或修改页表后,必须刷新 TLB 中的无效条目,否则会导致地址转换错误。
2.2.2 刷新策略对比
Linux 内核根据场景选择不同的 TLB 刷新策略,以平衡性能与正确性。在嵌入式裁剪中,减少不必要的全局刷新是优化关键。
| 策略 | 触发场景 | 性能影响 | 适用场景 |
|---|---|---|---|
| 全局刷新 | 进程切换、修改页全局目录 | 高(所有 TLB 条目失效) | 通用操作系统 |
| 选择性刷新 | 修改单个页表项、释放页面 | 低(仅失效特定条目) | 实时系统、微内核 |
| 惰性刷新 | 使用 ASID(Address Space ID) | 极低(避免切换时刷新) | 多进程、虚拟化场景 |
2.2.3 内核中的 TLB 刷新接口
Linux 内核提供了一系列体系结构相关的 TLB 刷新函数,位于 <asm/tlbflush.h> 中。在裁剪时,可以针对特定硬件优化这些函数的实现。
/* 全局刷新所有 TLB */
void flush_tlb_all(void);
/* 刷新指定进程的所有 TLB 条目 */
void flush_tlb_mm(struct mm_struct *mm);
/* 刷新指定进程的指定虚拟地址范围 */
void flush_tlb_range(struct vm_area_struct *vma,
unsigned long start, unsigned long end);
/* 刷新单个页面 */
void flush_tlb_page(struct vm_area_struct *vma,
unsigned long addr);
2.3 页表与 TLB 的协同优化
在内存管理裁剪中,页表层级和 TLB 策略需要协同考虑。例如,使用大页(Huge Pages)可以减少页表层级,并提高 TLB 的覆盖范围。
- 大页支持: 通过
CONFIG_HUGETLB_PAGE启用,使用 2MB 或 1GB 页,减少 PTE 数量,降低 TLB 压力。 - 页表预取: 在缺页异常处理时,可以预取相邻页的 PTE,提高 TLB 命中率。
- ASID 优化: 在支持 ASID 的硬件上,为每个进程分配唯一 ID,避免上下文切换时刷新 TLB。
flush_tlb_mm 的实现,以最小化 TLB 刷新开销。