第 21 章 超越物理内存:机制

核心挑战:超越物理内存

为了支持比物理内存更大的虚拟地址空间,操作系统需要在内存层级(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)”,但访问被交换到硬盘的合法地址是正常行为。之所以沿用此称呼,是因为其处理流程与非法访问导致的异常类似,都需要硬件中断当前执行并陷入内核态。

页错误处理流程

当页错误发生时,操作系统负责将缺失的页从硬盘换入内存。

  1. 查找硬盘地址:操作系统通过 PTE 中的相关位(通常在页不在内存时复用 PFN 字段)获取该页在硬盘上的位置。
  2. 发起 I/O 请求:操作系统发出读取硬盘的请求,将页载入物理内存。
  3. 更新页表:I/O 完成后,操作系统更新 PTE,将存在位设为 1,并填入正确的 PFN。
  4. 重试指令:恢复进程运行并重试触发故障的指令。此时 TLB 仍会未命中,但随后的页表查找将成功命中内存。
为什么由软件处理页错误?
  1. 性能开销可忽略:硬盘 I/O 极其缓慢(毫秒级),相比之下操作系统的处理开销极小。
  2. 硬件简化:处理页错误需要了解交换空间、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 未命中的三种情况:

  1. 页存在且有效:硬件从页表中获取 PFN,插入 TLB 并重试指令。
  2. 页有效但不在内存:触发 页错误(Page Fault),交给操作系统处理。
  3. 页无效:访问非法地址,触发 段错误(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() // 重新执行指令
  1. 分配物理帧:寻找空闲物理页,若内存已满则调用替换算法腾出空间。
  2. 磁盘换入:从交换空间读取数据(此过程进程阻塞,OS 可切换其他进程)。
  3. 更新与重试:更新 PTE 状态,重试指令。此时会再次触发 TLB 未命中,但随后的硬件查找将成功命中内存。

交换何时真正发生

操作系统通常不会等到内存完全耗尽才开始换出页,而是采取更主动的策略。

水位线机制 (Watermarks)

操作系统通过设置 高水位线 (HW)低水位线 (LW) 来触发内存释放:

  • 触发条件:当可用内存少于 LW 时,后台线程开始运行。
  • 停止条件:直到可用内存达到 HW,后台线程进入休眠。

这个后台线程通常被称为 交换守护进程 (Swap Daemon)页守护进程 (Page Daemon)

性能优化:聚集与分组

为了提高磁盘效率,操作系统通常会将多个待写入的页 聚集 (Cluster)分组 (Group) 后一次性写入交换空间。这减少了磁盘寻道和旋转开销,显著提升了 I/O 性能。

后台工作的优势

将任务(如磁盘写入缓冲)放在后台运行,不仅可以合并操作提高硬件效率,还能优化写入延迟,并利用系统空闲时间完成工作。

小结

  • 透明性:超越物理内存的机制(页交换)对进程是透明的,进程只需关注其私有的虚拟地址空间。
  • 关键机制:通过 存在位 (Present Bit) 标识页的位置,利用 页错误处理程序 实现按需换入。
  • 性能代价:虽然提供了巨大空间的假象,但最坏情况下(频繁触发磁盘 I/O),单条指令的执行时间可能从纳秒级飙升至毫秒级。