VAX/VMS 操作系统(由 Dave Cutler 领导开发)是软硬件协同设计的典范。它面临的主要挑战是通用性(Generality),即需要在从低端到高端的各种 VAX 机器上高效运行。VMS 展示了如何通过软件创新来掩盖硬件缺陷(如过小的页大小)。
内存管理硬件
VAX-11 采用分页与分段的混合策略:
- 虚拟地址:32 位,分为 512 字节的页(Page)。
- 地址结构:
- 高 2 位:段标识符(Segment ID)。
- 中间 21 位:虚拟页号(VPN)。
- 低 9 位:页内偏移(Offset)。
真实地址空间
VMS 将 32 位地址空间划分为四个象限(每段 1GB):
| 段 | 名称 | 用途 | 增长方向 |
|---|---|---|---|
| P0 | 程序空间 (Program) | 用户代码、数据、堆 | 向下增长 |
| P1 | 控制空间 (Control) | 用户栈 | 向上增长 |
| S0 | 系统空间 (System) | 操作系统代码、数据 | - |
| S1 | 未使用 | 保留 | - |

1. 减少页表内存占用
VAX 的页大小仅为 512 字节,导致线性页表过大。VMS 采取了两种策略:
- 分段页表:P0 和 P1 之间的未使用空间不需要页表项(P0 基址/界限寄存器 + P1 基址/界限寄存器)。
- 内核虚拟内存中的页表:用户页表(P0/P1)本身存储在内核虚拟内存(S0)中。这意味着页表本身可以被交换到磁盘,从而缓解物理内存压力。
地址转换的复杂性由于页表在虚拟内存中,查找 P0/P1 地址时,硬件可能需要先查找系统页表(S0)的物理地址。TLB 在此过程中至关重要,用于加速转换。
2. 空指针检测与内核映射
- 空指针保护:P0 的第 0 页被标记为不可访问,访问
NULL指针会触发段错误(Segmentation Fault)。 - 内核映射:内核地址空间(S0)被映射到每个进程的高端地址。
页交换 (Page Replacement)
VAX 硬件没有引用位(Reference Bit),这给页面置换算法带来了挑战。
1. 分段 FIFO (Segmented FIFO)
VMS 引入了驻留集大小 (RSS) 的概念,即每个进程在内存中允许保留的最大页数。
- 策略:每个进程维护一个私有的 FIFO 队列。
- 驱逐:当进程超过 RSS 时,利用 FIFO 驱逐页面。
2. 二次机会列表 (Second-Chance Lists)
为了缓解 FIFO 的性能问题(避免踢出热点页),VMS 使用两个全局列表作为“缓冲区”:
- 全局干净页列表 (Global Clean List):存放被驱逐但未修改的页。
- 全局脏页列表 (Global Dirty List):存放被驱逐且已修改的页。
回收机制 (Reclaim)当进程 P 需要访问一个已被驱逐到全局列表的页时,可以直接从内存中**回收(Reclaim)**该页,避免磁盘 I/O。这使得算法表现接近 LRU。
3. 页聚集 (Page Clustering)
为了解决小页面(512B)导致的 I/O 低效问题,VMS 将全局脏列表中的页面分组成大批次(Cluster),一次性写入磁盘。
其他虚拟内存技巧
1. 按需置零 (Demand Zeroing)
防止信息泄露(看到其他进程的数据)需要将新分配的页清零。
- 惰性策略:分配时仅在页表中标记“按需置零”且不可访问。
- 触发:当进程实际写入该页时,触发缺页异常,OS 此时才分配物理页、清零并映射。
- 收益:如果申请了内存但从未使用,则完全避免了物理分配和清零开销。
2. 写时复制 (Copy-On-Write, COW)
用于高效实现 fork() 和共享库。
- 机制:将页面映射为只读。
- 触发:当任一进程尝试写入时,触发保护异常。OS 复制该页,并将新旧页面重新映射为可读写。
- 收益:避免了大量不必要的内存复制。
// 模拟空指针访问导致的段错误
int *p = NULL; // 虚拟地址 0
*p = 10; // 触发异常:VPN 0 标记为无效