Linux虚拟内存系统详解
1. 引言:为什么需要虚拟内存?
从计算机体系结构的角度来看,虚拟内存(Virtual Memory)是现代操作系统最核心的抽象之一。它在物理内存(RAM)和应用程序之间建立了一个中间层,解决了以下核心问题:
- 内存隔离与保护:每个进程都有独立的虚拟地址空间,互不干扰。进程A无法直接读写进程B的内存,也无法破坏内核空间。
- 地址空间扩展:进程可以使用比物理内存更大的地址空间(例如在32位系统上使用4GB虚拟空间,即使物理内存只有512MB)。
- 内存共享 :多个进程可以共享只读代码段(如
glibc库),节省物理内存。 - 动态分配与延迟加载:通过缺页异常(Page Fault)机制,实现按需分配(Demand Paging)和写时复制(COW)。

2. Linux虚拟内存实现机制
2.1 地址空间布局
Linux 将虚拟地址空间划分为两部分:用户空间(User Space) 和 内核空间(Kernel Space)。

- 内核空间:位于高地址(如 x86_32 的 3GB~4GB,x86_64 的高 128TB)。所有进程共享同一份内核映射。
- 用户空间 :位于低地址,每个进程独立。包含:
- Text Segment: 代码段,只读。
- Data Segment: 已初始化的全局变量。
- BSS Segment: 未初始化的全局变量。
- Heap : 动态分配区,向上增长(
brk())。 - Memory Mapping Segment :
mmap()区域,用于动态库和文件映射。 - Stack: 栈区,向下增长。
2.2 页表结构 (Page Tables)
Linux 使用多级页表来管理虚拟地址到物理地址的映射。以 64 位系统(x86_64)为例,通常使用 4 级页表:
- PGD (Page Global Directory): 全局页目录。
- PUD (Page Upper Directory): 上层页目录。
- PMD (Page Middle Directory): 中间页目录。
- PTE (Page Table Entry): 页表项,指向最终的物理页框(Page Frame)。
页表查询流程 :
CPU MMU 硬件自动遍历页表。如果 TLB(Translation Lookaside Buffer)缓存命中,则直接获取物理地址(耗时 1-2 周期);否则需要访问内存遍历页表(耗时 10-100 周期)。

2.3 内存映射原理 (mmap)
mmap 是 Linux 内存管理中最强大的系统调用之一。它将文件或设备映射到进程的虚拟地址空间。
关键结构体:
struct mm_struct: 描述进程的整个内存空间。struct vm_area_struct(VMA): 描述一段连续的虚拟内存区域(如代码段、堆、栈)。每个 VMA 定义了该区域的起始/结束地址、权限(读/写/执行)和对应的文件操作。
c
// 示例:内核中遍历 VMA
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;
down_read(&mm->mmap_sem);
for (vma = mm->mmap; vma; vma = vma->vm_next) {
printk("VMA start: %lx, end: %lx\n", vma->vm_start, vma->vm_end);
}
up_read(&mm->mmap_sem);
2.4 页面置换与 LRU 算法
当物理内存不足时,Linux 内核通过 kswapd 守护进程回收内存。核心算法是 双链表 LRU (Least Recently Used)。
- Active List: 最近频繁访问的页。
- Inactive List: 较长时间未访问的页,是回收的首选目标。

页面状态流转:
- 新分配的页通常加入 Active List。
- 随着时间推移,如果未被访问,移动到 Inactive List(老化)。
- 如果再次被访问(Page Fault),移回 Active List(复活)。
- 如果内存紧张,Inactive List 尾部的页被回收(写入 Swap 或直接丢弃)。
2.5 交换空间 (Swap)
Swap 是物理内存的扩展。当匿名页(Anonymous Page,如堆栈内存)需要被回收时,必须写入磁盘 Swap 分区。
- Swap In: 当进程访问已被换出的页时,触发缺页异常,内核从磁盘读回数据。
- Swap Out: 内存压力大时,将不活跃的匿名页写入磁盘。
2.6 OOM Killer (Out Of Memory)
当所有回收手段(Page Cache 回收、Swap)都失效,物理内存耗尽时,内核触发 OOM Killer 。
它根据 oom_score(基于内存占用、运行时间、权限等计算)选择一个"最佳"受害者进程杀死,以释放内存保护系统核心。
3. 内核版本差异 (2.6.x vs 4.x+)
| 特性 | Linux 2.6.x | Linux 4.x+ |
|---|---|---|
| 页表锁 | 全局 mm->page_table_lock |
细粒度锁 (Split Page Table Locks) |
| 透明大页 | 有限支持 (THP 引入较晚) | 成熟的 THP (Transparent Huge Pages) |
| LRU 算法 | 简单的 Active/Inactive 链表 | 多级 LRU (Refault Distance 算法),防颠簸 |
| Cgroup 内存控制 | 初步支持 | 完善的 MemControl (v2),支持 Swap 限制 |
| 缺页处理 | do_page_fault 较简单 |
引入 userfaultfd,支持用户态处理缺页 |
4. 性能调优与故障排查
4.1 性能调优建议
-
HugePages (大页) : 对于数据库(Oracle, PostgreSQL)或 Java Heap,开启大页可以显著减少 TLB Miss,提升性能。
bashsysctl -w vm.nr_hugepages=1024 -
Swappiness : 控制使用 Swap 的积极程度(0-100)。服务器建议设低(1-10),避免不必要的磁盘 I/O。
bashsysctl -w vm.swappiness=10 -
Overcommit Memory : 控制内存分配策略。
0(默认): 启发式拒绝。1: 总是允许分配(适合科学计算)。2: 严格限制(Physical RAM + Swap * ratio)。
4.2 常见问题排查
案例:利用 /proc 分析内存泄漏
- 查看整体内存 :
free -h或cat /proc/meminfo。 - 查看进程内存 :
cat /proc/<PID>/status。VmRSS: 实际物理内存占用。VmSize: 虚拟内存大小。
- 查看内存映射 :
cat /proc/<PID>/maps。- 分析堆(Heap)是否持续增长。
- 分析是否存在大量未释放的 mmap 区域。
- OOM 排查 : 查看
dmesg或/var/log/syslog,搜索 "Out of memory"。
5. 参考文献
- Understanding the Linux Kernel, 3rd Edition - Daniel P. Bovet, Marco Cesati
- Linux Kernel Development, 3rd Edition - Robert Love
- Linux Kernel Source Documentation (
Documentation/vm/) - LWN.net Memory Management Articles
本文档基于 Linux 4.4 内核源码分析。