《深入理解计算机系统》虚拟存储器
《深入理解计算机系统》(CSAPP)的**第 9 章:虚拟存储器(Virtual Memory, VM)**是全书的巅峰章节之一。它结合了之前学过的硬件(缓存、内存)、软件(链接、加载)和操作系统(进程、异常控制流)知识,构建了一个统一、高效且安全的内存抽象模型。
1. 物理寻址 vs 虚拟寻址
- 物理寻址:早期计算机直接使用物理地址访问主存。缺点是多任务难以隔离,程序重定位困难。
- 虚拟寻址:CPU 生成虚拟地址(VA),通过内存管理单元(MMU)转换为物理地址(PA),再访问主存。
- 核心优势:
- 缓存主存:将磁盘作为主存的缓存(利用局部性)。
- 内存管理简化:每个进程拥有独立的线性地址空间,无需关心物理内存碎片。
- 内存保护:防止进程访问其他进程或内核的数据。
- 核心优势:
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 魔法发生的地方。
处理流程:
- MMU 触发异常,控制权交给内核的缺页异常处理程序。
- 内核确定缺失的页面在磁盘上的位置(交换区或可执行文件映射区)。
- 内核选择一个牺牲页(Victim Page)。如果牺牲页被修改过(脏页),需先写回磁盘。
- 从磁盘读取缺失页到物理内存的一个空闲页框中。
- 更新页表,将该 PTE 标记为 Valid,并指向新的物理页框。
- 重新执行导致缺页的那条指令(此时 TLB 和页表已更新,访问成功)。
关键点:对应用程序而言,缺页处理是透明的,只是感觉访问变慢了。
4. 内存映射(Memory Mapping)
Linux 将虚拟存储器组织为一系列区域(Area),每个区域对应一个连续的虚拟地址区间,具有特定的权限(读/写/执行)。
mmap函数
- 私有映射(Private Mapping):
- 用于匿名内存(如
malloc分配的大块内存)或复制写(Copy-on-Write, COW)的文件映射。 - 修改不会写回文件,而是创建新副本。
- 用于匿名内存(如
- 共享映射(Shared Mapping):
- 用于共享库(.so/.dll)或进程间通信。
- 修改会反映到文件中,其他映射该文件的进程可见。
动态链接与 VM
共享库通过 VM 映射到进程的地址空间。多个进程可以共享同一份物理内存中的库代码,极大地节省了内存。
5. 动态内存分配(Dynamic Memory Allocation)
用户程序通过malloc和free管理堆(Heap)。
堆的结构
- 堆是一个运行时数据结构,由一系列**块(Block)**组成。
- 块可以是已分配(Allocated)或空闲(Free)。
- 维护空闲块的链表(隐式链表或显式链表)。
常见问题
- 碎片化(Fragmentation):
- 内部碎片:块内未使用的空间(由于对齐或最小块大小限制)。
- 外部碎片:空闲块总和足够,但没有单个连续的空闲块满足请求。
- 内存泄漏(Memory Leak):分配了内存但忘记释放,导致可用内存逐渐减少。
- 悬垂指针(Dangling Pointer):释放了内存但仍保留指向它的指针,再次访问会导致不可预测的行为(通常通过填充毒值如
0xDEADBEEF来调试)。
垃圾回收(Garbage Collection, GC)
自动管理内存的技术。
- Mark-and-Sweep(标记-清除):基于根集合(寄存器、全局变量、栈)遍历可达对象,标记后清除未标记对象。
- 引用计数:每个对象记录被引用的次数,归零时释放(无法处理循环引用)。
6. 综合示例:fork和execve中的 VM
fork():- 传统做法:复制整个地址空间(开销大)。
- 现代做法(Copy-on-Write):父子进程共享相同的物理页,页表项标记为只读。只有当任一进程尝试写入时,才触发缺页异常,内核此时才复制该页。
execve():- 删除当前进程的现有用户级 VM 区域。
- 为新的可执行文件和共享库创建新的 VM 区域。
- 初始化栈和堆。
7. 本章核心实验:Malloc Lab
你需要实现自己的malloc,free, 和realloc。
- 目标:最大化吞吐量(Throughput)和空间利用率(Utilization)。
- 挑战:
- 如何快速找到合适的空闲块(最佳适配、首次适配、分离空闲链表等)。
- 如何合并相邻的空闲块以减少外部碎片。
- 如何处理边界情况(如请求大小为 0,或释放空指针)。
总结
虚拟存储器是计算机系统中最重要的抽象之一,它:
- 统一了内存和磁盘的管理:通过分页和缺页机制。
- 简化了编程模型:每个进程拥有独立的、连续的地址空间。
- 提供了保护机制:通过页表权限位。
- 支持高效的共享:通过内存映射和共享库。
理解 VM 对于编写高性能、低内存占用且无内存错误的程序至关重要。