虚拟内存核心原理
什么是虚拟内存?
核心概念:虚拟内存是操作系统提供的一种内存管理技术,它为每个进程提供一个连续、独立且看似无限的地址空间,而实际上物理内存可能有限且分散。
为什么需要虚拟内存?
内存隔离
每个进程都有独立的地址空间,防止相互干扰
内存扩展
使用磁盘作为交换空间,提供比物理内存更大的地址空间
内存共享
多个进程可以共享同一物理内存页(如共享库)
简化编程
程序员无需关心物理内存布局,使用连续虚拟地址
物理内存 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等