虚拟存储器
虚拟存储器 是一种 计算机内存管理技术,它在 物理内存 和 磁盘存储 之间创建了一个抽象的、扩展的内存空间,以提供更大的可用内存容量。
物理内存
物理内存(Physical Memory)指的是计算机系统中的实际硬件内存,即随机存取存储器(RAM)。
物理内存 是计算机直接用于存储和操作数据的地方,所有进程的数据和代码实际上都是存储在 物理内存 上。
虚拟内存
虚拟内存(Virtual Memory)是一种计算机系统内存管理技术,它使得进程可以认为自己拥有一个 连续且独立的内存空间,即使实际上 物理内存 可能不够用或者是分散的。
进程使用 虚拟地址 来访问内存中的数据和指令,而不需要了解 物理内存 的详细情况。进程使用虚拟地址去访问 虚拟内存,
当进程访问 虚拟内存 时,操作系统会将 虚拟地址 转化为 物理地址, 进而根据物理地址去访问 物理内存。
页式虚拟存储器
为了更灵活地管理内存,操作系统采用 页式虚拟存储器 的方式,将 虚拟地址空间 和 物理地址空间 都划分为大小固定的 页(Page)。程序运行时,并不需要将整个虚拟地址空间都加载到 物理内存 中,而是按需将部分 页面 载入内存,其余的页面保存在硬盘上。
页式虚拟存储器的设计核心在于将 逻辑地址空间 与 物理内存 进行 解耦。程序在 编译和运行 时所看到的是一个连续、完整的虚拟地址空间,而实际上这些地址并不直接对应物理内存中的位置,而是通过 页表和TLB 进行映射。
当 CPU 发出一条内存访问指令时,地址转换机构 会找到对应的物理页框。如果发现该页面尚未加载到内存,就会触发 缺页中断。缺页中断交由操作系统内核处理,内核会判断该页面是否在磁盘的交换区或程序映像文件中,如果存在,就将其调入内存。如果内存已满,还需要根据 页面置换算法(如最近最少使用 LRU 或时钟算法)选择一个合适的页面换出到磁盘,再将新页面调入。整个过程对应用程序是透明的,它只会感知到一次访问延迟,而不会意识到内存与磁盘之间的交换。
这种机制不仅让 有限的物理内存 可以支持 更大规模的虚拟地址空间,还实现了多进程之间的 隔离与保护。每个进程都有自己的页表,彼此之间的虚拟地址不会直接冲突,从而避免了进程间的非法访问。同时,页式虚拟存储器还为实现内存共享提供了可能,比如多个进程可以将不同的虚拟页映射到同一个物理页框,用于共享代码段或数据。
页面划分和地址结构
在 页式虚拟存储器 中,虚拟内存空间 被划分为一个个 虚拟页面(VP,Virtual Page),物理内存空间 被划分为一个个 物理页面(PP,Physical Page)。
虚拟地址(VA, Virutal Address)被划分为 虚拟页面号(VPN,Virutal Page Number)和 页内偏移(Offset)这两个字段。
在使用 虚拟地址 去访问 虚拟内存 时,我们可以根据 虚拟页面号 找到该地址所在的 虚拟页面,在找到 虚拟页面 后,我们可以根据 页内偏移 找到该地址在 虚拟页面 内的偏移大小。
同理, 物理地址(PA, Physical Address)被划分为 物理页面号(PPN,Physical Page Number)和 页内偏移(Offset)这两个字段。
在使用 物理地址 去访问 物理内存 时,我们可以根据 物理页面号 找到该地址所在的 物理页面,在找到 物理页面 后,我们可以根据 页内偏移 找到该地址在 物理页面 内的偏移大小。
需要注意和区分一下以下几个名词,它们具有相同的含义:
虚拟页面(Virutal Page)= 逻辑页面(Logical Page)
页框(Frame) = 物理页面(Physical Page)
地址翻译机构
CPU 中的 内存管理单元(Memory Management Unit,MMU)是计算机体系结构的重要组成部分,它负责 虚拟内存 到 物理内存 的地址映射和内存访问的控制。MMU 的主要功能包括:
- 地址转换:MMU 负责将程序使用的 虚拟地址 转换为对应的 物理地址。
- 地址保护:MMU 实施内存保护策略,以确保不同的程序或进程无法越界访问彼此的内存空间。
- 内存访问权限:MMU 根据地址映射和保护位(在页表或段表中定义)来控制内存访问权限,包括读、写、执行等。
页表
页表(page table)是操作系统维护的一张表,用于将 虚拟地址 转化为 物理地址,每个运行的进程都有自己 页表。
进程的 页表 存储在其内存空间中的 内核空间 中,详见进程内存空间。
在进程执行时,MMU 使用当前活动进程的 页表 来执行地址转换。
单级页表
页表 的功能是将 虚拟页号 转换为 物理页号,进而实现地址翻译。
对于 单级页表 而言,只需访问一次 页表 即可实现 页面号 的翻译过程。
单级页表结构
一个典型的 单级页表 结构如下图所示:
页表 中的每一行叫做 页表项(PTE, page table entry),页表项 可包含如下内容:
- 虚拟页框号(VPN,Virtual Page Number):对于 单级页表 而言,VPN 并不需要实际存储在 页表 的字段中,其隐性地作为 页表项 的下标进行存储。
- 物理页框号(PPN,Physical Page Number):当前 VPN 所对应的 物理页号。
- 有效位(Valid Bit):用于指示虚拟页 是否有效。若为 1,表示可用于地址转换;若为 0,表示无效,会导致错误。
- 修改位(Dirty Bit):用于指示虚拟页的内容 是否已被修改。若为 1,可能需要写回到磁盘或其他非易失性存储介质。
- 访问位(Accessed Bit):用于指示虚拟页 是否已被访问。若为 1,对页面置换算法有帮助。
- 保护位(Protection Bits):用于指定虚拟页的 访问权限,例如读取、写入或执行权限。
- 缓存位(Caching Bits):用于指示是否允许将虚拟页的内容 缓存在高速缓存中。
需要注意的是, 页表项 的具体结构可以因不同的计算机体系结构和操作系统而异,在做题目时根据题目的具体指示进行判断。
这里主要关注 虚拟页框号、物理页框号以及 有效位 即可,这是大多数 单级页表 中都会包含的内容。
对于 页表项 中的其他位,主要管制 修改位 和 访问位,保护位 和 缓存位 不太会考察。
单级页表地址翻译
虚拟地址和物理地址格式 如下:
- 虚拟地址(Virutal Address)分为两个部分:
- 虚拟页框号(VPN, Virtual Page Number):当前地址所在的页框在 虚拟内存 对应的所有页框中的下标
- 页内偏移(VPO, Virutal Page Offset):地址在页面内的偏移
- 物理地址(Physical Address)同样分为两个部分:
- 物理页框号(PPN, Physical Page Number):当前地址所在的页框在 物理内存 对应的所有页框中的下标
- 页内偏移(PPO, Physical Page Offset):地址在页面内的偏移
其中 虚拟地址和物理地址的偏移量是相同的(VPO = PPO = offset),页面大小 = $2^{\text{offset}}$
虚拟页面个数 = $2^{\text{VPN}}$,物理页面个数 = $2^{\text{PPN}}$
单级页表地址翻译过程 如下:
PPO 和 VPO 的内容一致,所以地址翻译主要在于通过将 VPN 转化为 PPN。VPN 的值为对应的 页表项(PTE)在 页表 中的下标。找到对应的 页表项 后,判断 Valid 字段是否为 1:
- 若为 0,会发生 缺页中断
- 若为 1,读取其中的 PPN,完成地址翻译
举一个实际 例子 说明一下:
假设 虚拟内存 为 16 MB,物理内存 为 1 MB,页面大小为 4 KB,则翻译虚拟地址 0x321654
- VPO 和 PPO 的位数为 12($4\text{ KB} = 2^{12}\text{B}$),地址对应的 page offset 为
0x654
- 虚拟地址 的总位数为 24($16\text{ MB} = 2^{24}\text{B}$)
- 物理地址 的总位数为 20($1\text{ MB} = 2^{20}\text{B}$)
- VPN 的位数为 24 − 12 = 12,虚拟地址 对应的 VPN 为
0x321
- PPN 的位数为 20 − 12 = 8,
- 虚拟地址 格式为
| VPN (12bits) | VPO (12bits) |
- 物理地址 格式为
| PPN ( 8bits) | PPO (12bits) |
- VPN 为
0x321
,找到 页表 的第0x321
个 页表项,判断其中的 valid 字段是否为 1;若为 0,则调用缺页中断;若为 1,则找到其中的 PPN,并使用 PPN 和 PPO 组成 物理地址。
多级页表
单级页表会有什么问题?
在 单级页表 中,为了管理大型 虚拟地址空间,需要创建庞大的 页表,其中包含大量 页表项,这会导致 页表 本身占用大量内存。
假如我们有一个 32 位 4GB 的 虚拟地址空间、4KB 的页面和一个 4 字节 的 PTE,那么我们将需要一个 4 MB 的 页面表 始终驻留在内存中,即使应用程序只引用 虚拟地址空间 的一小块。
多级页表结构
在 多级页表 中,虚拟页号(VPN)被分割为多个字段,假设被分割为 k 个字段的话,第 k 个字段对应的页表中的查询内容为 PPN,前 k‑1 个字段对应的页表中的查询内容为下一级页表的位置。
如果当前 多级页表 对应的一级页表有 $m^k$ 个 PTE,将 VPN 分割为 k 个长度相同的子字段后,每个子字段对应的页表的 PTE 个数为 $\log_k{m^k} = m$。
其中,第一层有 $m$ 个页表,第二层最多有 $m^2$ 个页表在内存中存在,$\cdots$,第 k 层最多有 $m^k$ 个页表在内存中存在。
多级页表是如何节省内存的?
只有一级页表需要始终在主存中,对于其他层次的页表,可以 按需分配;如果使用到的,就在内存中创建对应的页表结构,如果未使用到,就不需要为其分配内存。这代表了巨大的潜在节省,因为典型程序的 4GB 虚拟地址空间 中的大部分都是未分配的。
对于 k 级页表而言,前 k‑1 级页表中 PTE 存储的关键字段都是下一级页表的位置,如果其中某个 PTE 的 有效位 为 0,那么操作系统无需为该 页表项 对应的下一级页表分配内存空间。
TLB
TLB(Translation Lookaside Buffer)是 CPU 内存管理单元(MMU)中的一种 高速缓存,用于加速 虚拟地址(VA)到 物理地址(PA)的地址转换过程。TLB 存储了最近用过的 虚拟地址 到 物理地址 的映射,以减少每次内存访问时的地址翻译延迟。
由于 页表 存储在内存中,所以每一次通过 页表 的地址翻译过程都至少需要一次访存,开销仍然比较大。为降低开销,TLB 应运而生。与 Cache 类似,你可以从概念上将 TLB 理解成一个硬件结构,因离 CPU 更近,其访问速度更快。
存储结构
TLB 是一个硬件结构,但从逻辑上可以将其理解为一张表。
与 cache 存储结构 类似,TLB 由许多 表项(TLB Entry)构成,每一个 表项 包含多个字段,其中 tag 和 PPN 是必须的,其他字段是可选的。
TLB 和 cache 对比
TLB 和 页表 的关系类似于 cache 与 主存 的关系,TLB 和 cache 都是硬件结构,只是作用场景不同。TLB 与 Cache 的区别如下表所示:
TLB | Cache | |
---|---|---|
存储的是什么? | 物理页面号(PPN) | 主存块 |
使用什么去查找? | 虚拟页面号(VPN) | 主存块号 |
在何种场景下使用? | 将 虚拟地址 翻译为 物理地址 时 | 访问 物理地址 时 |
TLB 中也包含三种映射方式:直接映射、全相联映射、组相联映射。
在访问 TLB 时,虚拟地址 中的 虚拟页号(VPN)被按照映射方式进行不同的切分。在访问 cache 时,物理地址 中的 主存块号 被按照映射方式进行不同的切分。
两者的对比如下所示:
在 直接映射 中,通过 TLB 行号去对应行的 表项 进行查询。
在 组相联映射 中,通过 TLB 组号去对应组进行查询(遍历组中的所有 TLB 表项)。
在 全相联映射 中,遍历 TLB 中的所有 表项。
地址结构
当我们通过 TLB 进行 虚拟页号(VPN)→ 物理页号(PPN) 的翻译时,访问 TLB 需要使用到 VPN。
从逻辑上而言,VPN 可以被分为 标记 和 匹配字段 这两个部分:
- 标记(TLBT, 即 TLB Tag):与 TLB 中的 tag 进行对比,判断是否命中 TLB 表项。
- 匹配字段(Match Field),通过该字段判断 VPN 可能被哪些 TLB 表项所缓存,这里 TLB 与 Cache 类似,同样具有三种映射方式,每一种映射方式的 匹配字段 位数不同:
- 直接映射:匹配字段为 TLB 表项编号,即行编号(TLB entry index),其位数为 $\log_2{(\text{\small TLB 行数})}$
- 全相联映射:没有匹配字段,即匹配字段位数为 0,因为每一个 VPN 都可能被任何一个 TLB 行所缓存
- 组相联映射:匹配字段为组号(TLB group index),位数为 $\log_2{(\text{\small TLB 组数})}$
使用 TLB 进行地址翻译的过程如下:
- 给定一个 虚拟地址(VA),从中提取出 虚拟页号(VPN)
- 根据映射方式从 VPN 中提取出 标记(TLBT)和 匹配字段
- 根据 匹配字段 从 TLB 的相应表项中依次查找,命中时应满足如下条件:
- 有效位(valid)为 1
- 该表项中的 TLBT 与 VPN 中的 tag 字段相同
- 若命中某个表项,则通过 TLB 完成 VPN → PPN 的翻译
- 若未命中,则通过 页表 完成翻译
请求页式管理
请求页式管理(Demand Paging)是一种计算机操作系统中的内存管理技术,它允许进程在需要时才将 页面(或者说 虚拟内存 中的数据块)加载到 物理内存 中,而不是一次性将整个进程加载到内存中。核心思想是将 物理内存 划分成固定大小的 页框(page frame),并将 虚拟内存 划分成相应大小的 页面(page)。
页面错误
当进程尝试访问一个 虚拟页面,但该页面当前未加载到 物理内存 中时,会触发 页面错误(page fault)。此时,操作系统会将相应的页面从磁盘加载到 物理内存,进行 页面替换。
如何判断访问的页面是否在物理内存中呢?
通过查询 TLB 和 页表(见 单级页表)以判断某个 虚拟地址 对应的页面是否在内存中。
从 虚拟地址(VA)中提取出 虚拟页号(VPN),再查询 TLB 和 页表 中是否存在包含 VPN 的记录。如果都不存在,则说明内存中不存在与 VPN 对应的 物理页面。
页面替换
页面替换 时包含两种情况:
- 物理内存 中存在 空闲页面(未被任何进程使用的页面)。
- 进程 中不存在任何 空闲页面,即所有页面都被进程使用了。
如果存在 空闲页面,当一个进程出现 缺页中断 时,直接使用 空闲页面 即可。
如果 物理内存 已满,操作系统需要选择一个页面来替换。通常,操作系统会选择一个 不再需要的页面 进行替换,这个决策基于 使用的页面置换算法。
如果操作系统中配置了 交换分区(swap area),被置换的页面会被写入 交换分区;当该页面再次被需要时,页面会从 交换分区 中加载回 物理内存。
交换分区交换分区 是硬盘上专门划出的一个区域,用来作为系统内存的“扩展”。当 物理内存 使用接近或达到上限时,操作系统会把暂时不用的内存页(比如不活跃的后台程序)移到 交换分区,从而释放出 物理内存。
访存过程
对于进程而言,它自己可见的就是 虚拟地址(VA),当进程访问 虚拟地址 时,实际上是对某个 物理地址 进行访问。
访存 主要包含两大过程:
- 地址翻译:VPN → PPN,即由 虚拟地址(VA)得到 物理地址(PA)的过程。
- 根据 物理地址(PA)去 cache 或 主存 中读取数据。
需要熟练掌握这两大过程中的各种细节,考试中常将这些知识点组合考察。
虚拟地址翻译过程
操作系统经过如下步骤将 虚拟地址 转为 物理地址:
- 从 虚拟地址(VA)中提取 VPN(虚拟页号)。
- 根据 VPN 去访问 TLB。
- 命中 TLB:从 TLB 中读取相应的 物理页号(PPN)。
- 未命中 TLB:读取 页表 后更新 TLB。
- 命中页表:从 页表 中读取相应的 PPN,并更新 TLB 表项。
- 未命中页表:触发 缺页中断,从内存中找一个页面以加载 物理页面
- 存在 空闲页面,直接使用 空闲页面,使用其 PPN 更新 页表。
- 不存在 空闲页面,选择一个进程的页面进行置换。
- 组装 物理页号(PPN)和 页内偏移(PPO)得到 物理地址。
上述过程可以由如下的流程图表示:
上文中提到的 缺页中断 过程忽略了一些细节,详细过程如下:
- 触发缺页中断:
- 当 CPU 尝试访问一个 虚拟内存页面,但该页面未加载到 物理内存(或页面无效)时,MMU 检测到页面缺失,触发 缺页中断。
- 判断缺页原因:
- 页面未分配、页面在磁盘(换出)、非法访问(越界或权限不足)。
- 若为 非法访问,操作系统会终止程序(如段错误,Segmentation Fault)。
- 判断是否有空闲页面:
- 若有 空闲页面,操作系统会分配一个新的 物理页面。
- 若 物理内存 不足,可能会通过页面置换算法(如 LRU)选择一个现有页面换出到磁盘,腾出空间。
- 加载页面内容:
- 若 页面未分配,操作系统会分配一个新的 物理页面。
- 若 物理内存 不足,同上使用页面置换算法。
- 更新页表:添加一条新的页面映射表项。
- 恢复执行:缺页中断 处理完成后,操作系统恢复被中断的程序上下文。CPU 重新执行引发缺页的指令,此时页面已可用,程序继续正常运行。
可以通过以下流程图理解缺页中断的过程:
flowchart TD classDef smallFont font-size:10px; A[CPU访问虚拟内存页面] --> B{MMU检查页面是否在物理内存} B -->|页面存在| C[正常访问,继续执行] B -->|页面不存在| D[触发缺页中断] D --> E{判断缺页原因} E -->|非法访问<br/>越界或权限不足| F[操作系统终止程序<br/>段错误 Segmentation Fault] E -->|页面未分配| G[分配新页面流程] E -->|页面在磁盘<br/>被换出| H[从磁盘加载页面流程] G --> I{物理内存是否有空闲页面} H --> I I -->|有空闲页面| J[分配空闲物理页面] I -->|内存不足| K[使用页面置换算法<br/>如LRU选择页面换出] K --> L[将选中页面写回磁盘<br/>释放物理页面] L --> J J --> M{页面类型} M -->|新分配页面| N[初始化页面内容<br/>通常清零] M -->|从磁盘加载| O[从磁盘读取页面内容<br/>加载到物理页面] N --> P[更新页表<br/>添加虚拟地址到物理地址映射] O --> P P --> Q[恢复程序上下文<br/>返回中断前状态] Q --> R[CPU重新执行<br/>引发缺页的指令] R --> S[页面现已可用<br/>程序继续正常运行] F --> T[程序结束] C --> S style D fill:#ffcccc style F fill:#ff6666 style K fill:#ffeb99 style P fill:#ccffcc style S fill:#99ff99
物理地址访存过程
访存 过程常与 地址翻译 过程一起考察。进程访问一个 虚拟地址 时,首先完成 地址翻译 得到 物理地址,随后使用该 物理地址 访问内存。
使用 物理地址 访问内存时首先判断是否命中 cache;若未命中,则访问 内存,随后更新 cache。在使用 主存块 更新 cache 块 的过程中,还要根据 cache 与 主存 的映射方式决定替换哪个 cache 块,详细过程如下图所示。
虚拟地址翻译和访存过程总结
如果将 地址翻译过程 与 访存过程 放在一起,一个 虚拟地址 访问内存的大致流程为(? 表示不一定会发生):
虚拟地址 → TLB → (页表)? → (缺页中断)? → 物理地址 → Cache → (内存)? → (更新 Cache)?