第 15 章 机制:地址转换

在实现内存虚拟化时,我们追求与 CPU 虚拟化类似的 受限直接访问(LDE 策略,旨在平衡 高效控制灵活性

  • 高效:利用硬件支持(如寄存器、TLB、页表)实现快速地址转换。
  • 控制:操作系统确保进程只能访问自身的地址空间,实现内存保护。
  • 灵活性:允许程序以任意方式访问其地址空间,简化编程模型。
核心问题

如何高效、灵活地虚拟化内存,同时保持对应用程序内存访问的严密控制?

基于硬件的地址转换

地址转换(Address Translation) 是实现内存虚拟化的通用技术,是对 LDE 的补充:

  • 硬件职责:在每次内存引用(取指、读写数据)时,自动将指令中的 虚拟地址(Virtual Address) 转换为 物理地址(Physical Address)
  • OS 职责:在关键点介入,管理内存分配(记录空闲与占用),并正确配置硬件以执行转换。

通过软硬结合,系统为每个程序创造了拥有“私有内存”的假象,而底层则是多个程序在安全、高效地共享物理内存。

初始假设

为了简化讨论,我们先对地址空间做如下假设(后续会逐步放宽):

  1. 连续性:进程的地址空间必须连续地存放在物理内存中。
  2. 大小受限:地址空间小于物理内存总量。
  3. 固定大小:每个进程的地址空间大小完全相同。

实例解析

设想一段简单的 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),通过硬件寄存器实现地址转换:

  • 基址寄存器(Base Register):存储进程在物理内存中的起始地址。
  • 界限寄存器(Bound/Limit Register):存储地址空间的大小,用于访问保护。

地址转换公式

每次内存引用时,硬件按如下方式转换地址:

物理地址 = 虚拟地址 + 基址

访问保护

硬件在转换前(或转换后)会检查:0 <= 虚拟地址 < 界限。若超出范围,CPU 将触发异常并终止进程。

静态重定位(软件实现)

在硬件支持前,曾使用 静态重定位:由加载程序(Loader)在运行时修改指令中的地址。

  • 缺点:缺乏访问保护(进程可越界访问);一旦加载,难以在内存中移动。

转换示例

设进程地址空间为 4KB,加载到物理地址 16KB 处(基址=16KB,界限=4KB):

虚拟地址物理地址说明
016KB基址 + 0
1KB17KB基址 + 1024
300019384基址 + 3000
4400错误(越界)4400 > 4KB

硬件角色:MMU

负责地址转换的硬件单元统称为 内存管理单元(Memory Management Unit, MMU)。随着技术演进,MMU 的功能会变得更加复杂。

空闲列表(Free List)

操作系统使用 空闲列表 记录当前未使用的物理内存范围,以便为新进程分配空间。

硬件支持:总结

实现地址转换需要以下硬件支持:

硬件要求解释
特权模式防止用户进程执行特权操作
基址/界限寄存器每个 CPU 需要一对寄存器支持地址转换和界限检查
地址转换与越界检查电路快速完成 物理地址 = 虚拟地址 + 基址 并检查 虚拟地址 < 界限
修改基址/界限的特权指令仅允许操作系统在切换进程时设置这些值
注册异常处理程序的特权指令允许操作系统告知硬件异常发生时的处理代码位置
触发异常的能力当进程试图执行特权指令或越界访问内存时触发

MMU(内存管理单元)通过基址寄存器实现重定位,通过界限寄存器确保进程隔离。任何非法访问都会触发 CPU 异常,交由操作系统的异常处理程序(exception handler)处理(通常是终止进程)。

操作系统的问题

操作系统在以下关键时刻介入管理:

  1. 进程创建:在物理内存中寻找空闲槽块(通过空闲列表 free list 管理),分配空间并标记为已用。
  2. 进程终止:回收内存,将其放回空闲列表。
  3. 上下文切换:在 PCB(进程控制块)中保存和恢复基址与界限寄存器的值。
  4. 异常处理:提供并加载处理程序,对违规进程采取行动(如终止进程)。
操作系统的要求解释
内存管理为新进程分配内存,从终止进程回收内存(空闲列表)
基址/界限管理上下文切换时正确设置寄存器
异常处理当异常发生时采取行动(如终止犯错进程)

下面这个表展示了硬件与操作系统在受限直接访问(LDE)框架下的交互流程:

操作系统 @ 启动(内核模式)硬件程序(用户模式)
初始化陷阱表记住系统调用/异常处理程序地址
启动时钟中断设置时钟中断周期
初始化进程表与空闲列表
操作系统 @ 运行(核心模式)硬件程序(用户模式)
启动进程 A:分配内存、设置基址/界限、从陷阱返回恢复寄存器、切到用户模式、跳至 PC
进程 A 运行
转换虚拟地址、检查界限、执行加载/保存
时钟中断:切到内核模式、跳至处理程序
上下文切换:保存 A 的寄存器/基址/界限;恢复 B 的相应值;从陷阱返回
恢复 B 的寄存器、切到用户模式、跳至 PC
进程 B 运行:执行错误加载
加载越界:触发陷阱、切到内核模式
处理异常:终止进程 B、回收内存、更新进程表

小结

基址加界限(动态重定位)通过硬件支持实现了高效的地址转换和内存保护,对进程完全透明。

然而,该技术存在内部碎片(internal fragmentation)问题:由于必须将整个地址空间分配在固定大小的物理槽块中,进程内部未使用的空间(如栈和堆之间的空隙)会被白白浪费。为了提高内存利用率,我们需要更复杂的机制,如接下来的分段(segmentation)