0%

OS Virtual Memory Management

参考: 中国科学院大学 2025年秋《高级操作系统教程》课件

历史

最早期的计算机系统

  • 硬件: 物理内存容量小
  • 软件
    • 单个应用程序 + (简单)操作系统
    • 直接面对物理内存编程
    • 各自使用物理内存的一部分

多道编程时代

  • 多用户多程序: 计算机很昂贵,多人同时使用(远程连接)
  • 分时复用 CPU 资源: 保存恢复寄存器速度很快
  • 分时复用物理内存资源: 将全部内存写入磁盘开销太高
  • 多个进程同时使用、各占一部分物理内存: 没有安全性(隔离性)

IBM 360 内存隔离: Protection Key 机制

  • 内存被划分为一个个大小为 2KB 的内存块(Block)
  • 每个内存块有一个 4-bit 的 key,保存在寄存器中
  • 1MB 内存需要 256 个保存 key 的寄存器,占 256-Byte
    • 内存变大,则需要改 CPU 以增加 key 寄存器
  • 每个进程对应一个 key
    • CPU 用另一个专门的寄存器,保存当前运行进程的 key
    • 不同进程的 key 不同
  • 当一个进程访问一块内存时
    • CPU 检查进程的 key 与内存的 key 是否匹配
    • 不匹配则拒绝内存访问

缺点:

  • 不具有可扩展性,随着内存扩大,需要的 key 寄存器太多,无法实现
    • 可以将 key 放到内存中实现
  • 加载内存的定位问题: 绝对内存寻址在不同进程上的行为不同
    • 这个问题本质上来源于直接使用物理地址寻址

直接使用物理地址寻址的问题:

  • 干扰性:一个应用会因其他应用的加载而受到影响
  • 扩展性:一旦物理地址范围确定,则很难使用更大范围的内存
  • 安全性:一个应用可通过自身的内存地址,猜测其他应用的位置

硬件虚拟地址寻址机制

分段

分段机制常见于 x86 平台: 现代操作系统通常不再依赖分段

  • x86 ISA 中 GDT 和 LDT

虚拟地址

  • 虚拟地址分为:段号 + 段内地址
    • 段号:由操作系统决定
    • 段内地址:由编译器生成

虚拟地址转换

维护一个段表(由操作系统配置)
翻译时由 CPU 负责查表并转换

分段机制的问题:

  • 对物理内存连续性的要求: 物理内存也必须以段为单位进行分配
  • 存在问题:内存利用率
    • 外部碎片:段与段之间留下碎片空间
    • 内部碎片:段内部预留未使用的碎片空间

分页

虚拟地址

  • 虚拟地址空间划分成连续的、等长的虚拟页
  • 物理内存也被划分成连续的、等长的物理页
  • 虚拟页和物理页的页长相等
  • 虚拟地址分为:虚拟页号 + 页内偏移
  • 使用页表记录虚拟页号到物理页号的映射

不同 ISA 的页表基地址:

  • RISC-V: satp 寄存器
  • x86-64: CR3 寄存器
  • AARCH64(Arm): 两个寄存器:TTBR0_EL1 & TTBR1_EL1
    • 根据虚拟地址第63位选择
    • 以Linux为例
      • 应用程序(地址首位为0):使用 TTBR0_EL1
      • 操作系统(地址首位为1):使用 TTBR1_EL1
    • 系统调用中不需要切换页表
      • RISC-V 和 x86 都使用单个页表基地址寄存器,在系统调用过程需要切换页表基地址寄存器
      • 将内核映射到虚拟地址高位可以避免 TLB flush

分页机制的特点

  • 物理内存离散分配
    • 任意虚拟页可以映射到任意物理页
    • 大大降低对物理内存连续性的要求
  • 主存资源易于管理,利用率更高
    • 按照固定页大小分配物理内存
    • 能大大降低外部碎片和内部碎片
  • 被现代处理器和操作系统广泛采用

TLB 优化

降低 TLB flush 开销

新的硬件特性 ASID(RISC-V): Address Space ID

  • OS 为不同进程分配8位或16位 ASID (SATP 的第 44-59 位)
  • TLB 的每一项也会缓存 ASID
  • 地址翻译时,硬件会将 TLB entry 的 ASID 与 SATP 的 ASID 对比
    • 若不匹配,则 TLB miss

使用了 ASID 之后:

  • 切换页表(即切换进程)后,不再需要刷掉 TLB,提高性能
  • 但修改页表映射后,仍需刷掉 TLB

多核场景下的 TLB 实现

  • OS 修改页表后,需要清空其它核的 TLB 吗?
    • 需要,因为一个进程可能在多个核上运行
  • OS 如何知道需要清空哪些核的 TLB?
    • 操作系统知道进程调度信息
  • OS如何清空其它核的TLB?
    • x86_64: 发送 IPI 中断某个核,通知它主动清空
    • AARCH64: 可在 local CPU 上清空其它核 TLB
      • 调用的 ARM 指令: TLBI ASIDE1IS
    • RISC-V:SFENCE.VMA
      • rs1 针对页表,指示了页表哪个虚拟地址对应的转换被修改了
      • rs2 给出了被修改页表的进程的地址空间标识符 (ASID)
      • • SBI call + IPI 实现 remote sfence

操作系统对虚拟内存的管理

OS 采用段来管理虚拟地址

  • 段内连续,段与段之间非连续
  • 合法虚拟地址段:代码、数据、堆、栈
  • 非法虚拟地址段:未映射
    一旦访问,则触发 segfault
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
+---------+
| | --------------+
| | |
+---------+ |
| Stack | |
+---------+ |
| | |
| | ----------+ |
| | | |
+---------+
| Heap | Unmapped
+---------+
| | | | |
| | --------+ | |
+---------+ | |
| RO data | | |
+---------+ | |
| | -----------+ |
+---------+ |
| code | |
+---------+ |
| | --------------+
+---------+

VMA

在 Linux 中通过 vm_area_struct(VMA)结构体管理记录进程的合法虚拟地址空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
+---------+               +--------------------- -+
| | | start: 0x7ffcfbc67000 |
| | +---| end : 0x7ffcfbc88000 | VMA0
+---------+ | | perm : R, W |--+
| Stack |<----------+ +-----------------------+ |
+---------+ |
| | +--------------------- -+ |
| | | start: 0x144f000 |<-+
| | +---| end : 0x1470000 | VMA1
+---------+ | | perm : R, W |--+
| Heap |<----------+ +-----------------------+ |
+---------+ |
| | +--------------------- -+ |
| | +---| start: 0x600000 |<-+
+---------+ | | end : 0x601000 | VMA2
| RO data |<----------+ | perm : R |--+
+---------+ +-----------------------+ |
| | |
+---------+ +--------------------- -+ |
| code |<----------+ | start: 0x400000 |<-+
+---------+ | | end : 0x401000 | VMA3
| | +---| perm : R, X |
+---------+ +-----------------------+

VMA 的添加:

  1. OS 在创建应用程序时分配
    • 数据(对应 ELF 段)
    • 代码(对应 ELF 段)
    • 栈(初始无内容)
  2. 应用程序主动向 OS 发出请求
    • brk()(扩大、缩小堆区域)
      • 可选策略: OS 也可以在创建应用时分配初始的堆 VMA
    • mmap()
      • 申请空的虚拟内存区域 (匿名映射)
      • 申请映射文件数据的虚拟内存区域

VMA 和作用:便于操作系统更快速高效的完成地址的检查(使用页表则慢的多)

  • 不落在 VMA 区域的访存,则为非法

物理页面的分配

操作系统何时分配物理页:

  1. 立即映射:每个虚拟页都对应了一个物理内存页
  2. 延迟映射(延迟映射/按需调页, DEMAND PAGING):有些虚拟页不对应任何物理内存页
    • 对应的数据在磁盘上
    • 没有对应的数据(初始化为0)
    • 通过缺页异常处理函数映射
    • 优势:节约内存资源
    • 劣势:缺页异常导致访问延迟增加
    • 权衡优化(预取 Prefault): 每次 page fault 载入连续的多个页,减少 fault 次数

OS 内存管理中的其他机制

  1. 共享内存: 多个进程共享一段物理内存
  2. 写时拷贝(copy-on-write): 共享读,写触发 page fault 处理时拷贝
  3. 内存去重(memory deduplication)
    • 基于 copy-on-write 在内存中扫描发现具有相同内容的物理页面时执行去重
    • 操作系统发起,对用户态透明
    • 例:Linux KSM, kernel same-page merging
  4. 内存压缩
    • 当内存资源不充足的时候, 选择将一些“最近不太会使用”的内存页进行数据压缩,从而释放出空闲内存
    • Windows 10
      • 压缩后的数据仍然存放在内存中
      • 当访问被压缩的数据时,操作系统将其解压即可
    • Linux
      • zswap:换页过程中磁盘的缓存
      • 将准备换出的数据压缩并先写入 zswap 区域 (内存)
      • 好处:减少甚至避免磁盘I/O;增加设备寿命
  5. 大页的使用
    • 好处
      • 减少TLB缓存项的使用,提高 TLB 命中率
      • 减少页表的级数,提升遍历页表的效率
    • 案例
      • 提供API允许应用程序进行显示的大页分配
      • 透明大页(Transparent Huge Pages) 机制
    • 弊端
      • 未使用整个大页而造成物理内存资源浪费
      • 增加管理内存的复杂度
    • 不同 ISA 对页面大小的支持不同
      • RISC-V: 1G, 2M, 4K
      • x86: 4K
      • AARCH64
        • TCR_EL1 可以配置 3 种:4K, 16K, 64K
        • 4K + 大页:2M/1G
        • 16K + 大页:32M
        • 64K + 大页:512M