内存基础:物理内存与虚拟内存、MMU与页表、内存寻址原理
各位好,我是你们这门课的老朋友。今天咱们正式开篇,聊聊内存最底层的那些事。
很多人觉得内存调优就是调几个内核参数,改改 vm.swappiness 完事。说实话,我早年也这么干过。直到有一次线上服务莫名其妙 OOM,查了三天才发现是页表碎片把 TLB 刷爆了。从那以后我明白一个道理:不懂内存寻址的底层逻辑,调优就是瞎蒙。
这一章,咱们把地基打牢。后面二十多章的实战技巧,全都建立在这些概念之上。
1.1 物理内存 vs 虚拟内存:一个经典的障眼法
先问个问题:你的程序能直接访问物理内存吗?
答案是——不能。或者说,现代操作系统不允许你这么做。
每个进程看到的,都是一个从 0 到 4GB(32位)或更大(64位)的连续地址空间。这叫虚拟地址空间。而真正的物理内存,可能只有 8GB、16GB,而且碎片化严重。
我习惯把虚拟内存比作「酒店房卡」。你拿着房卡,以为整栋楼都是你的。实际上你只能进自己的房间。操作系统就是那个前台,帮你把房卡上的房间号,映射到真正的物理房间。
核心区别一句话:虚拟内存是给进程看的「假象」,物理内存是硬件真正在用的「真相」。中间靠 MMU 做翻译。
为什么要搞这么复杂?三个原因:
- 隔离:进程 A 不能乱写进程 B 的内存,崩溃也只崩自己。
- 简化编程:程序员不用管物理内存碎片,只管用连续的虚拟地址。
- 按需分配:你申请 1GB 虚拟内存,物理内存可能只给了 4KB。用到了才真正分配。
嗯,这里要注意:虚拟内存 ≠ 交换分区(swap)。虚拟内存是地址空间的概念,swap 只是它的一种物理后援。很多人搞混,我当年也踩过这个坑。
1.2 MMU 与页表:地址翻译的「翻译官」
虚拟地址怎么变成物理地址?靠的是 MMU(内存管理单元),一个集成在 CPU 里的硬件模块。
MMU 手里拿着一张「翻译对照表」,叫页表。每次 CPU 发出一个虚拟地址,MMU 就查页表,找到对应的物理地址,然后去拿数据。
这个过程叫地址翻译。听起来简单,但细节里全是魔鬼。
页表的结构
页表不是一张大平表。现代操作系统用多级页表。以 x86_64 为例,是 4 级页表:
| 级别 | 名称 | 作用 |
|---|---|---|
| L1 | PML4(页映射表) | 指向下一级页表目录 |
| L2 | PDPT(页目录指针表) | 指向页目录 |
| L3 | PD(页目录) | 指向页表 |
| L4 | PT(页表) | 指向物理页帧 |
为什么要搞这么多级?省内存。如果只用一级页表,4GB 虚拟空间需要 100 多万个条目,每个进程都得来一份,内存根本扛不住。多级页表可以「按需创建」——很多中间目录是空的,不占内存。
我的经验:调优时多留意 /proc/meminfo 里的 PageTables 字段。如果这个值异常大(比如超过几百 MB),说明进程太多或虚拟地址空间碎片化严重。我曾经在一个容器密集部署的环境里,光页表就吃了 2GB 内存,后来靠调整 mmap 的映射策略才降下来。
TLB:加速翻译的「快照」
每次地址翻译都查 4 级页表,太慢了。CPU 搞了个缓存叫 TLB(转译后备缓冲器),专门存最近用过的虚拟地址到物理地址的映射。
TLB 命中,一次翻译只要几个 CPU 周期。TLB 未命中,就得走一遍完整的页表遍历,几十甚至上百个周期。
所以,TLB 命中率是内存性能的关键指标之一。
我曾经踩过的坑:一个数据库服务,内存访问模式极度随机,导致 TLB 频繁未命中。性能分析发现 perf stat 里 dTLB-load-misses 高达 30%。后来改用大页(HugePages),TLB 条目覆盖的内存范围从 4KB 变成 2MB,命中率直接飙到 99% 以上。这个后面章节会细讲。
1.3 内存寻址原理:从虚拟地址到物理地址的完整路径
咱们走一遍完整的寻址流程。假设 CPU 要访问虚拟地址 0x7f3a5b6c8d00:
- CPU 发出虚拟地址,送到 MMU。
- MMU 拆分地址:x86_64 下,64 位虚拟地址实际只用低 48 位。这 48 位被拆成 5 段:
- 前 9 位:PML4 索引
- 接下来 9 位:PDPT 索引
- 接下来 9 位:PD 索引
- 接下来 9 位:PT 索引
- 最后 12 位:页内偏移
- 查 TLB:如果 TLB 里有这个虚拟地址的映射,直接拿到物理页帧号,加上偏移,完事。
- TLB 未命中:MMU 从进程的页表基址寄存器(CR3)开始,逐级查表。每级查到一个物理地址,再去下一级。
- 拿到物理页帧号,加上页内偏移,得到最终的物理地址。
- 访问物理内存:通过内存总线去拿数据。
整个过程,硬件自动完成。但理解它,对性能调优至关重要。
一句话总结寻址路径:虚拟地址 → TLB(快)→ 多级页表(慢)→ 物理页帧 → 物理地址 → 内存数据。
1.4 知识体系总览
下面这张图,把本章的核心概念串起来。我建议你多看几遍,后面每章都会用到这些概念。
1.5 本章小结
这一章我们干了三件事:
- 搞清了物理内存和虚拟内存的区别——一个是真金白银,一个是画饼充饥。
- 理解了MMU 和页表怎么合作做地址翻译——多级页表省内存,TLB 加速翻译。
- 走了一遍完整的寻址路径——从虚拟地址到物理地址,每一步都有性能代价。
这些概念,是后面所有调优动作的「说明书」。不懂它们,你调 vm.dirty_ratio 也好,配 HugePages 也好,都是在碰运气。