长文104: 内存管理-虚拟篇 I:页表操作与虚拟内存区域(VMA)深度解析

“虚拟内存是现代操作系统的基石,而 VMA 是进程地址空间的‘地图’。 本文将深入 x86 页表结构、VMA 设计原理,并对比 Linux 的实现细节, 构建一个支持按需分页、高半内核映射的工业级虚拟内存系统。”

引言:虚拟内存的核心价值

虚拟内存(Virtual Memory)是现代操作系统最伟大的抽象之一,它提供了三大核心能力:

  1. 地址空间隔离

    :每个进程拥有独立的 4GB 虚拟地址空间

  2. 内存保护

    :防止进程越界访问(如用户态访问内核)

  3. 内存扩展

    :通过交换(Swapping)支持大于物理内存的应用

而实现虚拟内存的关键组件是:

页表

(Page Table):硬件级地址翻译机制
VMA

(Virtual Memory Area):软件级地址空间管理

本文将系统性地剖析这两个核心组件,从 x86 页表硬件细节到 VMA 软件设计,并 深度对比 Linux 的实现 ,最终提供一个可运行的工业级框架。


第一章:x86 页表结构与硬件机制

1.1 32 位 x86 分页机制

x86 32 位分页采用 两级页表结构 :

地址翻译流程:

  1. CR3 寄存器

    :存储页目录基地址(物理地址)

  2. 页目录项

    (PDE):通过高 10 位索引,得到页表基地址

  3. 页表项

    (PTE):通过中间 10 位索引,得到物理页帧地址

  4. 物理地址

    = 页帧地址 + 页内偏移

页目录项(PDE)结构(32 位):

名称 说明
0 P 存在位 :1=页在内存
1 R/W 读写位 :1=可写,0=只读
2 U/S 用户/超级用户位 :1=用户态可访问
3 PWT 页写通(缓存策略)
4 PCD 页缓存禁用
5 A 访问位 (CPU 自动置 1)
6 D 脏位(仅页表项)
7 PS 页大小 :1=4MB 大页(仅 PDE)
12-31 页表基地址 物理地址高 20 位 (4KB 对齐)

页表项(PTE)结构(32 位):

基本同 PDE,但 PS 位无效 , D 位有效 (写时置 1)。

⚠️ 所有地址字段都是物理地址!虚拟地址仅用于索引 。

1.2 关键权限位详解

存在位(P)

P=0

:触发
Page Fault

#PF

用途

:实现按需分页、交换、内存保护

读写位(R/W)

R/W=0

:只读页
写时触发 Page Fault

:实现写时复制(CoW)

用户/超级用户位(U/S)

U/S=0

:仅内核态可访问(Ring 0)
U/S=1

:用户态可访问(Ring 3)
硬件自动检查

:用户态访问 U/S=0 的页 →
#PF

全局位(G)

G=1

:TLB 条目在进程切换时不刷新
用途

:高半内核映射优化

1.3 TLB 与性能优化

TLB (Translation Lookaside Buffer)是页表的硬件缓存:

命中

:直接返回物理地址(1-2 时钟周期)
未命中

:遍历页表(100+ 时钟周期)

TLB 刷新时机:

进程切换

:加载新 CR3 → 刷新除 G=1 外的所有 TLB
页表修改

:需手动
invlpg
刷新特定地址

高半内核映射优化:

内核映射设置
G=1
进程切换时 TLB 保留内核条目 →
性能提升 30%+


第二章:页表操作 API 设计

2.1 页表数据结构

页目录与页表

页表项操作宏

2.2 核心页表操作函数

获取页表(按需创建)

映射虚拟地址到物理页

取消映射

获取物理地址

2.3 递归页表:页表自映射

问题:内核如何操作任意进程的页表?

页表本身在虚拟内存中
需要一种方式通过虚拟地址访问页表

解决方案:递归页表(Recursive Page Table)

将页目录的最后一项(PDE 1023)指向
页目录自身
形成虚拟地址 → 页目录 → 页表 → 页 的映射

虚拟地址布局:

实现:

💡 Linux 使用 4 级页表,但递归映射思想相同 !


第三章:VMA 设计与进程地址空间

3.1 VMA 的核心作用

VMA(Virtual Memory Area)是进程虚拟地址空间的 软件抽象 :

描述连续虚拟区域

:代码段、堆、栈、mmap 区域
管理区域属性

:权限、类型、文件映射
支持高效查找

:红黑树或链表

没有 VMA 的问题:

无法知道虚拟地址属于哪个区域
无法正确处理 Page Fault
无法实现
munmap
系统调用

3.2 VMA 数据结构设计

基础 VMA 结构

进程 PCB 扩展

3.3 VMA 操作算法

查找 VMA(find_vma)

插入 VMA(insert_vma)

合并相邻 VMA

3.4 进程地址空间布局

典型 32 位布局:

关键常量:


第四章:按需分页与 Page Fault 处理

4.1 按需分页(Demand Paging)原理

传统分配问题:

进程启动时分配所有页 → 内存浪费
大部分页从未被访问

按需分页解决方案:

VMA 创建时不分配物理页
首次访问时触发 Page Fault
内核分配页并映射

优势:

内存节省

:仅分配实际使用的页
启动加速

:避免一次性分配大量页

4.2 Page Fault 处理流程

Page Fault 错误码:

说明
0 0=不存在的页,1=保护错误
1 0=读错误,1=写错误
2 0=内核态,1=用户态
3 1=保留位错误
4 1=指令获取错误

处理逻辑:

4.3 文件映射的按需加载

mmap 文件映射流程:

  1. mmap

    创建 VMA(标记
    VM_FILE

  2. 首次访问触发 Page Fault
  3. 内核从文件读取对应页到物理页
  4. 映射并返回

实现:


第五章:高半内核映射与进程切换优化

5.1 高半内核映射设计

问题:每次进程切换都要刷新 TLB

所有 TLB 条目失效 → 性能下降 30%+

解决方案:高半内核映射

低地址

(0-0xBFFFFFFF):用户空间(每个进程独立)
高地址

(0xC0000000-0xFFFFFFFF):内核空间(所有进程共享)

实现:

创建进程页目录:

5.2 进程切换优化

传统切换(低效):

高半内核切换(高效):

📊 性能提升 :TLB 未命中率降低 50%+,尤其在频繁系统调用时!

5.3 内核访问用户内存

问题:内核运行在高地址,如何安全访问用户内存?

不能直接解引用用户指针

(可能触发 Page Fault)

解决方案:临时映射

⚠️ 绝对不要直接解引用用户指针 !可能触发 Page Fault 或访问非法地址。


第六章:Linux VMA 实现深度对比

6.1 Linux VMA 数据结构

核心结构( mm_types.h ):

关键改进:

双向链表 + 红黑树

:兼顾遍历和查找
rb_subtree_gap

:快速查找空闲区域(
mmap
无地址指定时)
vm_ops

:VMA 特定操作(如文件映射的
fault
回调)

6.2 VMA 操作函数(vm_operations_struct)

文件映射的 fault 回调:

6.3 内存描述符(mm_struct)

Linux 使用 mm_struct 管理进程内存:

引用计数设计:

mm_users

:用户态引用(线程共享)
mm_count

:内核态引用(如内核线程)

💡 Linux 的 VMA 设计是工业级复杂度的典范 !


结论:虚拟内存系统的工程艺术

虚拟内存系统是操作系统内核的 皇冠明珠 ,它融合了:

硬件机制

(页表、TLB、Page Fault)
软件抽象

(VMA、mm_struct)
算法优化

(红黑树、按需分页)
性能工程

(高半内核、临时映射)

理解虚拟内存不仅有助于内核开发,更能培养 系统级思维 :

抽象层次

:硬件页表 → 软件 VMA → 用户 mmap
性能权衡

:内存节省 vs TLB 压力
安全边界

:用户/内核隔离

对于希望深入 Linux 内核的开发者,建议:

阅读
mm/mmap.c

mm/memory.c
源码
使用
pmap

/proc//maps
查看 VMA
通过
perf
分析 Page Fault 开销

虚拟内存的故事,仍在继续。随着大页(HugeTLB)、用户态页表(Userfaultfd)等新特性的出现,这一古老而优雅的系统将焕发新的生机。


附录:关键数据结构与函数速查

核心数据结构

结构 作用 文件
struct vm_area_struct VMA 描述符 mm_types.h
struct mm_struct 内存描述符 mm_types.h
pgd_t / pte_t 页表类型 pgtable.h

关键函数

函数 功能 文件
find_vma 查找 VMA mmap.c
insert_vm_struct 插入 VMA mmap.c
handle_mm_fault Page Fault 处理 memory.c
do_mmap mmap 系统调用 mmap.c

调试接口

接口 用途
/proc//maps 查看进程 VMA
/proc//smaps 详细 VMA 信息
pmap 命令行工具
cat /proc/pagetypeinfo 页类型分布
最后修改: 2026年7月1日

作者

评论

发表评论

您的邮箱地址不会被公开。