Linux内核MMU TLB缓存刷新与TLB缺失优化 - 第1节
1.1 虚拟地址与物理地址:内存的两层抽象
现代操作系统(如 Linux)运行在 MMU(Memory Management Unit,内存管理单元) 之上。MMU 的核心职责是将程序看到的 虚拟地址(Virtual Address, VA) 转换为硬件实际访问的 物理地址(Physical Address, PA)。这种两层抽象是内存隔离、进程独立和高效利用物理内存的基石。
关键概念: 虚拟地址是 CPU 执行指令时发出的地址(如 0x7ffe1234),物理地址是内存芯片上真实的存储单元编号(如 0x0000abcd)。MMU 负责在两者之间建立映射关系。
1.1.1 为什么需要虚拟地址?
- 进程隔离: 每个进程拥有独立的虚拟地址空间,不会误写其他进程的内存。
- 简化编程: 程序员无需关心物理内存的碎片化或容量限制,可以连续使用大块地址。
- 内存共享与保护: 通过页表权限位(读/写/执行)控制访问,内核可安全地共享代码段或内存映射。
- 按需分配: 物理内存仅在真正访问时才分配(缺页异常),提高内存利用率。
1.1.2 地址转换概览
从 CPU 发出虚拟地址到最终访问物理内存,经过以下关键步骤:
- CPU 取指或访存,发出虚拟地址 VA。
- MMU 根据 VA 的高位索引页表,查找对应的页表项(PTE)。
- 如果 PTE 有效且权限允许,MMU 提取物理页帧号(PFN),拼接页内偏移得到物理地址 PA。
- 如果 PTE 无效或权限不足,触发缺页异常(Page Fault),由内核处理。
性能提示: 每次地址转换都查内存中的页表会非常慢(多次访存)。因此 MMU 内部集成了 TLB(Translation Lookaside Buffer)缓存最近使用的页表项,这是后续章节优化的核心。
1.2 页表:地址转换的映射表
页表是存储在内存中的一种数据结构,用于将虚拟页号(VPN)映射到物理页帧号(PFN)。Linux 内核使用 多级页表(通常为 4 级或 5 级,取决于架构)来节省内存,避免为每个虚拟地址都建立一级映射。
1.2.1 多级页表结构(以 x86_64 4 级页表为例)
虚拟地址被划分为多个位段,每段索引一级页表。典型的 4 级页表包括:
- PGD(Page Global Directory): 顶级目录,由进程的 mm_struct 指向。
- P4D(Page 4th Directory): 第 4 级目录(某些架构或配置下存在)。
- PUD(Page Upper Directory): 第 3 级目录。
- PMD(Page Middle Directory): 第 2 级目录。
- PTE(Page Table Entry): 最后一级页表,直接指向物理页帧。
注意: 不同架构(ARM64、RISC-V)的页表级数和位段划分可能不同,但核心思想一致:通过多级索引缩小页表占用的物理内存。
1.3 页表项(PTE):映射的核心单元
页表项(PTE)是页表中的最小数据单元,通常为 8 字节(64 位架构)。它包含了物理页帧号(PFN)以及一系列权限和状态标志位。
1.3.1 PTE 的典型字段
| 字段 | 位宽(示例) | 作用 |
|---|---|---|
| 物理页帧号(PFN) | 高 52 位(x86_64) | 指向物理内存中 4KB 页帧的基地址 |
| Present(有效位) | 1 位 | 该页是否在物理内存中(0 表示缺页或换出) |
| R/W(读写位) | 1 位 | 0 表示只读,1 表示可读写 |
| U/S(用户/超级用户位) | 1 位 | 0 表示仅内核可访问,1 表示用户态也可访问 |
| Dirty(脏位) | 1 位 | 该页是否被写入过(用于回写策略) |
| Accessed(访问位) | 1 位 | 该页是否被读取或写入过(用于页面置换算法) |
| NX(不可执行位) | 1 位 | 禁止在该页上执行代码(硬件安全特性) |
优化视角: PTE 的 Accessed 和 Dirty 位由硬件自动设置,但软件(内核)需要定期检查或清除。TLB 缺失时,MMU 必须读取完整的 PTE 才能完成地址转换,因此 PTE 的布局和缓存效率直接影响性能。
1.3.2 PTE 在地址转换中的作用
当 MMU 遍历多级页表找到最后一级 PTE 时,执行以下操作:
- 检查 Present 位:若为 0,触发缺页异常;若为 1,继续。
- 检查权限位(R/W, U/S, NX):若违反当前 CPU 模式(用户态/内核态)或操作类型(读/写/执行),触发保护错误。
- 提取 PFN,与虚拟地址的页内偏移拼接,形成完整的物理地址。
- 硬件自动设置 Accessed 位(若未设置),并根据写操作设置 Dirty 位。
1.4 页表与 PTE 在 Linux 内核中的体现
Linux 内核通过 struct page 和 pte_t 类型来管理物理页和页表项。关键数据结构如下:
// Linux 内核中页表项类型(x86_64)
typedef struct { unsigned long pte; } pte_t;
// 页全局目录项
typedef struct { unsigned long pgd; } pgd_t;
// 宏:读取 PTE 的物理页帧号
#define pte_pfn(x) ((unsigned long)(((x).pte >> PAGE_SHIFT) & ((1UL << (64 - PAGE_SHIFT)) - 1)))
// 宏:设置 PTE 的权限
#define pte_mkwrite(pte) __pte(pte_val(pte) | _PAGE_RW)
#define pte_mkdirty(pte) __pte(pte_val(pte) | _PAGE_DIRTY)
内核优化点: 在 TLB 缺失处理路径中,内核会频繁读取和修改 PTE。例如,当页面被换出时,内核将 PTE 的 Present 位清 0,并将换出位置编码到 PTE 的剩余位中(swap entry)。这种设计避免了额外的数据结构查找,但要求 TLB 刷新操作必须同步。
1.5 本章小结
| 概念 | 核心作用 | 与 TLB 优化的关系 |
|---|---|---|
| 虚拟地址 | 进程独立的内存视图 | TLB 缓存的是 VA→PA 的映射 |
| 物理地址 | 真实硬件访问地址 | TLB 命中后直接输出 PA |
| 多级页表 | 节省页表内存,支持稀疏地址空间 | TLB 缺失时需遍历多级页表,增加延迟 |
| 页表项(PTE) | 存储映射关系和权限/状态位 | PTE 的 Present/Dirty/Accessed 位影响 TLB 刷新策略 |