《深入理解计算机系统》虚拟存储器

《深入理解计算机系统》(CSAPP)的**第 9 章:虚拟存储器(Virtual Memory, VM)**是全书的巅峰章节之一。它结合了之前学过的硬件(缓存、内存)、软件(链接、加载)和操作系统(进程、异常控制流)知识,构建了一个统一、高效且安全的内存抽象模型。


1. 物理寻址 vs 虚拟寻址

  • 物理寻址:早期计算机直接使用物理地址访问主存。缺点是多任务难以隔离,程序重定位困难。
  • 虚拟寻址:CPU 生成虚拟地址(VA),通过内存管理单元(MMU)转换为物理地址(PA),再访问主存。
    • 核心优势
      1. 缓存主存:将磁盘作为主存的缓存(利用局部性)。
      2. 内存管理简化:每个进程拥有独立的线性地址空间,无需关心物理内存碎片。
      3. 内存保护:防止进程访问其他进程或内核的数据。

2. 地址翻译(Address Translation)

这是 VM 的硬件基础。MMU 利用存储在内存中的**页表(Page Table)**进行地址转换。

分页机制(Paging)
  • 页(Page):虚拟内存块(通常 4KB)。
  • 页框(Page Frame):物理内存块。
  • 页表项(PTE, Page Table Entry):包含一个有效位(Valid Bit)和一个物理页框号(PPN)。
    • Valid = 1:页面在物理内存中。
    • Valid = 0:页面不在物理内存中(可能在磁盘上或未分配)。
多级页表

为了节省空间,现代系统使用多级页表。如果某一级页表项无效,则不需要分配下一级页表的空间。这利用了程序地址空间的稀疏性。

TLB(Translation Lookaside Buffer)
  • 作用:MMU 内部的高速缓存,缓存最近使用的 PTE。
  • 命中:直接获取 PPN,速度极快。
  • 缺失:需要访问内存中的页表(可能引发缺页异常)。

3. 缺页异常(Page Faults)

当 CPU 访问一个 Valid=0 的页面时,会触发缺页异常(Page Fault)。这是 VM 魔法发生的地方。

处理流程:

  1. MMU 触发异常,控制权交给内核的缺页异常处理程序。
  2. 内核确定缺失的页面在磁盘上的位置(交换区或可执行文件映射区)。
  3. 内核选择一个牺牲页(Victim Page)。如果牺牲页被修改过(脏页),需先写回磁盘。
  4. 从磁盘读取缺失页到物理内存的一个空闲页框中。
  5. 更新页表,将该 PTE 标记为 Valid,并指向新的物理页框。
  6. 重新执行导致缺页的那条指令(此时 TLB 和页表已更新,访问成功)。

关键点:对应用程序而言,缺页处理是透明的,只是感觉访问变慢了。


4. 内存映射(Memory Mapping)

Linux 将虚拟存储器组织为一系列区域(Area),每个区域对应一个连续的虚拟地址区间,具有特定的权限(读/写/执行)。

mmap函数
  • 私有映射(Private Mapping)
    • 用于匿名内存(如malloc分配的大块内存)或复制写(Copy-on-Write, COW)的文件映射。
    • 修改不会写回文件,而是创建新副本。
  • 共享映射(Shared Mapping)
    • 用于共享库(.so/.dll)或进程间通信。
    • 修改会反映到文件中,其他映射该文件的进程可见。
动态链接与 VM

共享库通过 VM 映射到进程的地址空间。多个进程可以共享同一份物理内存中的库代码,极大地节省了内存。


5. 动态内存分配(Dynamic Memory Allocation)

用户程序通过mallocfree管理堆(Heap)。

堆的结构
  • 堆是一个运行时数据结构,由一系列**块(Block)**组成。
  • 块可以是已分配(Allocated)空闲(Free)
  • 维护空闲块的链表(隐式链表或显式链表)。
常见问题
  1. 碎片化(Fragmentation)
    • 内部碎片:块内未使用的空间(由于对齐或最小块大小限制)。
    • 外部碎片:空闲块总和足够,但没有单个连续的空闲块满足请求。
  2. 内存泄漏(Memory Leak):分配了内存但忘记释放,导致可用内存逐渐减少。
  3. 悬垂指针(Dangling Pointer):释放了内存但仍保留指向它的指针,再次访问会导致不可预测的行为(通常通过填充毒值如0xDEADBEEF来调试)。
垃圾回收(Garbage Collection, GC)

自动管理内存的技术。

  • Mark-and-Sweep(标记-清除):基于根集合(寄存器、全局变量、栈)遍历可达对象,标记后清除未标记对象。
  • 引用计数:每个对象记录被引用的次数,归零时释放(无法处理循环引用)。

6. 综合示例:forkexecve中的 VM

  • fork()
    • 传统做法:复制整个地址空间(开销大)。
    • 现代做法(Copy-on-Write):父子进程共享相同的物理页,页表项标记为只读。只有当任一进程尝试入时,才触发缺页异常,内核此时才复制该页。
  • execve()
    • 删除当前进程的现有用户级 VM 区域。
    • 为新的可执行文件和共享库创建新的 VM 区域。
    • 初始化栈和堆。

7. 本章核心实验:Malloc Lab

你需要实现自己的malloc,free, 和realloc

  • 目标:最大化吞吐量(Throughput)和空间利用率(Utilization)。
  • 挑战
    • 如何快速找到合适的空闲块(最佳适配、首次适配、分离空闲链表等)。
    • 如何合并相邻的空闲块以减少外部碎片。
    • 如何处理边界情况(如请求大小为 0,或释放空指针)。

总结

虚拟存储器是计算机系统中最重要的抽象之一,它:

  1. 统一了内存和磁盘的管理:通过分页和缺页机制。
  2. 简化了编程模型:每个进程拥有独立的、连续的地址空间。
  3. 提供了保护机制:通过页表权限位。
  4. 支持高效的共享:通过内存映射和共享库。

理解 VM 对于编写高性能、低内存占用且无内存错误的程序至关重要。