在实现内存虚拟化时,我们追求与 CPU 虚拟化类似的 受限直接访问(LDE) 策略,旨在平衡 高效、控制 与 灵活性:
- 高效:利用硬件支持(如寄存器、TLB、页表)实现快速地址转换。
- 控制:操作系统确保进程只能访问自身的地址空间,实现内存保护。
- 灵活性:允许程序以任意方式访问其地址空间,简化编程模型。
核心问题如何高效、灵活地虚拟化内存,同时保持对应用程序内存访问的严密控制?
基于硬件的地址转换
地址转换(Address Translation) 是实现内存虚拟化的通用技术,是对 LDE 的补充:
- 硬件职责:在每次内存引用(取指、读写数据)时,自动将指令中的 虚拟地址(Virtual Address) 转换为 物理地址(Physical Address)。
- OS 职责:在关键点介入,管理内存分配(记录空闲与占用),并正确配置硬件以执行转换。
通过软硬结合,系统为每个程序创造了拥有“私有内存”的假象,而底层则是多个程序在安全、高效地共享物理内存。
初始假设
为了简化讨论,我们先对地址空间做如下假设(后续会逐步放宽):
- 连续性:进程的地址空间必须连续地存放在物理内存中。
- 大小受限:地址空间小于物理内存总量。
- 固定大小:每个进程的地址空间大小完全相同。
实例解析
设想一段简单的 C 代码及其对应的 x86 汇编:
void func() {
int x;
x = x + 3; // 目标代码
}128: movl 0x0(%ebx), %eax ; 将 [ebx] 加载到 eax
132: addl $0x03, %eax ; eax += 3
135: movl %eax, 0x0(%ebx) ; 将 eax 存回 [ebx]从进程视角看,发生了以下内存访问:
- 取指:从地址 128、132、135 获取指令。
- 数据访问:从地址 15KB 加载数据,处理后再存回 15KB。
介入(Interposition)的价值介入是指在接口之间增加一层处理逻辑。在虚拟内存中,硬件介入每次内存访问,将虚拟地址透明地转换为物理地址。这种透明性使得程序无需修改即可运行在不同的物理位置。

重定位挑战
虽然程序认为自己的地址空间从 0 开始到 16KB 结束,但操作系统可能将其放置在物理内存的任何位置(例如从 32KB 开始)。
核心挑战如何在对进程透明的前提下,在物理内存中重定位进程,并维持“地址空间从 0 开始”的假象?
动态重定位(基址加界限)
基址加界限(Base and Bound) 机制,又称 动态重定位(Dynamic Relocation),通过硬件寄存器实现地址转换:
地址转换公式
每次内存引用时,硬件按如下方式转换地址:
物理地址 = 虚拟地址 + 基址访问保护
硬件在转换前(或转换后)会检查:0 <= 虚拟地址 < 界限。若超出范围,CPU 将触发异常并终止进程。
静态重定位(软件实现)
在硬件支持前,曾使用 静态重定位:由加载程序(Loader)在运行时修改指令中的地址。
- 缺点:缺乏访问保护(进程可越界访问);一旦加载,难以在内存中移动。
转换示例
设进程地址空间为 4KB,加载到物理地址 16KB 处(基址=16KB,界限=4KB):
| 虚拟地址 | 物理地址 | 说明 | |
|---|---|---|---|
| 0 | 16KB | 基址 + 0 | |
| 1KB | 17KB | 基址 + 1024 | |
| 3000 | 19384 | 基址 + 3000 | |
| 4400 | 错误(越界) | 4400 > 4KB |
硬件角色:MMU
负责地址转换的硬件单元统称为 内存管理单元(Memory Management Unit, MMU)。随着技术演进,MMU 的功能会变得更加复杂。
空闲列表(Free List)操作系统使用 空闲列表 记录当前未使用的物理内存范围,以便为新进程分配空间。
硬件支持:总结
实现地址转换需要以下硬件支持:
| 硬件要求 | 解释 |
|---|---|
| 特权模式 | 防止用户进程执行特权操作 |
| 基址/界限寄存器 | 每个 CPU 需要一对寄存器支持地址转换和界限检查 |
| 地址转换与越界检查电路 | 快速完成 物理地址 = 虚拟地址 + 基址 并检查 虚拟地址 < 界限 |
| 修改基址/界限的特权指令 | 仅允许操作系统在切换进程时设置这些值 |
| 注册异常处理程序的特权指令 | 允许操作系统告知硬件异常发生时的处理代码位置 |
| 触发异常的能力 | 当进程试图执行特权指令或越界访问内存时触发 |
MMU(内存管理单元)通过基址寄存器实现重定位,通过界限寄存器确保进程隔离。任何非法访问都会触发 CPU 异常,交由操作系统的异常处理程序(exception handler)处理(通常是终止进程)。
操作系统的问题
操作系统在以下关键时刻介入管理:
- 进程创建:在物理内存中寻找空闲槽块(通过空闲列表 free list 管理),分配空间并标记为已用。
- 进程终止:回收内存,将其放回空闲列表。
- 上下文切换:在 PCB(进程控制块)中保存和恢复基址与界限寄存器的值。
- 异常处理:提供并加载处理程序,对违规进程采取行动(如终止进程)。
下面这个表展示了硬件与操作系统在受限直接访问(LDE)框架下的交互流程:
| 操作系统 @ 启动(内核模式) | 硬件 | 程序(用户模式) |
|---|---|---|
| 初始化陷阱表 | 记住系统调用/异常处理程序地址 | |
| 启动时钟中断 | 设置时钟中断周期 | |
| 初始化进程表与空闲列表 | ||
| 操作系统 @ 运行(核心模式) | 硬件 | 程序(用户模式) |
| 启动进程 A:分配内存、设置基址/界限、从陷阱返回 | 恢复寄存器、切到用户模式、跳至 PC | |
| 进程 A 运行 | ||
| 转换虚拟地址、检查界限、执行加载/保存 | ||
| 时钟中断:切到内核模式、跳至处理程序 | ||
| 上下文切换:保存 A 的寄存器/基址/界限;恢复 B 的相应值;从陷阱返回 | ||
| 恢复 B 的寄存器、切到用户模式、跳至 PC | ||
| 进程 B 运行:执行错误加载 | ||
| 加载越界:触发陷阱、切到内核模式 | ||
| 处理异常:终止进程 B、回收内存、更新进程表 |
小结
基址加界限(动态重定位)通过硬件支持实现了高效的地址转换和内存保护,对进程完全透明。
然而,该技术存在内部碎片(internal fragmentation)问题:由于必须将整个地址空间分配在固定大小的物理槽块中,进程内部未使用的空间(如栈和堆之间的空隙)会被白白浪费。为了提高内存利用率,我们需要更复杂的机制,如接下来的分段(segmentation)。