Skip to content

虚拟存储器

2026 大纲 三(七)虚拟存储器(基本概念;页式——页表、地址转换、TLB;段式与段页式的基本原理)。页式要求掌握到能完成地址转换,段式与段页式知道结构与转换步骤即可。

程序写死的那个地址,其实一个字节都没落在主存上

写程序时你会用到形形色色的地址——数组首址、函数入口、栈顶。这些地址在编译时就定下来了,可是编译器怎么知道这个程序运行时会被装到主存的哪个位置?如果同时跑两个程序,它们的地址岂不是要撞车?更别说程序本身可能比整个主存还大。

虚拟存储器的解法是把这三个问题一起打包:程序一律使用虚拟地址,硬件在每次访存时把它翻译成物理地址。于是编译器不必关心装到哪里、进程之间自然隔离、程序也可以比主存大(用不到的部分留在磁盘上)。

这套机制里有两条贯穿全篇的规则,先立住:

🔴 翻译只换高位那一段。虚页号换成实页号,页内地址原样保留——位数不变、取值也不变。由此直接推出:实页号的位数由物理地址位数决定,与虚拟地址位数无关

🔴 顺序是先翻译、后访存。 MMU 翻译期间可能触发 TLB 缺失、缺页、访问越权;拿到物理地址之后 Cache 才开始查找。所以"Cache 缺失"不属于 MMU 在地址转换过程中检测的事件。

⚠️ 还要提醒一句:虚拟地址位数与物理地址位数相互独立。32 位虚拟地址配 24 位物理地址完全正常(虚页号 20 位而实页号只有 12 位),也有物理地址更宽的系统。做题时两个位数分别读题给的条件,不要拿一个去推另一个。

地址合成是位串拼接物理地址=实页号页内地址,等价的算术写法是 实页号×页大小+页内地址

交互可视化

加载可视化中...

一、与 Cache 的类比

主存相当于磁盘的 Cache——同一种"用小而快的存储器缓存大而慢的存储器"的思想,只是层次不同,对照着记效率最高:

Cache ↔ 主存主存 ↔ 磁盘(虚存)
传输单位块(行)
未命中叫什么缺失(miss)缺页
未命中代价几十~上百个时钟周期105 个时钟周期
由谁处理未命中硬件自动完成操作系统介入处理
写策略写回或写直达一律写回
映射方式直接/组相联/全相联相当于全相联

二、页式虚拟存储器

虚拟地址空间和物理地址空间都按固定大小切成页,都是"高位段 + 页内地址"两段:虚拟地址 = 虚页号 VPN 页内地址,物理地址 = 实页号(页框号) 页内地址。页大小 2k 字节时页内地址就是 k 位——32 位虚拟地址、4 KB 页时,页内地址 = VA[11:0]、虚页号 = VA[31:12]

页表项

图 7.40 主存中的页表示例

图 7.40 主存中的页表示例(袁春风《计算机组成与系统结构(第 3 版)》p256)

字段(别名)作用
存放位置(实页号 / 页框号)装入位为 1 时指向主存页框;为 0 时给出磁盘上的位置
装入位(有效位、存在位)该页是否已调入主存
修改位(脏位)该页是否被写过,换出时据此决定要不要写回磁盘
使用位(访问位、替换控制位)配合替换策略(如 LRU、FIFO)
访问权限位(保护位)可读 / 可写 / 可执行
禁止缓存位该页是否允许装入 Cache,用于保证数据一致性

地址转换与缺页处理

图 7.42 分页式虚存的地址转换

图 7.42 分页式虚存的地址转换(袁春风《计算机组成与系统结构(第 3 版)》p259)

转换四步:页表基址寄存器给出页表首址 → 以虚页号为下标定位到对应页表项 → 检查装入位,为 1 则取出实页号 → 实页号与页内地址拼接成物理地址。这一整套由 MMU(存储管理部件)完成,TLB 是它的一部分。

三项检查各归谁,是判断题反复取材的地方:装入位 =0 由 MMU 触发缺页异常访问权限不符由 MMU 触发访问越权异常;而 Cache 是否命中由 Cache 控制器判断,在 MMU 交出物理地址之后才登场——它不在地址转换过程之内。

装入位为 0 时,操作系统的缺页处理走五步:

  1. MMU 发现装入位为 0,触发缺页异常
  2. 缺页处理程序按存放位置字段从磁盘读出该页
  3. 找一个空闲页框存放;若无空闲页框,则选一页换出(根据修改位决定是否写回磁盘
  4. 更新页表
  5. 返回发生缺页的那条指令,重新执行它

🔴 第 5 步返回的是发生缺页的那条指令本身,不是它的下一条。 这是缺页处理最常被问反的一点。原因在于缺页属于故障(fault)类异常——故障的返回点就是当前指令,因为这条指令还没有做完。

第 5 步还有一个可计数的后果:由于该指令要重新执行,同一次访存会把地址转换的过程走两遍——第一遍触发缺页、第二遍才成功。统计访存次数或 TLB 查询次数时,这一遍最容易被漏掉。

顺带把"缺页为什么用异常机制而 Cache 缺失不用"说清:Cache 缺失由硬件自动完成、对软件完全透明;缺页却必须由操作系统介入(访问磁盘、更新页表、可能要选择换出页面),因而必须以异常的形式陷入内核。对照 异常和中断处理——缺页是由当前指令的访存动作引发的同步事件,习惯上称"缺页中断",但按定义它是异常不是中断。

虚页的三种状态(想弄清"未缓存"与"未分配"差在哪时展开)
  • 已缓存页:装入位 1,存放位置字段是主存页框号;
  • 未缓存页:装入位 0,存放位置字段是磁盘地址——已经分配了空间,只是尚未调入;
  • 未分配页:装入位 0,存放位置字段为空——这个虚页根本还没有被分配过。

后两者的装入位都是 0,但缺页处理的结果不同:未缓存页从磁盘调入即可,访问未分配页则是真正的非法访问。

页表为什么大到必须分级:一张页表的规模算给你看,以及两级页表的两跳与代价(想弄清"多级页表省的到底是什么"时展开)

页表的项数由虚拟地址空间大小决定。以 32 位虚拟地址、4 KB 页为例:

页表项数=232212=220,每项 4 B页表大小=4 MB

而这是每个进程都要有一张的,全部常驻主存显然不合适。

两级页表把虚页号再切成两段:

[ 页目录索引一级  |  页表索引二级  |  页内地址不变 ]

转换要走两跳:页目录基址寄存器 → 页目录项(给出二级页表的基址)→ 页表项(给出实页号)。

代价是访存次数增加:TLB 缺失时,两级页表要访问 2 次主存才能拿到实页号,再加 1 次取数据。收益是二级页表可以按需建立,没有用到的地址区间不必为它分配页表。64 位系统通常采用四级页表。

三、TLB(快表)

页表在主存中,这意味着一次访存要变成两次:先查页表、再取数据。解决办法与 Cache 完全相同——把最近用过的页表项缓存起来,这个缓存就是 TLB(Translation Lookaside Buffer,快表):TLB 之于页表,正如 Cache 之于主存

位置也要记准:页表在主存里、每个进程一张,首址记在页表基址寄存器TLB 在 CPU 内部、用 SRAM 实现、常采用全相联或路数较少的组相联。

采用组相联时字段切分与 Cache 同构,只是被切的对象是虚页号

VPN=TLB 标记高位  TLB 组号log2(TLB 组数) ,TLB 组数=TLB 项数路数

🔴 组号取自虚页号的低位,不是虚拟地址的低位。 虚拟地址的低位是页内地址,它根本不参与 TLB 查找。切错这一步,标记位数会连带全错。

举一组数走一遍:虚拟地址 30 位、页大小 4 KB、TLB 共 16 项 2 路组相联。页内地址 = VA[11:0],虚页号 = VA[29:12] 共 18 位;TLB 组数 =16÷2=8,故组号 3 位 = VA[14:12];TLB 标记 =183=15= VA[29:15]。注意组号取的是虚页号的低 3 位(即 VA[14:12])而不是 VA[2:0]

一个 TLB 项的构成是 标记 + 实页号 + 控制位(有效位、修改位、权限位等)。这里有个位数来源的分工要记住:标记的宽度由虚拟地址位数决定,实页号的宽度由物理地址位数决定。所以题目问"把虚拟地址位数加宽几位,TLB 项增加几位"时,增加的全部落在标记字段上,实页号一位不动。

TLB 装满后同样需要替换,常用 LRU,走查手法与 Cache 的替换算法 完全一样——命中时也要更新最近使用记录,否则替换决策会出错。

四、TLB、页表与 Cache 的联合访问

图 7.43 TLB 和 cache 的访问过程

图 7.43 TLB 和 cache 的访问过程(袁春风《计算机组成与系统结构(第 3 版)》p260)

注意页内地址那 12 位自始至终没有进入 TLB,它直接旁路到物理地址的低位。

三者各有命中与否,共 8 种组合,其中 3 种在逻辑上不可能出现。判断靠两条判据:

🔴 判据一:TLB 命中 该页必在主存。 TLB 项是页表项的副本,能命中说明它曾经有效,所以"TLB 命中而页表缺失"自相矛盾。这条成立的前提是换出某页时必须同步让对应的 TLB 项失效——反过来用就是:页表里 P =0 的页,同步之后其 TLB 项的有效位必须为 0。

🔴 判据二:页不在主存 Cache 不可能命中。 Cache 缓存的是主存的内容,主存里都没有的东西,Cache 里不可能有。

TLB页表Cache可能说明
命中命中命中最快路径
命中命中缺失需访问主存取数据
命中缺失命中违反判据一
命中缺失缺失违反判据一
缺失命中命中查页表得地址,数据恰在 Cache 中
缺失命中缺失查页表并访问主存
缺失缺失命中违反判据二
缺失缺失缺失缺页,最慢路径
虚实地址变换完整走一遍:从十六进制切字段到查表拼接与算术验算(想核对自己切字段与进制换算这两步时展开)

先把整条路径串一遍:查 TLB,命中即得实页号;缺失则查页表,装入位为 1 则得实页号并回填 TLB、为 0 则触发缺页异常交 OS 调页后重执行。拿到物理地址后才轮到 Cache——命中直接返回数据,缺失则访问主存并装入 Cache。

设定:某系统虚拟地址 32 位、物理地址 24 位、页大小 4 KB。页表部分内容如下(装入位为 1 才有效)。求虚拟地址 0008 2840H 对应的物理地址。

虚页号(十进制)装入位实页号
12810x00A
1290
13010x018
13110x0C3

第一步,定各字段位数。 页大小 4 KB 页内地址 log24096=12 位;虚页号 =3212=20= VA[31:12];实页号 =2412=12 位(由物理地址位数决定)。

第二步,切出字段的值。 十六进制下每位对应 4 个二进制位,低 3 位十六进制正好是 12 位页内地址:

0008 2840H虚页号=0x00082,页内地址=0x840

第三步,查页表。 页表的虚页号列按十进制编号,需先换算 0x82=8×16+2=130;查第 130 项,装入位 =1,实页号 = 0x018

第四步,拼接。

PA=0x01812 位实页号  0x84012 位页内地址=0x018840

算术验算:0x018=2424×4096+0x840=98304+2112=100416=0x18840

五、段式与段页式

页式按固定大小等分地址空间,段式则按程序的逻辑结构划分成长度不等的段(代码段、数据段、栈段),虚拟地址因而是二维的:段号 + 段内地址。转换过程:段表基址寄存器 → 加段号定位到段表项 → 取出段首址 → 与段内地址相加得物理地址。

图 7.45 分段式虚存的地址转换

图 7.45 分段式虚存的地址转换(袁春风《计算机组成与系统结构(第 3 版)》p263)

段页式先分段、段内再分页,虚拟地址切成三段(段号 + 段内页号 + 页内地址):段号查段表取出该段页表的基址,段内页号查该段页表取出实页号,再与页内地址拼接。它兼有两者的性质——段对程序员可见(保留逻辑划分与共享保护),页对程序员透明(消除外碎片)。

页式段式段页式
划分依据等长,硬件决定变长,程序逻辑决定先分段,段内分页
虚拟地址切几段223
表项关键字段实页号段首址 + 段长 + 装入位段表存页表基址;页表存实页号
地址合成方式拼接相加,需越界检查拼接
无 TLB 时访存次数223
对程序员透明不透明段不透明、页透明
碎片内碎片外碎片内碎片

这张表里最值得抓的两条差别:

🔴 段式的实质增量是"段长"。 段是变长的,所以必须检查「段内地址 < 段长」;页式不需要这项检查——页内地址的位数天然把取值范围限死了。也正因为段首址不必对齐到 2 的幂,段式合成地址靠相加而不是拼接。

🔴 段页式不能只查一次表。 段表项里存的是该段页表的基址(不是段的基址),拿到之后还得再查一次页表才得到实页号。所以无 TLB 时需要 3 次主存访问:查段表、查页表、取数据。

段的共享与保护、外碎片的紧凑、页面置换算法属于操作系统的内容,本节不展开。

考点速记

  1. 主存之于磁盘正如 Cache 之于主存,但缺页由操作系统处理、Cache 缺失由硬件处理;虚存相当于全相联一律写回,两者都源于缺页代价极高(约 105 个时钟周期)。
  2. 地址转换只换高位段实页号位数由物理地址位数决定、TLB 标记位数由虚拟地址位数决定;顺序固定为先翻译后访存(Cache 命中与否不在 MMU 的检测范围内);缺页是同步异常中的故障,处理完重新执行原指令
  3. TLB 是页表的 Cache,组相联时切的是虚页号;两条判据(TLB 命中 页必在主存页不在主存 Cache 不可能命中)决定 8 种组合里有 3 种不可能。段式多出段长越界检查、地址靠相加;段页式无 TLB 时访存 3 次。

这一节在真题里被考过的形式(下方「真题练习」逐题对应):

  • 问 TLB / Cache / Page 的命中组合中哪一种不可能发生:用两条判据筛。"TLB 命中而页缺失"违反判据一,"页缺失而 Cache 命中"违反判据二。
  • 给页表内容与一个虚拟地址,问虚实地址变换的结果:切字段(十六进制低几位正好是页内地址)→ 虚页号换算成页表的编号进制 → 查装入位 → 装入位为 1 则拼接、为 0 则答缺页。⚠️ 页表行号常按十进制编号而虚页号切出来是十六进制,换算这一步最容易出错。
  • 给 TLB 项数与路数,问 TLB 标记字段至少多少位:虚页号 = 虚拟地址位数 页内偏移位数;组数 = 项数 ÷ 路数;标记 = 虚页号位数 log2 组数。全相联时标记就等于完整的虚页号。
  • 问把虚拟地址加宽几位后 TLB 项增加几位:只增加在标记字段上,实页号由物理地址位数决定、一位不动。
  • 给页表某项 P =0,问哪个 TLB 表项不可能出现在某组里:先用"虚页号 mod 组数 = 组号"反推每个选项的虚页号,再用判据一——页表里 P =0 的那一页,其 TLB 项的有效位必须为 0,出现 V =1 的即为不可能。
  • 挑关于缺页处理的错误叙述:常设的错点是"缺页处理完成后回到发生缺页的下一条指令执行"——应当重新执行该条指令本身
  • 问哪个事件不是在 MMU 地址转换过程中检测的:答 Cache 缺失。越权、缺页、TLB 缺失都在转换过程中,Cache 是拿到物理地址之后才查的。
  • 大题里给一串虚页号问哪个 TLB 表项被替换:手法同 Cache 的 LRU 走查,先按 mod 组数分组,只推被访问次数超过路数的那一组,命中也要更新 LRU 顺序

易错:缺页处理后回到"下一条指令"。故障类异常的返回点是当前指令

易错:拿虚拟地址的低位当 TLB 组号。要取虚页号的低位。

易错:用虚拟地址位数去推实页号位数。实页号由物理地址位数决定,两者相互独立。

易错:把 Cache 缺失算进 MMU 的检测范围。它在地址翻译完成之后才发生。

易错:段页式答成查两次表。段表给的是页表基址,无 TLB 时要访存 3 次。

教材出处
  • 袁春风《计算机组成与系统结构(第 3 版)》§7.6 虚拟存储器:页表项各字段与三种虚页状态、图 7.40(p256)、页表过大问题与多级页表(p257)、图 7.42 分页式地址转换(p259)、图 7.43 TLB 与 cache 的访问过程(p260)、图 7.45 分段式地址转换(p263)

相关知识

异常和中断处理存储器的层次结构Cache 的基本工作原理

真题练习