核心挑战:超越物理内存
为了支持比物理内存更大的虚拟地址空间,操作系统需要在内存层级(Memory Hierarchy)中引入更底层的存储设备(如硬盘 or SSD)。
关键问题操作系统如何利用大而慢的设备,透明地提供巨大虚拟地址空间的假象?
为什么需要巨大地址空间?
- 易用性(Convenience):程序员无需担心内存容量限制,可自然地分配内存,避免了早期“内存覆盖(Memory Overlays)”技术带来的手动管理负担。
- 多道程序(Multiprogramming):支持多个进程并发运行。当物理内存不足以容纳所有进程的页时,操作系统必须能够将部分页换出到外部存储。
存储技术虽然硬盘(HDD)是传统的交换介质,但现代系统更多使用闪存(SSD)。目前只需假设存在一个大而慢的设备,用于构建虚拟内存的假象。
交换空间
交换空间(Swap Space) 是硬盘上开辟的一块空间,用于物理页的移入(Swap-in)和移出(Swap-out)。
- 基本单位:操作系统以 页(Page) 为单元读写交换空间。
- 寻址:操作系统必须记录给定页在硬盘上的地址(Disk Address)。
- 容量意义:交换空间的大小决定了系统在某一时刻能支持的最大虚拟内存页数。

硬盘上的其他交换目的地交换空间并非唯一的目的地。例如,二进制程序的代码页 最初就在硬盘上。当内存不足时,系统可以回收这些代码页的物理内存,因为它们是只读的,后续可以随时从原始二进制文件中重新加载,无需占用交换空间。
存在位
为了支持页交换,页表项(PTE)中引入了 存在位(Present Bit)。
- 存在位 = 1:页在物理内存中,流程与普通内存访问一致。
- 存在位 = 0:页不在物理内存中,而是在硬盘上。
页错误(Page Fault)
当进程访问一个存在位为 0 的页时,会触发 页错误(Page Fault)。
- 本质:这实际上是一种“页未命中(Page Miss)”。
- 处理机制:硬件无法处理此类访问,会触发异常并将控制权交给操作系统。
- 处理程序:操作系统调用 页错误处理程序(Page-Fault Handler) 来处理该异常。
术语说明虽然被称为“错误(Fault)”,但访问被交换到硬盘的合法地址是正常行为。之所以沿用此称呼,是因为其处理流程与非法访问导致的异常类似,都需要硬件中断当前执行并陷入内核态。
页错误处理流程
当页错误发生时,操作系统负责将缺失的页从硬盘换入内存。
- 查找硬盘地址:操作系统通过 PTE 中的相关位(通常在页不在内存时复用 PFN 字段)获取该页在硬盘上的位置。
- 发起 I/O 请求:操作系统发出读取硬盘的请求,将页载入物理内存。
- 更新页表:I/O 完成后,操作系统更新 PTE,将存在位设为 1,并填入正确的 PFN。
- 重试指令:恢复进程运行并重试触发故障的指令。此时 TLB 仍会未命中,但随后的页表查找将成功命中内存。
为什么由软件处理页错误?
- 性能开销可忽略:硬盘 I/O 极其缓慢(毫秒级),相比之下操作系统的处理开销极小。
- 硬件简化:处理页错误需要了解交换空间、I/O 协议等复杂细节,交给操作系统处理能显著简化硬件设计。
多道程序的交叠(Overlap)在等待硬盘 I/O 完成期间,故障进程处于 阻塞(Blocked) 状态。操作系统会切换到其他就绪进程运行,从而充分利用 CPU 资源。
内存满了怎么办
如果物理内存已满,在换入新页之前,操作系统必须先换出(Page out)一个或多个页。
- 页交换策略(Page-replacement policy):决定选择哪些页被替换的过程。
- 性能影响:错误的替换决策会导致程序频繁访问硬盘,性能下降数万倍。具体的替换算法将在下一章详细讨论。
完整控制流
通过硬件(TLB/页表查找)与软件(页错误处理)的配合,我们可以总结出内存访问的完整控制流。
硬件处理逻辑 (TLB & Page Table)
当 CPU 发起内存访问时,硬件首先尝试在 TLB 中查找转换关系。
VPN = (VirtualAddress & VPN_MASK) >> SHIFT
(Success, TlbEntry) = TLB_Lookup(VPN)
if (Success == True) // TLB Hit
if (CanAccess(TlbEntry.ProtectBits) == True)
Offset = VirtualAddress & OFFSET_MASK
PhysAddr = (TlbEntry.PFN << SHIFT) | Offset
Register = AccessMemory(PhysAddr)
else
RaiseException(PROTECTION_FAULT)
else // TLB Miss
PTEAddr = PTBR + (VPN * sizeof(PTE))
PTE = AccessMemory(PTEAddr)
if (PTE.Valid == False)
RaiseException(SEGMENTATION_FAULT)
else
if (CanAccess(PTE.ProtectBits) == False)
RaiseException(PROTECTION_FAULT)
else if (PTE.Present == True)
// 硬件管理 TLB 的情况
TLB_Insert(VPN, PTE.PFN, PTE.ProtectBits)
RetryInstruction()
else if (PTE.Present == False)
RaiseException(PAGE_FAULT) TLB 未命中的三种情况:
- 页存在且有效:硬件从页表中获取 PFN,插入 TLB 并重试指令。
- 页有效但不在内存:触发 页错误(Page Fault),交给操作系统处理。
- 页无效:访问非法地址,触发 段错误(Segmentation Fault),通常会导致进程被终止。
软件处理逻辑 (OS Page-Fault Handler)
当硬件抛出页错误异常后,操作系统接管并执行以下步骤:
PFN = FindFreePhysicalPage()
if (PFN == -1) // 内存已满
PFN = EvictPage() // 运行替换算法换出旧页
DiskRead(PTE.DiskAddr, PFN) // 发起磁盘 I/O 并阻塞进程
PTE.present = True // 更新页表状态
PTE.PFN = PFN
RetryInstruction() // 重新执行指令- 分配物理帧:寻找空闲物理页,若内存已满则调用替换算法腾出空间。
- 磁盘换入:从交换空间读取数据(此过程进程阻塞,OS 可切换其他进程)。
- 更新与重试:更新 PTE 状态,重试指令。此时会再次触发 TLB 未命中,但随后的硬件查找将成功命中内存。
交换何时真正发生
操作系统通常不会等到内存完全耗尽才开始换出页,而是采取更主动的策略。
水位线机制 (Watermarks)
操作系统通过设置 高水位线 (HW) 和 低水位线 (LW) 来触发内存释放:
- 触发条件:当可用内存少于 LW 时,后台线程开始运行。
- 停止条件:直到可用内存达到 HW,后台线程进入休眠。
这个后台线程通常被称为 交换守护进程 (Swap Daemon) 或 页守护进程 (Page Daemon)。
性能优化:聚集与分组
为了提高磁盘效率,操作系统通常会将多个待写入的页 聚集 (Cluster) 或 分组 (Group) 后一次性写入交换空间。这减少了磁盘寻道和旋转开销,显著提升了 I/O 性能。
后台工作的优势将任务(如磁盘写入缓冲)放在后台运行,不仅可以合并操作提高硬件效率,还能优化写入延迟,并利用系统空闲时间完成工作。
小结
- 透明性:超越物理内存的机制(页交换)对进程是透明的,进程只需关注其私有的虚拟地址空间。
- 关键机制:通过 存在位 (Present Bit) 标识页的位置,利用 页错误处理程序 实现按需换入。
- 性能代价:虽然提供了巨大空间的假象,但最坏情况下(频繁触发磁盘 I/O),单条指令的执行时间可能从纳秒级飙升至毫秒级。