OSTEP-操作系统导论-内存虚拟化
内存虚拟化的目的是为进程创造独享大量虚拟内存的错觉,通过地址映射(Address Mapping)将进程的虚拟地址空间转换为硬件的物理地址。
1.1 虚拟地址空间的抽象
地址空间(Address Space)是程序可视的系统内存,包含其运行所需的代码和数据。每个程序运行在各自的地址空间中,实现了强隔离。用户进程使用的是虚拟地址(Virtual Address, VA),通过硬件(例如内存管理单元,Memory Management Unit, MMU)和操作系统协同实现的地址转换机制,将虚拟地址转换为物理地址(Physical Address, PA)。
物理地址结构与平台相关,通常不仅包括RAM,还包括I/O设备的内存映射和启动ROM。内核虚拟地址空间由内核设计,包含内核代码、数据、内核栈及可分配给用户程序的内存等;而用户进程的虚拟地址空间由内核提供,涵盖了用户代码、数据、栈和堆等。
在C语言中,变量直接声明使用栈内存,而堆内存则需要使用内存分配函数手动管理,如:
malloc()
:申请指定大小的堆内存,成功时返回地址,失败时返回NULL。free()
:释放已申请的内存。
上述两种内存管理的区别在于:堆内存可跨越函数调用持续存在,而栈内存会在函数返回后自动释放。此外,程序所操作的API皆基于虚拟地址。
内存分配的底层通过系统调用(以Linux为例)实现:
brk()
:调整程序数据段的结束位置,以扩展或缩小堆的大小。sbrk()
:增加或减少程序数据段的大小,返回新的数据段结束位置。
这些调用实际上是在改变堆内存结构,扩展程序可用的内存空间。此外,与内存映射I/O相关的调用有:
mmap()
:将文件或设备的部分映射到内存中。munmap()
:解除文件或设备在内存中的映射。
1.2 地址转换方法
1.2.1 地址转换策略演变
策略 | 说明 | 硬件支持 | 演进 | 缺陷 |
---|---|---|---|---|
基址+界限(Base and Limit Registers) | 整块内存分配,利用基址计算PA=VA+base,且VA需小于界限 | 需要MMU寄存器 | 简单直接 | 内存利用率低,导致碎片 |
分段(Segmentation) | 地址空间分段,使用段号及偏移量来定位 | 需要寄存器存放段信息 | 减少内部碎片 | 产生不等大小的空闲空间(外部碎片) |
分页(Paging) | 固定大小的内存页与页框映射 | 页表寄存器和地址转换单元 | 消除外部碎片 | 地址转换开销大 |
段页式(Segmented Paging) | 多段页表减少页表大小 | 所需寄存器和硬件单元 | 不依赖段分配内存 | 大稀疏堆仍有页表浪费 |
多级页表(Multilevel Page Tables) | 多级页表减少不必要空间使用 | 多级页表结构减少内存占用 | 广泛应用 | 访存操作多,增加耗时 |
1.2.2 RISC-V系统(XV6)三级页表
XV6基于RISC-V平台,定义物理地址(Physical Address, PA)为56位,虚拟地址(Virtual Address, VA)为39位,使用三级页表索引。L2页目录存有L1页表信息,而L1页表包含L0页表信息。结构类似于Unix V6,页表项包括物理页号(Physical Page Number, PPN)和权限标志位。
1.2.3 X64硬件(Linux)四级页表
在x86-64架构的Linux系统中,采用四级页表结构,包括:
- PGD(Page Global Directory):页全局目录
- PUD(Page Upper Directory):页上层目录
- PMD(Page Middle Directory):页中层目录
- PTE(Page Table Entry):页表项
地址转换过程与RISC-V平台类似,但由于x86-64架构的复杂性,页表层级更多,以提供更细粒度的内存管理。每级页表通过指针链接,逐级索引,最终获取物理地址。
1.3 缓存和页面交换
即便使用多级页表,仍然存在以下问题:
- 多次访存查找页表导致的耗时开销。
- 页表和进程占用过多内存,可能导致RAM耗尽。
通过以下存储层级方案解决:
- 采用**TLB(Translation Lookaside Buffer)**缓存未来访问的映射关系。
- 使用外部存储进行页面交换(Page Swapping),记录过多的内存页面。
1.3.1 TLB机制
TLB是一种高效缓存最近使用的页表信息的硬件装置。访存时,系统首先查找TLB,若命中(Hit),则直接使用缓存的物理地址;若未命中(Miss),则触发异常,按页表流程查找,并将新的映射关系记录到TLB中。TLB满时,替换策略决定更新内容。
1.3.2 页面交换
页面交换(Page Swapping)是将不常用的页面从内存移至磁盘的过程。当访问某页时,若该页不在内存中,则引发页错误(Page Fault),系统将该页从磁盘换入内存。在内存空间不足时,操作系统根据替换策略选择换出页面,腾出内存空间。
1.3.3 替换策略发展
策略 | 解释 | 缺点 |
---|---|---|
最优替换(Optimal Replacement) | 基于未来访问预测,逐出最长时间不使用的页面 | 无法实现,因需提前知道未来访问顺序 |
FIFO(First In, First Out) | 逐出最先进入内存的页面 | 容易出现抖动,导致频繁换页 |
随机替换(Random Replacement) | 随机选择页面替换 | 命中率随机,可能导致效率低下 |
LRU(Least Recently Used) | 逐出最少使用的页面 | 实现成本高,需维护最近使用信息 |
近似LRU(Approximate LRU) | 定期清除访问标记,模拟LRU | 参数选择影响命中率,精度较低 |
1.3.4 页错误
页错误分为以下几种情况:
- 硬错误(Hard Fault):页面在磁盘中,需要换入内存。
- 软错误(Soft Fault):页面在内存中,但未建立映射关系。
- 段错误(Segmentation Fault):访问了不在虚拟地址空间中的地址。
1.4 其他内存虚拟化机制
- 惰性分配(Lazy Allocation):延迟内存分配,直到确实需要时才进行实际分配。
- 按需清零(Zero-on-Demand):仅在写入前分配的内存区域返回已清零的内存。
- 写时复制(Copy-on-Write, CoW):
fork()
时子进程和父进程共享内存,只有在写操作发生时才复制内存页。 - 按需调页(Demand Paging):仅在需要时加载程序的代码和数据,减少不必要的内存占用。
- 预取(Prefetching):在访问一页后,提前将下一页调入内存,提高访问效率。
- 集中写入(Write Gathering):磁盘换入时,同时写回多个页面,提高I/O效率。
- 内存映射I/O(Memory-Mapped I/O):将文件或设备映射到内存,延迟到真正需要时再执行I/O操作。