B05 —— MMU 是怎么把虚拟地址翻译成物理地址的

金句:每个虚拟地址,背后都有一段复杂的翻译过程——而你从未感知到它。


MMU(Memory Management Unit)是什么

MMU 是 CPU 芯片上的一个硬件单元,负责把 虚拟地址(Virtual Address, VA) 翻译成 物理地址(Physical Address, PA)

Bash
MMU 在系统中的位置:

  CPU Core                     Memory Controller
       │                             │
  执行单元 ──→ VA ──→ MMU ──→ PA ──→ Cache/DRAM
                    │
              Translation:
              VA → PA

MMU 的职责:
  1. 虚拟地址到物理地址的翻译(Address Translation)
  2. 访问权限检查(读/写/执行权限)
  3. 内存保护(防止用户进程访问内核空间)
  4. TLB 缓存(加速翻译)

为什么需要 MMU:
  - 让每个进程有独立的地址空间(隔离)
  - 支持虚拟内存(内存可以比实际物理内存大)
  - 防止恶意程序访问其他进程或内核的内存

虚拟地址到物理地址的翻译

x86-64 架构的虚拟地址翻译:

Bash
x86-64 虚拟地址(48-bit)的结构:

  6348(符号扩展)| 4739 | 3830 | 2921 | 2012 | 110
  [31 bits]        | PML4   | PDPT   | PD     | PT     | Offset

  每个索引 = 9 bits(512 个条目)
  页大小 = 4KB(Offset = 12 bits)
  总虚拟地址空间 = 2^48 = 256TB

  线性地址解析:
  VA[47:39] → PML4 索引(512 条目)
  VA[38:30] → PDPT  索引(512 条目)
  VA[29:21] → PD    索引(512 条目)
  VA[20:12] → PT    索引(512 条目)
  VA[11:00] → 页内偏移(4KB)

翻译过程(四级页表)

Bash
VA = 0xFFFF880000001000(48-bit)的翻译过程:

Step 1:读取 CR3(页目录基址寄存器)
  CR3 = 0x100000(假设)

Step 2:PML4[VA[47:39]]
  Index = VA[47:39] = 0x1FF(511)
  PML4[511] = 0x1010000000000863
  → 这是 PDPT 的物理地址(高 52 bits 有效)
  → 权限:User + RW + Present

Step 3:PDPT[VA[38:30]]
  PDPT 物理地址 = 0x1010000000000863 & 页对齐 = 0x1010000000000000
  Index = VA[38:30] = 0x000
  PDPT[0] = 0x1020000000000867
  → 这是 PD 的物理地址

Step 4:PD[VA[29:21]]
  PD 物理地址 = 0x1020000000000000
  Index = VA[29:21] = 0x000
  PD[0] = 0x1030000000000867
  → 这是 PT 的物理地址

Step 5:PT[VA[20:12]]
  PT 物理地址 = 0x1030000000000000
  Index = VA[20:12] = 0x001
  PT[1] = 0x0000000000000863
  → 这是 4KB 页的物理基址

Step 6:生成物理地址
  PA = 0x0000000000000863 + Offset(VA[11:0] = 0x000)
  PA = 0x0000000000000863

最终:VA 0xFFFF880000001000 → PA 0x0000000000000863

页表条目(PTE)的结构

Bash
x86-64 PTE(Page Table Entry,8 bytes):

  63  62  60  59  58  57  56  55  52  51  50  49  48  47  40  39  32
  P   RSVD   XD   RSVD   U  RW PS   G   PAT  AVL  [  Physical Address 52:48 ]

  31                                            12   11  9    8  7  6  5  4  3  2  1  0
  [      Physical Address 47:12              ] | PAT | G |D| A| PCD| PWT| U| W| P|

字段解释:
  P(Present):页是否在内存中(0=不存在,换出到磁盘)
  RW(Read/Write):读写权限
  U(User/Supervisor):用户/内核访问权限
  PS(Page Size):0=4KB, 1=2MB/1GB(大页)
  G(Global):全局页(TLB 不刷新)
  A(Accessed):访问过(用于页面换出判断)
  D(Dirty):脏页(被写过,用于写回)
  XD(Execute Disable):禁止执行(安全)
  PCD/PWT:缓存控制

TLB(Translation Lookaside Buffer)

MMU 每次地址翻译都需要访问页表,但页表在 DRAM 里,访问延迟很高(~60ns)。TLB 是 MMU 内置的高速缓存,缓存最近用过的地址翻译。

Bash
TLB 的工作原理:

CPU 请求 VA = 0xFFFF880000001000
  → TLB 查询(并行检查所有 TLB 条目)
    → TLB Hit!(PTE 已在 TLB 中)
      → 直接使用 TLB 中的 PA
      → 延迟 ≈ 1 cycle(~0.3ns)

如果 TLB Miss:
  → MMU 查四级页表(60ns 延迟)
  → 发现 PA,构建 PTE
  → 把 PTE 存入 TLB(缓存)
  → 返回 PA
  → 后续访问同一页,TLB Hit

TLB 的命中率(典型):
  - L1 TLB:~90%+ 命中率(128 条目)
  - L2 TLB:~95%+ 命中率(1K~2K 条目)
  - 每次 TLB Miss 约 60ns 额外延迟

Linux TLB 刷新:
  - 当页表改变时,内核需要刷新 TLB(INVLPG/INVPCID)
  - 进程切换时(CR3 改变),自动 flush TLB

多级页表的内存开销

为什么 x86-64 用四级页表而不是一级大页表?

Bash
一级页表的问题(2^48 个条目):

假设用一级页表(48-bit 索引):
  - 需要 2^48 个条目 × 8 bytes = 2^48 × 8 = 256 TB 的页表!
  - 完全不可行

多级页表的优势:

  假设每个进程只用 128TB 虚拟地址空间中的 128MB:

  PML4:1 个条目(1 页)
  PDPT:1 个条目(1 页)
  PD:32 个条目(32 页)
  PT:32 × 128 个条目(32 × 128 × 8 = 32KB)

  总计:4 页 = 16KB(而不是 256TB!)

  稀疏虚拟地址只需要稀疏的页表项。

多级页表的内存节省:
  - 进程只需要实际使用的虚拟地址区域对应的页表
  - 不使用的 VMA 不需要页表
  - 节省了大量内存

大页(Huge Pages / Large Pages)

为了减少 TLB Miss,现代 CPU 支持大页:

Bash
x86-64 支持的大页:

  2MB 大页:PDE.PS=1,页内偏移 21 bits
  1GB 大页:PDPTE.PS=1,页内偏移 30 bits

2MB 大页的 TLB 优势:
  - 一个 TLB 条目覆盖 2MB(而不是 4KB)
  - 大型数据(数据库、机器学习)用大页可以显著减少 TLB Miss

Linux 配置大页:

  # 预留 16 个 2MB 大页(总共 32MB)
  $ echo 16 > /proc/sys/vm/nr_hugepages

  # 挂载 hugepage filesystem
  $ mount -t hugetlbfs nodev /mnt/hugepages

  程序使用:
  mmap(NULL, 32MB, PROT_READ|PROT_WRITE,
       MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB, -1, 0);

  或者使用 SHM:
  shmget(IPC_PRIVATE, 32MB, SHM_HUGETLB|IPC_CREAT|0600);

总结

  • MMU:内存管理单元,CPU 芯片上负责 VA → PA 翻译的硬件
  • 四级页表:PML4 → PDPT → PD → PT,每级 9-bit 索引,512 条目
  • 虚拟地址结构:x86-64 用 48-bit VA,分成 5 个 9-bit 索引 + 12-bit Offset
  • PTE 结构:8 bytes,包含物理基址 + 权限位(RW/U/P/XD/G/A/D)
  • TLB:MMU 内置高速缓存,~60ns 延迟 → 缓存命中 ~0.3ns,命中率 90%+
  • 多级页表:稀疏 VMA 只需要稀疏页表,节省大量内存(一级 256TB vs 四级 16KB)
  • 大页:2MB/1GB 大页减少 TLB Miss,适合大型数据应用

下篇预告(B06):Linux 的 buddy system 是怎么管理物理内存的?物理页的分配和释放,以及为什么要用伙伴系统而不是简单的位图管理。


关注公众号「AI不着急」,回复”资料”获取内存学习路线图。

最后修改: 2024年3月1日

作者

评论

发表评论

您的邮箱地址不会被公开。