从虚拟地址到物理页框:Linux 页表与内存管理全解析

从虚拟地址到物理页框:Linux 页表与内存管理全解析
(以 x86_64 架构为主,2025–2026 年主流 Linux 内核视角)

Linux 内存管理的核心目标:隔离进程 + 高效利用物理内存 + 按需分配 + 保护。实现这一切的关键机制就是分页(Paging) + 多级页表 + MMU(Memory Management Unit)硬件

1. 为什么需要虚拟地址?

  • 物理内存地址(PA)是全局的,所有进程共享。
  • 虚拟地址(VA)让每个进程认为自己独占整个地址空间(通常 0 ~ 2⁶⁴-1)。
  • 好处:
  • 进程隔离(一个进程无法直接访问另一个进程的内存)
  • 地址空间布局随机化(ASLR)
  • 写时拷贝(COW)、按需分页、内存映射文件、huge page 等高级特性

x86_64 常用虚拟地址位宽:48 位(有效范围 0x0000_0000_0000_0000 ~ 0x0000_ffff_ffff_ffff 和 0xffff_0000_0000_0000 ~ 0xffff_ffff_ffff_ffff)

2. 经典 4KB 页的多级页表结构(x86_64 四级页表)

Linux 在 x86_64 上默认使用 4 级页表(CONFIG_PGTABLE_LEVELS=4 或 5):

级别英文全称中文常用称呼每项大小每表条目数覆盖虚拟地址范围位段(48位 VA)
第1级Page Global Directory全局页目录 PGD4KB512256TB47–39 位(9 bit)
第2级Page Upper Directory上层目录 PUD4KB512512GB38–30 位(9 bit)
第3级Page Middle Directory中间目录 PMD4KB5121GB29–21 位(9 bit)
第4级Page Table页表 PT / PTE4KB5122MB → 4KB20–12 位(9 bit)
页内偏移4KB11–0 位(12 bit)
  • 每级页表都是 4KB 大小(一页),正好能被上一级指向。
  • 每个页表项(Entry)占 8 字节(64 位),所以 4KB / 8 = 512 项
  • 总虚拟地址拆分:9+9+9+9+12 = 48 位(高 16 位用于 canonical 形式)。

3. 虚拟地址 → 物理地址 的完整转换过程(Page Table Walk)

以虚拟地址 0x00007f1234567890 为例(简化后):

  1. CPU 拿到虚拟地址 VA。
  2. CR3 寄存器 读取当前进程的 PGD 基地址(物理地址)。
  3. 用 VA 的 47–39 位 作为索引,在 PGD 中找到对应条目 → 得到 PUD 的物理基地址
  4. 用 VA 的 38–30 位 在 PUD 中索引 → 得到 PMD 基地址
  5. 用 VA 的 29–21 位 在 PMD 中索引 → 得到 PTE 基地址(或直接是大页)。
  6. 用 VA 的 20–12 位 在 PTE 中索引 → 得到 物理页框号(PFN) + 页属性。
  7. 物理地址 PA = PFN << 12 | (VA 的 11–0 位偏移)。

整个过程由 MMU 硬件 自动完成,软件(内核)只负责填充页表。

4. 页表项(PTE / PMD / PUD / PGD)常见位含义(64 位)

含义说明
0Present (P)1 = 页存在,0 = 缺页(触发 page fault)
1Read/Write (R/W)1 = 可写,0 = 只读(写时拷贝、写保护关键位)
2User/Supervisor (U/S)1 = 用户态可访问,0 = 仅内核态
7Page Size (PS)在 PMD 级为 1 表示 2MB 大页,在 PUD 级为 1 表示 1GB 大页
63NX / XDNo-eXecute,防止代码执行(安全防护)
51–12Page Frame Number (PFN)物理页框号(高位通常为 0 或用于扩展)
其他Dirty、Accessed、PAT 等脏页标记、访问位、页面属性表等

5. 大页(Huge Page / THP)如何改变页表结构?

Linux 支持两种大页:

  • 传统 Huge Pages(hugetlbfs):2MB / 1GB,手动预分配。
  • 透明大页(THP):内核自动尝试合并 4KB 页为 2MB(甚至 1GB),对应用透明。
大页大小生效级别页表减少到几级优势缺点
2MBPMD3 级(PGD → PUD → PMD)TLB 命中率大幅提升,页表内存少内存碎片化更严重
1GBPUD2 级(PGD → PUD)极致性能(数据库、大数据、虚拟化)分配极难,碎片化严重

THP 默认开启(/sys/kernel/mm/transparent_hugepage/enabled),在匿名内存、page cache 中自动尝试。

6. 关键加速机制:TLB(Translation Lookaside Buffer)

纯页表遍历需要 4 次内存访问(PGD → PUD → PMD → PTE),太慢!

  • TLB:CPU 内部高速缓存,保存最近的 VA → PA 映射。
  • 命中:1 个周期。
  • 未命中:走完整 page walk(几十~上百周期)。
  • TLB 大小:几十到几千条(L1/L2 TLB)。
  • 大页(2MB/1GB)大幅减少 TLB miss,因为一条 TLB 条目覆盖更大范围。

7. 进程切换时页表怎么变?

  • 每个进程有独立的 mm_struct → pgd(CR3 指向的基地址)。
  • 切换进程时,内核修改 CR3 寄存器 → 加载新进程的 PGD 物理地址。
  • 现代 CPU 支持 PCID(Process Context Identifiers)(ASID),可以避免每次切换都 flush TLB。

8. 常见内核函数与工具(调试 / 理解用)

功能内核函数 / 宏 / 命令说明
虚拟地址 → 物理地址__virt_to_phys() / virt_to_phys()内核地址转换
物理地址 → 页结构pfn_to_page()PFN → struct page *
打印进程页表/proc//pagemap / smaps用户态查看
内核调试crash / drgn / gdb + vmlinux查看 CR3、页表项
强制大页echo always > /sys/kernel/mm/transparent_hugepage/enabled开启 THP 激进模式

总结:一句话记忆流程

虚拟地址(48位) → 分段索引(9+9+9+9) → 4 次查表(PGD → PUD → PMD → PTE) → 取出 PFN + 偏移 → 物理地址
硬件(MMU + TLB) + 内核(页表填充 + 缺页中断处理)共同完成。

想看哪一部分更详细?

  • 5 级页表(5-level paging)区别
  • 内核地址空间布局(直接映射区、高端内存、vmalloc 等)
  • 缺页异常(page fault)处理流程
  • ARM64 页表与 x86_64 的主要不同
  • 实际用 gdb / crash 看页表的例子

随时告诉我,继续深入~

文章已创建 4695

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部