1. 虚拟内存概念

虚拟内存是现代操作系统内核的基石之一。它通过一层间接映射,为每个进程提供一个独立的、连续的、几乎无限的地址空间,而底层实际存储则分散在物理内存和磁盘交换区中。理解虚拟内存的定义、与物理内存的关系以及它解决的核心问题,是掌握页表配置与权限控制的前提。

1.1 虚拟内存的定义

虚拟内存(Virtual Memory)是一种内存管理技术,它使应用程序认为自己拥有连续、私有的地址空间(虚拟地址空间),而实际数据可能分布在物理内存的不同页框(Page Frame)中,甚至暂时存储在磁盘上。CPU 发出的地址是虚拟地址(VA),由内存管理单元(MMU)根据页表(Page Table)将其转换为物理地址(PA)。

核心定义:虚拟内存 = 虚拟地址空间 + 页表 + MMU 硬件 + 物理内存 + 交换空间(Swap)。应用程序只与虚拟地址打交道,不感知物理内存的碎片化或容量限制。

1.2 物理内存与虚拟地址空间的关系

物理内存是硬件提供的真实存储介质(如 DRAM),容量有限且被所有进程共享。虚拟地址空间是每个进程看到的逻辑视图。两者通过页表建立映射关系,且这种映射是动态、按需的。

对比维度 物理内存 虚拟地址空间
本质 硬件存储资源,以页框为单位 进程的逻辑地址视图,以页为单位
容量 固定且有限(如 8GB) 理论上极大(如 64 位系统 256TB)
所有权 全局共享,由内核统一管理 每个进程私有,相互隔离
连续性 可能存在外部碎片 对进程始终连续
映射关系 — 通过页表映射到物理页框或磁盘

下图展示了虚拟地址空间中的连续页面如何通过页表映射到物理内存中不连续的页框,以及部分页面可能被换出到磁盘交换区。

虚拟地址到物理地址的映射关系 虚拟地址 → 物理地址映射示意图 虚拟地址空间 (进程 A) 虚拟页 0 虚拟页 1 虚拟页 2 虚拟页 3 (换出到磁盘) 页表 页 0 → 页框 3 页 1 → 页框 7 页 2 → 页框 5 页 3 → 磁盘 物理内存 页框 3 页框 5 页框 7 空闲 磁盘交换区
关键洞察:虚拟地址空间的大小由 CPU 位宽决定(如 64 位),而物理内存大小受硬件限制。页表就是连接这两个世界的“翻译官”,同时负责记录权限和状态(如是否在内存中)。

1.3 虚拟内存解决的核心问题

虚拟内存并非仅仅为了“看起来更大”,它解决了操作系统和应用程序面临的三个根本性难题:隔离、扩展和简化编程。

1.3.1 隔离(Isolation)

在没有虚拟内存的系统中,所有程序直接操作物理地址,一个程序的错误(如野指针)可能覆盖另一个程序甚至内核的数据,导致系统崩溃。虚拟内存通过为每个进程提供独立的地址空间,实现了硬件级别的隔离。

  • 进程间隔离:进程 A 无法访问进程 B 的虚拟地址空间,除非通过内核授权的共享内存机制。
  • 内核保护:用户态程序无法访问内核空间的虚拟地址(如高 128TB 区域),页表项中的权限位(如 Supervisor/User 位)会阻止非特权访问。
  • 错误隔离:一个进程的段错误(Segmentation Fault)不会影响其他进程或内核。
实践注意:隔离不是绝对的。通过 mmap 系统调用并设置 MAP_SHARED 标志,两个进程可以映射同一物理页框,实现高效的进程间通信(IPC)。此时页表项指向相同的物理地址,但权限控制仍然独立。

1.3.2 扩展(Extension)

虚拟内存允许程序使用比物理内存更大的地址空间。当物理内存不足时,操作系统将暂时不用的页面换出(Swap Out)到磁盘,需要时再换入(Swap In)。这种机制称为“按需分页”(Demand Paging)。

场景 物理内存 虚拟地址空间 实际可用
无交换 4 GB 4 GB ≤ 4 GB
有交换 4 GB 256 TB 4 GB + 交换空间

下图展示了按需分页的工作流程:当 CPU 访问一个尚未加载到物理内存的虚拟页时,MMU 触发缺页异常(Page Fault),内核从磁盘加载数据到空闲页框,并更新页表。

按需分页与缺页异常处理流程 按需分页与缺页异常处理流程 CPU 访问虚拟地址 MMU 查询页表 物理地址访问完成 缺页异常(Page Fault) 内核从磁盘加载页面 更新页表 & 恢复执行 恢复执行被中断的指令
扩展的代价:磁盘 I/O 比内存访问慢几个数量级(毫秒 vs 纳秒)。频繁的缺页异常会导致系统性能急剧下降,称为“系统颠簸”(Thrashing)。合理配置交换空间大小和页面置换算法至关重要。

1.3.3 简化编程(Simplified Programming)

虚拟内存极大地降低了程序员的负担。在物理内存直接暴露的时代,程序员必须手动管理内存覆盖(Overlay)、处理碎片、确保程序不超过物理内存上限。虚拟内存让这一切变得透明。

  • 连续地址空间:程序员可以假设内存是连续的,无需关心物理内存的碎片化布局。
  • 大内存假象:程序可以分配远大于物理内存的数据结构(如大型数组、数据库缓冲池),操作系统自动处理换入换出。
  • 链接与加载简化:动态链接库(DLL/.so)可以加载到任意虚拟地址,通过页表映射到共享物理代码页,无需地址重定位。
  • 进程创建效率:通过写时复制(Copy-on-Write, COW)技术,fork() 创建子进程时无需立即复制全部内存,只需复制页表并标记为只读,实际物理页在写入时才复制。
编程实践:在 Linux 中,使用 malloc() 分配大块内存时,内核通常只分配虚拟地址空间(通过 mmap),物理页框直到程序实际读写时才按需分配。这就是“内存超量分配”(Overcommit)的基础。

1.4 核心问题总结

核心问题 虚拟内存的解决方案 关键技术
隔离 每个进程拥有独立页表,地址空间互不重叠 页表权限位(U/S, R/W, NX)、上下文切换时切换页表基址寄存器(CR3)
扩展 利用磁盘交换区,按需加载页面 缺页异常处理、页面置换算法(LRU, Clock)、交换空间管理
简化编程 提供连续、大容量、私有的地址空间抽象 写时复制(COW)、内存映射文件(mmap)、动态链接