🧠 操作系统内存管理全景图

从虚拟地址到物理地址的完整旅程

🎯
什么是内存管理?
内存管理是操作系统最重要的核心功能之一。它负责:
• 让多个程序同时运行而不互相干扰
• 让程序可以使用比实际物理内存更大的地址空间
• 保护各个进程的内存不被非法访问
• 提高内存的使用效率
🏃 应用程序 (进程)
🔒 内核空间 (Kernel) 高地址 ←
↑ 向下增长
📚 栈 (Stack) - 函数调用、局部变量 ...
🏠 堆 (Heap) - 动态分配 ...
📦 BSS段 - 未初始化全局变量 0x...
📝 数据段 - 已初始化全局变量 0x...
💻 代码段 (Text) - 程序指令 0x00...
⬇️ MMU 虚拟地址 → 物理地址 ⬇️
🖥️ 物理硬件
📱 物理内存 (RAM) - 8GB
0x0000
0x1000
0x2000
0x3000
0x4000
0x5000
0x6000
0x7000
0x8000
0x9000
0xA000
0xB000
0xC000
0xD000
0xE000
0xF000
...
...
已分配
空闲
🗺️
虚拟内存 — 程序眼中的内存
虚拟内存为每个进程提供一个独立的、连续的地址空间,让程序"以为"自己拥有整台电脑的内存。实际物理内存可能只有 8GB,但每个进程都可以"看到" 2^64 字节(64位系统)的地址空间。

🔴 进程 A 的虚拟地址空间

0xFFFFFFFFFFFF
0x00007FFF0000
0x000055000000
0x000000000040

🟢 进程 B 的虚拟地址空间

0xFFFFFFFFFFFF
0x00007FFF0000
0x000055000000
0x000000000040
💡 关键点:两个进程的虚拟地址完全相同,但它们映射到物理内存的不同位置,这就是地址空间隔离
虚拟内存的好处
🏠 进程隔离
一个进程崩溃不会影响其他进程,每个进程有独立的地址空间
📏 简化编程
程序员不需要关心物理内存的具体位置,简化了开发
💾 内存扩展
程序可以使用比实际物理内存更大的地址空间
🔒 内存保护
可以设置内存区域的访问权限(只读、只执行等)
📄
分页机制 — 内存的"拼图游戏"
分页将虚拟内存和物理内存都划分为固定大小的"页框"(物理页)或"页"(虚拟页)。典型大小是 4KB。虚拟地址的转换通过页表完成。
虚拟地址结构 (64位系统):
页目录索引
页表索引
页内偏移

📄 虚拟页 (Virtual Pages)

VP0
VP1
VP2
VP3
VP4
VP5
VP6
VP7
VP8
VP9
VP10
VP11
VP12
VP13
VP14
VP15

📦 物理页框 (Physical Frames)

PF0
PF1
PF2
PF3
PF4
PF5
PF6
PF7
PF8
PF9
PF10
PF11
PF12
PF13
PF14
PF15
页表映射示例:
VP0 → PF3 | VP1 → PF0 | VP2 → PF9 | VP4 → PF6 | VP5 → PF12 ...
多级页表
1

CR3 寄存器

CPU 中的 CR3 寄存器保存着页目录表的物理地址

2

页目录表 (Page Directory)

根据虚拟地址的高位索引,找到对应的页表指针

3

页表 (Page Table)

根据虚拟地址的中间位索引,找到物理页框号

4

页内偏移

加上偏移量,得到最终的物理地址

⚙️
MMU — 内存管理单元
MMU (Memory Management Unit)是CPU中的一个硬件组件,负责将虚拟地址转换为物理地址。这个过程对程序员是透明的,由硬件自动完成。
虚拟地址: 0x2360
⬇️ MMU ⬇️
查找页表 → 找到物理页框 → 加上偏移
物理地址: 0x50260

🔍 地址转换详解

虚拟地址 0x2360 (十六进制)
= 0010 0011 0110 0000 (二进制)
高 10 位: 0010 0011 = 35 → 页目录索引
中 10 位: 01 0110 00 = 88 → 页表索引
低 12 位: 00 0000 0000 = 0 → 页内偏移
物理地址计算:
物理页框号 (如 0x50) + 偏移 (0x600) = 0x50260
🚨
TLB — 翻译后备缓冲器
每次地址转换都要查页表太慢了!TLB是MMU内部的一个高速缓存,存储最近使用的虚拟地址到物理地址的映射。
⚡ TLB 命中 (Hit)
TLB 中已有映射 → 直接转换,约 1 个时钟周期
🐢 TLB 未命中 (Miss)
需要查页表,可能触发多级查找,约 100+ 时钟周期
📦
内存分配算法
🏃 首次适配 (First Fit)
找到第一个足够大的空闲块。速度快,但可能产生很多小碎片。
遍历链表,找到第一个 ≥ 请求大小的块
🎯 最佳适配 (Best Fit)
找到最小的足够大的空闲块。减少碎片,但可能产生很多微小碎片。
遍历所有块,找最接近请求大小的
💥 最差适配 (Worst Fit)
分配到最大的空闲块。试图留下大块,但实际效果通常不好。
找到最大块,分割使用
🔄 下一适配 (Next Fit)
从上次分配的位置继续查找。性能均匀,但效果不如首次适配。
维护一个指针,从当前位置开始
🧩
伙伴系统 (Buddy System)
Linux 采用的内存分配算法。将内存划分为 2^k 大小的块,每次分配和释放都按伙伴(2的幂次方)进行。
512KB
256KB
256KB
128KB
128KB
256KB
分配 128KB:从 512KB 块不断二分,直到得到合适大小
🔄
交换空间 (Swap)
当物理内存不足时,操作系统将不活跃的内存页移动到磁盘的交换空间,腾出物理内存给需要的程序。
📱 物理内存 (RAM)
P1
P2
P3
FREE
需要加载 P4,但空间不足
🔄
💾 磁盘交换区 (Swap)
S1
S2
S3
将不活跃的页面换出
📊
页面置换算法
⏰ LRU (最近最少使用)
置换最长时间未使用的页面。近似实现,需要硬件支持。
🎰 FIFO (先进先出)
置换最早进入内存的页面。简单但可能置换常用页面。
🎯 OPT (最佳置换)
置换未来最长时间不会使用的页面。理论最优,无法实现。
🔄 Clock (时钟算法)
LRU 的近似实现,用环形链表和访问位。Linux 采用。
⚠️
内存管理异常
!

缺页中断 (Page Fault)

访问的页不在物理内存中,触发缺页异常,操作系统加载对应页

!

段错误 (Segmentation Fault)

访问了未映射的虚拟地址,通常是程序 bug(空指针、越界等)

!

OOM Killer

内存耗尽时,Linux 会选择并杀死最"坏"的进程释放内存