B05 —— MMU 是怎么把虚拟地址翻译成物理地址的
金句:每个虚拟地址,背后都有一段复杂的翻译过程——而你从未感知到它。
MMU(Memory Management Unit)是什么
MMU 是 CPU 芯片上的一个硬件单元,负责把 虚拟地址(Virtual Address, VA) 翻译成 物理地址(Physical Address, PA)。
MMU 在系统中的位置:
CPU Core Memory Controller
│ │
执行单元 ──→ VA ──→ MMU ──→ PA ──→ Cache/DRAM
│
Translation:
VA → PA
MMU 的职责:
1. 虚拟地址到物理地址的翻译(Address Translation)
2. 访问权限检查(读/写/执行权限)
3. 内存保护(防止用户进程访问内核空间)
4. TLB 缓存(加速翻译)
为什么需要 MMU:
- 让每个进程有独立的地址空间(隔离)
- 支持虚拟内存(内存可以比实际物理内存大)
- 防止恶意程序访问其他进程或内核的内存虚拟地址到物理地址的翻译
x86-64 架构的虚拟地址翻译:
x86-64 虚拟地址(48-bit)的结构:
63~48(符号扩展)| 47~39 | 38~30 | 29~21 | 20~12 | 11~0
[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)翻译过程(四级页表):
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)的结构
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 内置的高速缓存,缓存最近用过的地址翻译。
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 用四级页表而不是一级大页表?
一级页表的问题(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 支持大页:
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不着急」,回复”资料”获取内存学习路线图。
评论