虚拟存储

虚拟存储器分页机制、TLB快表与多级页表转换

Posted by CloudingYu on May 6, 2026

一、课程引入与行业背景

1.1 存储墙与功耗墙

当前计算机系统面临的两大瓶颈:

存储墙(Memory Wall):计算并行化已经相对成熟(流水线、乱序执行等),但数据传输带宽始终是瓶颈。即使英伟达 GPU 之间通过 NVLink 连接可达约 90 GB/s,服务器之间通过 InfiniBand 也只有约 5 GB/s,面对当前数据吞吐需求仍然不够。GPU 上的 HBM(High Bandwidth Memory)带宽已经非常高,但对 AI 训练和推理来说仍然存在瓶颈。

计算很重要,但在存储面前就是小弟。以后永远记得这两件事——存储墙和功耗墙。

功耗墙(Power Wall):数据中心需要消耗大量电力并进行散热。设计时需要在性能和功耗之间取得平衡:如果达到 80% 的性能但能将功耗降低一半,往往是值得的。CPU 可以主动降速运行甚至休眠来降低功耗。

1.2 KV Cache 与存储优化的现实意义

DeepSeek V4 等大模型如何降低成本——大量依赖 KV Cache(键值缓存),本质上是如何实现高质量的数据缓存。知道 100 个优化并行的方法,但关键是知道哪个方法能降低 memory 的需求,降低对数据带宽的要求。


二、虚拟存储器的动机

2.1 从实地址到虚拟地址

早期计算机使用实地址(物理地址)方式:程序员直接操作物理内存,知道每一个代码和数据的位置。优点:简单直接。缺点:资源利用率极低,程序无法在不同机器间移植(换台电脑,物理地址就变了)。

2.2 分页的引入

为了解决物理地址的局限性,引入分页机制:

  • 页框(Page Frame):将物理内存分成固定大小的块(通常 4KB)。
  • (Page):将进程的逻辑空间也分成同样大小的块。
  • 页框不需要连续分配,操作系统通过页表(Page Table)记录逻辑页到物理页框的映射。

就像人民币有 100、50、20、10 的面额一样,按固定大小分块给你,你要多少给多少个块。

2.3 逻辑地址 vs 物理地址

  • 逻辑地址(Virtual Address):程序看到的地址空间。32 位系统中,每个进程都以为自己拥有 0 到 4GB 的完整地址空间。
  • 物理地址(Physical Address):实际的内存地址,通常远小于逻辑地址空间。

程序以为自己在一片很宽阔的原野上奔跑——”心有多大世界就有多大”,但实际上可能住在一个很狭窄的小房间里。

2.4 按需分页(Demand Paging)

程序虽然可能很大,但不会同时使用所有代码和数据。用到哪个页就把哪个页调入内存,不用的放在硬盘上。这就是按需分页(Demand Paging)方式。


三、虚拟化的代价与云服务

3.1 虚拟化的性能开销

虚拟存储机制的好处是程序员不用关心物理内存布局,但缺点是地址转换需要大量开销。操作系统使应用程序的执行时间增加了大约 25%

3.2 云服务中的超卖问题

  • CPU 超卖容易:通过分时复用,一个 CPU 核可以卖给多个用户。虚拟化带来约 10%-15% 的性能损耗,但超卖后成本回收。
  • 内存超卖困难:内存不能像 CPU 那样简单地分时复用。一旦超卖导致内存不足就会崩溃。这就是为什么云服务中增加内存的价格涨幅远高于增加核数。

3.3 大厂为何自建 GPU 数据中心

AI 大厂追求极致性能,不愿意接受虚拟化带来的 10%-15% 性能损耗。GPU 一旦开始训练就满载运行,无法有效复用。因此大厂往往不使用云服务的虚拟化,而是自建裸金属 GPU 集群。

想做所有虚拟的时候,方便了程序员,但一定有性能消耗。


四、页表结构与地址转换

4.1 页表的基本结构

每个页表项(Page Table Entry, PTE)包含:

字段 含义
Valid(有效位) 该页是否已装入内存
Dirty(脏位) 该页是否被修改过
访问权限(R/W/X) 读/写/执行权限控制
禁止缓存位 用于 memory-mapped I/O,数据必须直接写出到外设
替换控制位 用于页面替换策略
物理页号(PPN) 对应的物理页框编号

禁止缓存位的作用:如果 memory 映射的是显示器等外设,不能用 write-back 策略一直不往外写——屏幕上一动不动,内心已经闪过无数场景了,那也不行。

4.2 虚拟地址到物理地址的转换过程

  1. 从虚拟地址中提取 VPN(Virtual Page Number)和 VPO(Virtual Page Offset)。
  2. 用 VPN 查页表,找到对应的 PTE。
  3. 检查 Valid 位:是否有效?
  4. 检查访问权限:是否允许当前操作(读/写/执行)?
  5. 提取 PPN(Physical Page Number),拼接上 VPO 得到物理地址。

4.3 页面映射方式

虚拟存储器采用全相联映射:因为缺页(page fault)的代价极高(需要访问磁盘,等待数百万个时钟周期),所以必须尽量减少 miss。全相联映射允许任何虚拟页映射到任何物理页框,冲突最少。替换策略通常使用 LRU,写策略使用 Write-Back(减少对磁盘的访问)。


五、缺页处理

5.1 缺页的后果

当访问的页不在物理内存中时发生缺页(Page Fault):

  1. 该指令被阻塞,当前进程被挂起
  2. 操作系统进行进程切换,让其他进程继续执行。
  3. 从硬盘(或 SSD)将所需页面调入内存。
  4. 数据准备好后通过中断通知 CPU。
  5. 恢复被挂起的进程继续执行。

缺页代价极高——一旦缺页,中断处理要做好多事情。所以配电脑的时候内存要配大——内存大了就不容易缺页。

5.2 SSD vs HDD 的缺页代价

  • HDD:缺页等待数百万个时钟周期。
  • SSD:速度提高约 10 倍,缺页等待数十万个时钟周期。

六、TLB(快表)

6.1 TLB 的动机

页表存储在主存中,每次地址转换都要访问主存查页表,这太慢了。解决方案:将页表中常用的条目缓存到一个小型高速 Cache 中。这个 Cache 就是 TLB(Translation Lookaside Buffer),中文叫快表

页表既然在主存里就是数据,能不能往 Cache 里放一点?就这想法。

6.2 TLB 的结构

TLB 中每项包含:VA address tag(虚拟地址标签)、PA(对应的物理页号)、Valid 位、Dirty 位。

6.3 带 TLB 的地址转换流程

  1. CPU 发出虚拟地址,先查 TLB。
  2. TLB Hit:直接得到物理地址,然后访问 Cache/主存获取数据。
  3. TLB Miss:去主存中的页表查找。
    • 页表中有(Page Table Hit):得到物理地址,更新 TLB,访问数据。
    • 页表中没有(Page Table Miss / 缺页):触发缺页处理流程。

6.4 各种 Hit/Miss 组合分析

TLB Page Table Cache 情况说明
Hit - Hit 最佳情况:不需要访问主存
Hit - Miss TLB 命中但数据不在 Cache,访问主存取数据
Miss Hit Hit TLB 未命中,查页表后数据在 Cache 中
Miss Hit Miss TLB 未命中,查页表后数据在主存中
Miss Miss - 最坏情况:缺页,需要从硬盘调入

不可能的组合

  1. TLB Hit + Page Table Miss:TLB 是页表的子集,不可能 TLB 有而页表没有。
  2. Page Table Miss + Cache Hit:页表都没记录的页,不可能已经在 Cache 中。

6.5 TLB 相关操作的权限

TLB 的 flush(刷新)指令和 Cache 的 flush 指令都是操作系统使用的特权指令。用户不能随便调 flush 指令,这就是为什么指令要分级。


七、多级页表

7.1 为什么需要多级页表

如果地址空间很大(如 32 位系统有 $2^{20}$ 个页面),一张平坦的页表需要上百万个条目,占用大量内存。

7.2 IA-32 的二级页表

  • 虚拟地址 32 位 = 10 位页目录索引 + 10 位页表索引 + 12 位页内偏移。
  • 页目录:1K 个目录项,指向 1K 个子页表。
  • 子页表:每个有 1K 个页表项。
  • 每页 4KB。

优势:只需要维护 2K 个表项(1K 目录项 + 当前使用的 1K 页表项),而非一个 1M 条目的巨大平坦表。

7.3 Intel Core i7 的四级页表

  • 线性地址划分为四级,每级 512 个条目(9 位索引)。
  • 每级页表只有 512 项,每项十几个字节,整张表非常小。
  • 相比不分级的方案(需要维护巨大的单张页表),四级页表显著降低内存占用。

如果不通过分级的方式,就会变成一个超级大的页表。分级以后每个表都很小。


八、段式、页式与段页式管理

8.1 三种管理方式

特性 分页 分段 段页式
对用户 透明(不可见) 可见 段可见,页透明
块大小 固定(如4KB) 可变 段内再分页
碎片问题 内部碎片小 外部碎片(长短段替换) 综合
内存分配 可以分散 需要连续 段内页可分散
磁盘通信 页为单位 段为单位 页为单位

8.2 段页式管理(Segmented Paging)

现代操作系统(Linux、Windows)使用段页式混合管理:顶层使用段描述(代码段、数据段、栈、堆等),段内再分页管理。通过段号找到段表,段表中指向对应的页表,再通过页表完成地址转换。

8.3 每个进程都有自己的页表

每一个进程都有自己这一套页表。进程很多的时候,光页表就要占很多空间。


九、存储保护机制

9.1 访问越界保护

C 语言中常见的内存越界问题(如 unsigned 的 length 为 0 时减 1 变成全 F,导致循环越界)。越界访问会触发 Access Violation(Windows)或 Segmentation Fault(Linux)。

9.2 页级权限保护

页表项中的权限位控制每个页面的访问属性:

  • Read Only:只读页面。
  • Read/Write:可读写数据页面。
  • Execute Only:代码段,不允许写入。

CTF 比赛中的 PWN(栈溢出攻击)就是通过填充数据让栈溢出,篡改控制流,让程序执行攻击者的代码。为了防止这种攻击,加了内存保护、Canary、地址随机化(ASLR)等机制。

9.3 硬件保护与软件保护协同

  • 软件保护(操作系统):设置页面属性(RO/RW/XO)、进行权限检查。
  • 硬件保护:提供管理模式(内核态)和用户模式、Ring 0-3 分级、异常/陷阱/系统调用机制。

光软件和光硬件都是不可能完成这些保护操作的,需要一起来。

9.4 Rust 对内存安全的改进

C 语言积重难返,大量代码存在内存安全问题。Rust 在编译层面做检查,消除越界访问。Python 通过解释器隔离,根本不让用户直接访问物理内存。社区有人呼吁用 Rust 重写 Linux 内核,但已有 C 内核经过长期验证,替换风险大。


十、RISC-V SV39 页表方案

10.1 SV39 概述

RISC-V 架构使用 SV39 方案:

  • 虚拟地址 39 位($2^{39}$ = 512GB 虚拟地址空间)。
  • 物理地址 56 位(PPN 44 位 + 12 位页内偏移)。
  • 页大小 4KB(Offset 12 位)。
  • 采用三级页表,每级 VPN 为 9 位(512 个条目)。

10.2 SV39 页表项字段

字段 含义
V(Valid) 有效位
R/W/X 读/写/执行权限
U 用户页面标志(User/Supervisor 模式访问控制)
G 全局映射位(对所有虚地址空间有效)
A(Accessed) 自上次清除以来是否被访问过
D(Dirty) 脏位,write-back 时需要注意
RSW 保留给操作系统使用
PPN[2:0] 物理页号,分三段对应三级页表

10.3 为什么 PPN 分三段

如果不分段,需要维护一个 $2^{44}$ 条目的巨大页表。分成三级后,每级只有 512 个条目,每张子表只有约 2K 条目,每条目几个字节。通过三级查找逐级缩小范围,最终得到完整的 56 位物理地址。