虚拟内存核心原理

基础概念
地址翻译
页表结构
多级页表
缺页中断

什么是虚拟内存?

核心概念:虚拟内存是操作系统提供的一种内存管理技术,它为每个进程提供一个连续、独立且看似无限的地址空间,而实际上物理内存可能有限且分散。

为什么需要虚拟内存?

内存隔离
每个进程都有独立的地址空间,防止相互干扰
内存扩展
使用磁盘作为交换空间,提供比物理内存更大的地址空间
内存共享
多个进程可以共享同一物理内存页(如共享库)
简化编程
程序员无需关心物理内存布局,使用连续虚拟地址

物理内存 vs 虚拟内存

特性物理内存虚拟内存
可见性操作系统管理,进程不可见进程直接访问的地址空间
大小受硬件限制(如16GB RAM)受CPU位数限制(32位→4GB,64位→16EB)
连续性可能分散、碎片化对每个进程看似连续
分配以页为单位分配给进程进程看到完整的地址空间

地址翻译过程

核心机制:CPU生成的地址是虚拟地址,需要通过页表翻译成物理地址,才能访问实际的物理内存。

地址结构

虚拟地址(VA)= 虚拟页号(VPN) + 页内偏移(Offset)
例如:32位系统,页大小4KB(2^12),则低12位是Offset,高20位是VPN

地址翻译流程(点击下一步查看)

步骤1:CPU生成虚拟地址
虚拟地址:0x12345678
VPN = 0x12345,Offset = 0x678
CPU执行内存访问指令时,生成虚拟地址。假设32位系统,页大小4KB(2^12),则低12位为页内偏移,高20位为虚拟页号(VPN)。
步骤2:查询页表
MMU使用VPN(0x12345)查找页表
页表基址寄存器(CR3)指向页表起始位置
页表项地址 = CR3 + VPN × PTE大小
内存管理单元(MMU)自动完成这个查询。页表存储在物理内存中,由操作系统维护。每个页表项(PTE)通常占4字节或8字节。
步骤3:获取物理页号(PPN)
页表项内容:有效位=1,PPN=0x6789
物理地址 = PPN << 12 | Offset = 0x6789678
如果有效位为1,表示该页在物理内存中,可以直接获取对应的物理页号(PPN)。然后将PPN和偏移量组合成完整的物理地址。
步骤4:访问物理内存
MMU使用物理地址0x6789678访问物理内存
读取或写入数据
✓ 地址翻译完成!
虚拟地址 0x12345678 → 物理地址 0x6789678
整个过程由MMU硬件自动完成,对程序透明。

页表原理

页表(Page Table):存储虚拟页到物理页映射关系的数据结构,每个进程都有自己的页表。

页表项(PTE)的关键字段

字段作用
有效位(Valid Bit)该页是否在物理内存中(1=在内存,0=不在)
物理页号(PPN)对应的物理页框号
访问位(Accessed Bit)该页是否被访问过(用于页面置换算法)
修改位(Dirty Bit)该页是否被修改过(换出时是否需要写回磁盘)
保护位(Protection Bits)读/写/执行权限
重要:当有效位为0时,访问该页会触发缺页中断,操作系统需要将页面从磁盘加载到物理内存。

多级页表设计

为什么需要多级页表?如果虚拟地址空间很大(如64位系统的16EB),单级页表会占用巨大空间。多级页表只存储实际使用的部分,大幅节省内存。

单级页表的问题

问题:32位系统,4KB页 → 需要2^20个页表项,每个4字节 → 每个进程需要4MB连续内存存储页表!
64位系统更夸张,理论上需要数EB的内存来存储页表!

多级页表的优势

节省空间
只为使用的虚拟地址分配页表
灵活性
支持大地址空间和稀疏地址分配
连续性要求低
各级页表可以分散在物理内存中
按需分配
只有实际使用的部分才分配页表

两级页表示例(32位系统)

地址划分:
- 高10位:一级页表索引(页目录)
- 中10位:二级页表索引
- 低12位:页内偏移(4KB页)

优点:如果某个区域未使用,对应的二级页表根本不需要分配!

缺页中断(Page Fault)

缺页中断:当进程访问的页面不在物理内存中时,会触发缺页中断,操作系统需要将所需页面从磁盘加载到物理内存。

缺页中断处理流程(点击下一步查看)

步骤1:进程访问虚拟页面
进程访问虚拟地址,该地址所在的页面不在物理内存中(有效位=0)
进程正常执行内存访问指令,但访问的页面尚未加载到物理内存。
步骤2:触发缺页中断
MMU发现页表项有效位为0
CPU切换到内核态,触发缺页中断异常
缺页中断是一种异常(Exception),会暂停当前进程的执行,切换到操作系统内核的缺页中断处理程序。
步骤3:操作系统处理缺页
1. 检查虚拟地址合法性(是否在进程地址空间内)
2. 从磁盘读取所需页面到物理内存
3. 分配物理页框,更新页表(设置有效位=1,填入PPN)
4. 如果物理内存已满,需要置换页面(选择牺牲页换出到磁盘)
这是最耗时的步骤,需要磁盘I/O。如果牺牲页被修改过,还需要先写回磁盘。
步骤4:重新执行指令
缺页中断处理完成
CPU切换回用户态,重新执行导致缺页的指令
✓ 缺页中断处理完成!
由于页表已经更新,这次内存访问可以正常完成。对用户进程来说,这一切是透明的。
性能影响与优化
⚠️ 性能警告:缺页中断需要磁盘I/O,代价很高!
内存访问:~100纳秒
磁盘访问:~10毫秒
相差 100,000倍
优化方向:
1. 减少缺页次数:预取、工作集模型
2. 减少单次缺页代价:使用SSD、减少页面换入换出
3. 好的页面置换算法:LRU、Clock等