内存管理全景
内存管理要解决什么问题
操作系统的内存管理需要解决四个核心问题:
- 地址空间隔离:每个进程以为自己独占整个内存,不能访问其他进程的数据
- 内存分配与回收:高效地为进程分配和回收内存
- 内存扩展:物理内存不够时,利用磁盘扩展可用内存(虚拟内存)
- 地址转换:将程序使用的虚拟地址转换为实际的物理地址
虚拟地址与物理地址
程序中使用的地址都是虚拟地址(Virtual Address),需要通过 MMU(内存管理单元) 转换为物理地址(Physical Address):
CPU MMU 物理内存
┌──────┐ ┌──────────┐ ┌──────────┐
│ 虚拟 │──────▶ │ 地址转换 │──────▶ │ 物理地址 │
│ 地址 │ │ (查页表) │ │ 的数据 │
└──────┘ └──────────┘ └──────────┘
0x00400000 页表翻译 0x7F200000
为什么不直接用物理地址?因为:
- 程序编译时无法知道运行时会被加载到哪个物理地址
- 多个进程可能冲突(都想用同一个物理地址)
- 没有隔离保护
内存管理的三种方式
1. 分段(Segmentation)
将进程的地址空间按逻辑功能分为若干段——代码段、数据段、栈段、堆段,每段有独立的基地址和长度。
逻辑地址 = 段号 + 段内偏移
段表:
段号 基地址 长度
0 0x1000 0x400 (代码段)
1 0x2000 0x300 (数据段)
2 0x3000 0x200 (栈段)
地址转换: 段号=1, 偏移=0x50
→ 物理地址 = 0x2000 + 0x50 = 0x2050
优点:符合程序的逻辑结构,便于共享和保护(如代码段只读) 缺点:段的大小不等,容易产生外部碎片
2. 分页(Paging)
将物理内存和虚拟地址空间都划分为固定大小的块。物理内存的块叫页帧(Frame),虚拟地址的块叫页(Page),通常大小为 4KB。
虚拟地址 = 页号 + 页内偏移
页表 (Page Table):
虚拟页号 物理帧号 有效位
0 3 1
1 — 0 ← 未映射(访问会触发缺页异常)
2 7 1
3 1 1
虚拟地址 0x00002050 (页号=2, 偏移=0x050)
→ 物理帧号 = 7
→ 物理地址 = 7 × 4096 + 0x050 = 0x7050
优点:固定大小,不产生外部碎片 缺点:可能产生内部碎片(最后一页可能没用满);页表本身占用大量内存
多级页表
一个 64 位地址空间如果用一级页表,需要 2^52 个页表项(假设 4KB 页),完全不可行。解决方案是使用多级页表:
x86-64 四级页表:
虚拟地址 (48位有效):
┌────────┬────────┬────────┬────────┬────────────┐
│ PML4 │ PDP │ PD │ PT │ Offset │
│ 9 bits │ 9 bits │ 9 bits │ 9 bits │ 12 bits │
└───┬────┴───┬────┴───┬────┴───┬────┴────────────┘
│ │ │ │
▼ ▼ ▼ ▼
PML4表 → PDP表 → PD表 → PT表 → 物理帧号 + Offset
多级页表的优势:按需分配。如果某一大块虚拟地址从未使用,对应的下级页表根本不需要创建,节省了大量内存。
3. 段页式(Segmentation with Paging)
先分段,再对每一段分页。Linux 在形式上保留了分段机制,但实际上将所有段的基地址设为 0、长度设为最大值,等于只使用了分页。
TLB:页表的缓存
每次地址转换都要查多级页表(可能 4 次内存访问),太慢了。TLB(Translation Lookaside Buffer,快表) 是 MMU 中的一个高速缓存,存储最近使用的页表项:
CPU 发出虚拟地址
│
▼
查 TLB ─── 命中(~1 cycle)───▶ 直接得到物理地址
│
未命中
│
▼
查多级页表(~100 cycles)
│
▼
更新 TLB
│
▼
得到物理地址
TLB 命中率通常 > 99%,因为程序访问内存具有局部性(Locality)——刚访问过的页面很可能马上再次访问。
生产高频题
分页和分段的区别?
分页将地址空间划分为固定大小的页(4KB),分段按逻辑功能划分为大小不等的段。分页解决外部碎片问题但可能有内部碎片,分段便于共享保护但容易产生外部碎片。现代操作系统主要使用分页。
为什么需要多级页表?
一级页表在 64 位地址空间下需要海量内存来存储页表项。多级页表通过层层索引,只为实际使用的虚拟地址范围分配页表,极大减少了页表占用的内存。
TLB 是什么?为什么进程切换后需要刷新 TLB?
TLB 是 MMU 中缓存页表项的高速缓存。不同进程的虚拟地址映射到不同的物理页,进程切换时如果不刷新 TLB,新进程可能使用旧进程的映射,访问到错误的物理内存。