第6讲 虚拟内存管理II

第6讲 虚拟内存管理II
agsd虚拟内存管理 II:从页表维护到缺页异常
《虚拟内存管理 I》解释了虚拟地址如何通过页表和 MMU 翻译成物理地址。《虚拟内存管理 II》进一步讨论的是:这些页表由谁创建、谁维护,操作系统如何利用页表、虚拟内存区域和缺页异常来构造进程的地址空间。
这一讲的主线可以概括为:
1 | CPU 使用虚拟地址 |
❗ 核心结论: 虚拟内存不是一张静态的地址翻译表,而是操作系统通过页表、虚拟内存区域、缺页异常和一系列优化机制共同维护出来的地址空间抽象。
1. 操作系统自己也使用虚拟地址
一个容易被忽略的问题是:既然普通程序使用虚拟地址,那么操作系统自己访问内存时,使用的是虚拟地址还是物理地址?
答案是:操作系统代码运行时同样使用虚拟地址。
CPU 执行内核代码时,指令中的地址仍然是虚拟地址。只是内核使用的是内核地址空间和内核页表,而用户进程使用的是用户地址空间和用户页表。
在 AArch64 中,可以粗略理解为:
| 寄存器 | 作用 |
|---|---|
TTBR0_EL1 |
通常指向用户地址空间的页表 |
TTBR1_EL1 |
通常指向内核地址空间的页表 |
于是系统中会有这样的地址空间分布:
1 | 低地址 |
这引出一个关键矛盾:
- 操作系统运行时使用虚拟地址;
- 但是页表项中保存的是物理地址;
- 操作系统维护页表时,必须读写页表页;
- 如果页表项给出的是物理地址,操作系统不能直接把它当作指针使用。
因此,操作系统需要一种简单方法,把物理地址转换成内核可以访问的虚拟地址。这就是直接映射要解决的问题。
❗ 本节核心结论: 操作系统运行时也处在虚拟地址体系中;它不能直接把页表项里的物理地址当作普通指针使用。
2. 直接映射 Direct Mapping:内核访问物理内存的桥
直接映射的核心思想是:内核拿出一段连续的虚拟地址空间,与物理内存建立线性映射关系。
它可以简化成一个公式:
1 | 内核虚拟地址 = 物理地址 + 固定偏移量 |
反过来也可以写成:
1 | 物理地址 = 内核虚拟地址 - 固定偏移量 |
例如:
1 | 物理地址 0x0000_3000 |
这样,操作系统只要知道物理地址和固定偏移量,就可以快速得到对应的内核虚拟地址。
直接映射不是给普通应用程序使用的。它主要服务于内核自身,尤其是:
- 访问任意物理页;
- 管理物理内存;
- 读写页表页;
- 在页表项保存的物理地址和内核可访问的虚拟地址之间转换。
为什么不使用不连续的内核虚拟地址来映射物理内存?不是因为不能,而是因为那会让管理变复杂。
如果使用线性映射:
1 | 物理地址 0x0000 → 内核虚拟地址 OFFSET + 0x0000 |
内核只需要加减偏移量即可完成转换。
如果使用不连续映射:
1 | 物理地址 0x0000 → 内核虚拟地址 A |
内核每次都要额外查询一张映射表,代码复杂、效率下降,也会让页表管理更绕。
❗ 本节核心结论: 直接映射的价值在于让内核通过简单的加减偏移访问物理内存,而不是每次都额外查找物理页对应的内核虚拟地址。
3. 进程的虚拟内存段分布
一个进程的虚拟地址空间不是一整块简单内存,而是由多个不同用途的区域组成。
典型进程中常见的区域包括:
| 区域 | 作用 |
|---|---|
.text |
保存程序机器指令,可读、可执行 |
.rodata |
保存只读数据,例如字符串字面量 |
.data |
保存已经初始化的全局变量和静态变量 |
.bss |
保存未初始化的全局变量和静态变量,初始值通常为 0 |
heap |
堆,供 malloc、new 等动态分配使用 |
stack |
栈,保存函数调用、局部变量、返回地址等 |
mmap 区域 |
由 mmap 创建的匿名映射或文件映射区域 |
| 动态库区域 | 例如 libc.so 等共享库 |
vvar / vdso |
系统提供的特殊用户态辅助区域 |
例如:
1 | int global_init = 10; // .data |
程序从源代码到进程地址空间,大致经历:
1 | .c / .cpp 源文件 |
ELF 文件记录了程序的代码段、数据段、只读段等信息。加载器和操作系统根据这些信息,为进程建立相应的虚拟内存区域。
flowchart TD
SRC[源代码] --> OBJ[目标文件]
OBJ --> ELF[ELF 可执行文件]
ELF --> LOAD[加载器加载]
LOAD --> TEXT[.text]
LOAD --> RODATA[.rodata]
LOAD --> DATA[.data]
LOAD --> BSS[.bss]
LOAD --> HEAP[heap]
LOAD --> STACK[stack]
LOAD --> MMAP[mmap 区域]
这张图强调:进程地址空间是由多个区域组合而成的,不同区域具有不同来源、权限和用途。
❗ 本节核心结论: 进程地址空间不是单一连续内存,而是由代码、数据、堆、栈、动态库和 mmap 区域等多个虚拟内存区域组成。
4. 页表映射管理:操作系统如何建立 va → pa
虚拟地址能否访问,最终取决于页表中有没有有效映射。页表由操作系统创建和维护,MMU 只是按照页表执行翻译。
页表管理的核心操作可以抽象成两个函数:
1 | void add_mapping(u64 pgtbl, u64 va, u64 pa); |
它们分别表示:
- 添加虚拟地址
va到物理地址pa的映射; - 删除虚拟地址
va对应的映射。
在四级页表中,添加映射大致需要:
1 | 根据 va 计算 L0 索引 |
可以用下图表示:
flowchart TD
VA[虚拟地址 va] --> L0[计算 L0 index]
L0 --> P1{L1 页表存在?}
P1 -- 否 --> A1[分配 L1 页表页]
P1 -- 是 --> L1[计算 L1 index]
A1 --> L1
L1 --> P2{L2 页表存在?}
P2 -- 否 --> A2[分配 L2 页表页]
P2 -- 是 --> L2[计算 L2 index]
A2 --> L2
L2 --> P3{L3 页表存在?}
P3 -- 否 --> A3[分配 L3 页表页]
P3 -- 是 --> L3[计算 L3 index]
A3 --> L3
L3 --> MAP[写入 pa 和权限位]
在这个过程中,经常会用到类似 get_next_pgtbl_page 的逻辑:
1 | u64 get_next_pgtbl_page(u64 *pgtbl, u32 index) { |
这个函数与直接映射紧密相关,关键在最后一步:
1 | return paddr_to_vaddr(pgtbl_entry); |
原因是:
- 页表项里保存的是下一级页表页的物理地址;
- 但操作系统代码运行时不能直接用物理地址当指针;
- 所以内核必须把物理地址转换成内核虚拟地址;
- 这个转换依赖直接映射。
因此:
1 | 页表项里的物理地址 |
删除映射时还有一个额外问题:TLB 中可能缓存了旧映射。即使页表项已经删除,如果 TLB 没刷新,CPU 仍可能使用旧的 虚拟页 → 物理页 翻译结果。因此删除或修改映射后,通常需要考虑刷新相关 TLB 项。
❗ 本节核心结论: 页表由操作系统按需创建和修改;页表项保存物理地址,而内核运行时使用虚拟地址,所以页表管理必须借助直接映射完成物理地址到内核虚拟地址的转换。
5. 立即映射 mmap:申请时立刻分配物理页
mmap 是用户程序申请虚拟内存区域的一种方式。例如:
1 | char *buf = mmap((void *)0x500000000, 0x2000, |
这表示程序希望申请一段虚拟地址区域:
- 起始地址大致为
0x500000000; - 长度为
0x2000,即 8192 字节; - 权限为可读、可写;
- 匿名映射,不对应具体文件;
- 私有映射。
如果页面大小是 4KB,那么 0x2000 对应 2 页。
所谓立即映射,是指 mmap 调用时,操作系统马上完成:
- 记录这段虚拟地址区域合法;
- 分配对应数量的物理页;
- 建立虚拟页到物理页的页表映射。
例如:
1 | 虚拟页 0 → 物理页 A |
这样,mmap 返回后,程序第一次访问这段内存时,页表映射已经存在,不需要再因为“没有物理页”而触发缺页处理。
立即映射的优点是第一次访问快,逻辑直接。缺点是可能浪费物理内存。
例如程序申请 1GB 内存,但最后只使用前 4KB。如果采用立即映射,系统可能提前分配大量物理页,其中绝大多数根本没有被使用。
❗ 本节核心结论: 立即映射会在 mmap 时立刻分配物理页并建立页表映射,首次访问快,但可能提前占用大量实际内存。
6. 延迟映射:先记录合法区域,用到再分配
延迟映射的思想是:mmap 时只记录虚拟地址区域合法,不马上分配物理页;等程序第一次真正访问某一页时,再分配物理页并建立映射。
操作系统需要为进程维护虚拟内存区域信息,例如:
1 | struct vmregion { |
一个进程的地址空间可以理解为:
1 | struct vmspace { |
也就是说:
1 | 进程地址空间 = 页表 + 虚拟内存区域列表 |
延迟映射下,mmap 做的事情主要是:
1 | 记录 [start, end) 是合法虚拟地址区域 |
例如程序申请 16KB 匿名内存,页大小为 4KB:
1 | 虚拟页 0:合法,但未分配物理页 |
如果程序第一次只访问 buf[0],通常只会为第 0 页分配一个物理页:
1 | 虚拟页 0 → 物理页 A |
之后如果程序访问 buf[5000],进入第 1 页,系统再为第 1 页分配物理页。
因此,延迟映射最基本的分配粒度是:访问哪一页,分配哪一页。
当然,在某些优化场景中,操作系统可能做预读、预取或透明大页分配。但从基本概念上看,延迟映射不是一次性分配整个申请区域,而是按实际访问逐页补上映射。
❗ 本节核心结论: 延迟映射把“申请虚拟地址区域”和“分配物理内存”分开;通常第一次访问哪一页,就只为哪一页分配物理页。
7. 缺页异常 Page Fault:不是所有缺页都是错误
当程序访问某个虚拟地址时,MMU 查页表发现没有有效映射,就会触发缺页异常。
缺页异常本身不一定意味着程序出错。它只是说明:当前访问无法直接通过页表完成翻译,需要操作系统介入处理。
缺页异常大致分为两类。
7.1 合法缺页
合法缺页指的是:
- 访问的虚拟地址属于某个合法
vmregion; - 访问权限也符合该区域的权限;
- 只是物理页尚未分配,或页表映射尚未建立。
例如:
1 | char *buf = mmap(...); |
如果采用延迟映射,buf[0] 第一次访问时可能触发缺页异常。操作系统处理流程是:
1 | 得到 fault_va |
这类缺页异常是正常机制的一部分。
7.2 非法访问
非法访问指的是:
- 访问地址不属于任何合法虚拟内存区域;
- 或者访问方式违反权限,例如向只读区域写入。
例如:
1 | char *p = NULL; |
操作系统发现该地址不合法,就不会分配物理页,而是向进程发送异常信号,最终表现为 Segmentation Fault。
因此需要区分:
| 概念 | 含义 |
|---|---|
| Page Fault | 硬件发现页表无法完成翻译,陷入操作系统 |
| Segmentation Fault | 操作系统判断访问非法后,终止或通知进程的结果 |
不是所有 Page Fault 都会导致 Segmentation Fault。延迟映射、写时拷贝、页面换入等机制都会利用 Page Fault 作为正常控制路径。
flowchart TD
ACCESS[访问虚拟地址] --> MISS{页表有有效映射?}
MISS -- 有 --> OK[正常访问物理内存]
MISS -- 无 --> PF[触发 Page Fault]
PF --> CHECK{地址在合法 vmregion 中?}
CHECK -- 否 --> SEGV[Segmentation Fault]
CHECK -- 是 --> PERM{权限允许?}
PERM -- 否 --> SEGV
PERM -- 是 --> ALLOC[分配物理页]
ALLOC --> MAP[建立页表映射]
MAP --> RET[返回继续执行]
❗ 本节核心结论: Page Fault 是硬件异常入口,不等于程序错误;Segmentation Fault 是操作系统判断访问非法后的结果。
8. Linux 风格的缺页处理路径
在真实 Linux 中,缺页处理不是简单地“分配一页”就结束。系统需要根据页面类型和访问原因进行分派。
大致路径可以理解为:
1 | do_translation_fault |
handle_pte_fault 之后可能继续判断:
- 页表项是否无效;
- 页面是否在 swap 中;
- 是否是匿名页;
- 是否是文件映射;
- 是否涉及写时拷贝;
- 是否是权限错误。
常见处理函数包括:
| 函数 | 大致作用 |
|---|---|
do_anonymous_page |
处理匿名映射页 |
do_fault |
处理文件映射或共享映射 |
do_swap_page |
处理被换出到磁盘的页面 |
do_wp_page |
处理写保护页,常与写时拷贝相关 |
学习这一部分时,不必把源码路径机械背诵到每一层。更重要的是理解:缺页异常是一个统一入口,操作系统会根据具体原因选择不同处理方式。
❗ 本节核心结论: Linux 的缺页处理会区分匿名页、文件映射、swap、写保护等多种情况;Page Fault 是多种内存机制共同使用的处理入口。
9. 写时拷贝 Copy-on-Write:不写就共享,一写就复制
写时拷贝简称 COW,是基于页表权限和缺页异常实现的重要优化。
最典型场景是 fork()。
假设父进程调用:
1 | fork(); |
如果操作系统立刻复制父进程的全部物理内存,成本会很高。尤其是很多程序在 fork() 后很快调用 exec(),原来的地址空间马上被替换,提前复制就非常浪费。
因此,操作系统使用写时拷贝:
fork()后父子进程暂时共享同一批物理页;- 父子进程页表都指向相同物理页;
- 这些共享页的页表权限被暂时设置为只读;
- 如果没有写入,就一直共享;
- 如果某一方尝试写入,就触发缺页异常;
- 操作系统分配新物理页,复制原页内容;
- 修改写入方页表,让它指向新物理页;
- 恢复写入方的写权限。
过程可以表示为:
flowchart TD
FORK[fork 创建子进程] --> SHARE[父子共享物理页]
SHARE --> RO[共享页设为只读]
RO --> WRITE{某进程写入?}
WRITE -- 否 --> KEEP[继续共享]
WRITE -- 是 --> PF[触发缺页异常]
PF --> COPY[分配新页并复制内容]
COPY --> REMAP[修改写入方页表]
REMAP --> W[恢复写权限]
写时拷贝的核心是:
1 | 不写就共享,一写就复制 |
它的优点是:
- 减少
fork()的时间开销; - 节省物理内存;
- 避免不必要的数据复制;
- 将复制成本推迟到真正写入时才发生。
❗ 本节核心结论: COW 利用只读权限和缺页异常捕捉写操作,把“立即复制整片内存”优化为“写到哪页才复制哪页”。
10. 内存去重:发现相同页面后合并共享
内存去重可以理解成写时拷贝的反方向应用。
写时拷贝是:
1 | 原本共享 |
内存去重是:
1 | 原本分开 |
例如系统中有两个物理页内容完全一样:
1 | 物理页 A:内容 = 0000... |
操作系统可以只保留其中一个页面,让多个虚拟页指向同一个物理页:
1 | 进程 1 虚拟页 → 物理页 A |
然后释放另一个重复物理页。
为了保证语义正确,合并后的共享页通常会被设置为只读。如果某个进程之后写入该页面,就触发写时拷贝:
1 | 写入共享页 |
Linux 中典型机制是 KSM,即 Kernel Same-page Merging。
不过,内存去重也有安全隐患。因为页面是否被合并可能影响写入延迟,攻击者有可能通过时间差推测其他进程中是否存在某些特定数据。这类问题属于侧信道风险。
❗ 本节核心结论: 内存去重通过合并内容相同的物理页节省内存,并依靠 COW 保证写入时重新分离;但它可能带来侧信道安全风险。
11. 内存压缩:用 CPU 时间换内存空间
当物理内存紧张时,操作系统不一定立刻把页面换出到磁盘。另一种策略是内存压缩。
传统 swap 的思路是:
1 | 内存页 |
缺点是磁盘 I/O 较慢,也可能增加 SSD 写入压力。
内存压缩的思路是:
1 | 不常用内存页 |
例如一个 4KB 页面压缩后可能只占 1KB。这样系统虽然付出了压缩和解压的 CPU 成本,但减少了真实内存占用,也减少了写磁盘的次数。
对比来看:
| 机制 | 做法 | 优点 | 代价 |
|---|---|---|---|
| Swap | 把页面写入磁盘 | 释放内存彻底 | 磁盘 I/O 慢 |
| 内存压缩 | 把页面压缩后留在内存 | 比磁盘快,减少 I/O | 消耗 CPU 进行压缩/解压 |
Windows 10 中有内存压缩机制;Linux 中有 zswap,可以把准备换出的页面先压缩放在内存区域中,减少真正写入磁盘的次数。
❗ 本节核心结论: 内存压缩通过压缩不常用页面缓解内存压力,本质上是用 CPU 计算成本换取更少的内存占用和更少的磁盘 I/O。
12. 大页 Huge Page:用更大映射粒度降低 TLB 压力
普通页大小通常是 4KB。大页则可能是:
- 2MB;
- 1GB。
大页的主要目标是降低页表和 TLB 压力。
假设程序访问 1GB 连续内存:
如果使用 4KB 页:
1 | 1GB / 4KB = 262144 个页面 |
这意味着需要大量页表项,也会占用大量 TLB 项。
如果使用 2MB 大页:
1 | 1GB / 2MB = 512 个页面 |
需要管理的页面数量大幅减少。
在四级页表中,普通 4KB 页通常走完整路径:
1 | L0 → L1 → L2 → L3 → 4KB 物理页 |
2MB 大页可以提前停止:
1 | L0 → L1 → L2 → 2MB 物理页 |
1GB 大页可以更早停止:
1 | L0 → L1 → 1GB 物理页 |
为什么 2MB 大页和 1GB 大页会自然出现?
在 4KB 页、每级页表 512 项的设定下:
1 | 512 × 4KB = 2MB |
所以如果跳过 L3,让 L2 页表项直接指向物理内存,就得到 2MB 大页。
继续向上一层:
1 | 512 × 2MB = 1GB |
所以如果跳过 L2 和 L3,让 L1 页表项直接指向物理内存,就得到 1GB 大页。
大页的优缺点如下:
| 优点 | 缺点 |
|---|---|
| 减少页表项数量 | 可能浪费物理内存 |
| 减少 TLB 项占用 | 需要更大连续物理内存 |
| 提高 TLB 命中率 | 管理更复杂 |
| 减少页表遍历级数 | 内存碎片问题更明显 |
一句话概括:大页是用空间换性能。
❗ 本节核心结论: 大页通过扩大单个页表项覆盖的地址范围,减少页表项数量和 TLB 压力,但可能带来内存浪费和管理复杂度。
13. 一个完整例子:OS 运行一个程序
假设有一个程序:
1 |
|
从操作系统角度看,这个程序经历了以下过程。
13.1 OS 建立直接映射
系统启动后,内核建立直接映射区域,使自己能通过:
1 | 内核虚拟地址 = 物理地址 + OFFSET |
访问物理内存和页表页。
13.2 加载程序并建立虚拟内存区域
加载器根据 ELF 信息建立:
.text:程序代码;.rodata:字符串"hello";.data:global_init;.bss:global_uninit;heap:供malloc使用;stack:供局部变量和函数调用使用;mmap区域:后续由mmap创建。
13.3 OS 维护页表映射
对于需要立即可访问的区域,操作系统分配物理页,并通过 add_mapping 建立:
1 | 虚拟页 → 物理页 |
如果某一级页表不存在,就分配新的页表页。因为页表项保存的是物理地址,内核需要通过直接映射把它转成可访问的内核虚拟地址。
13.4 程序调用 mmap
当程序执行:
1 | mmap(NULL, 8192, PROT_READ | PROT_WRITE, |
操作系统可以有两种策略:
- 立即映射:马上分配 2 个 4KB 物理页并填页表;
- 延迟映射:只记录这 8192 字节合法,暂时不分配物理页。
13.5 第一次访问触发缺页异常
如果采用延迟映射,程序执行:
1 | strcpy(mmap_buf, "Hello mmap"); |
第一次访问 mmap_buf[0] 时,页表中还没有有效映射,于是触发 Page Fault。操作系统检查该地址合法且可写,于是分配一个物理页,建立映射,然后返回程序继续执行。
如果后续访问第二页,例如 mmap_buf[5000],第二页再触发一次缺页异常,再分配第二个物理页。
13.6 fork 触发写时拷贝机制
程序执行:
1 | pid_t pid = fork(); |
父子进程先共享物理页,页表项被设置为只读。子进程执行:
1 | mmap_buf[0] = 'X'; |
写入只读共享页时触发缺页异常。操作系统识别这是 COW 写异常,于是分配新页、复制内容、修改子进程页表、恢复写权限。
13.7 系统可能进一步做优化
在内存管理过程中,操作系统还可能使用:
- 内存去重:发现多个页面内容相同后合并共享;
- 内存压缩:内存紧张时压缩不常用页面,减少磁盘换页;
- 大页:对大块连续内存使用 2MB 或 1GB 页,降低 TLB 压力。
这个例子把本讲的多个机制串成了一条完整链条。
❗ 本节核心结论: 一个普通程序的运行会同时涉及虚拟内存段分布、页表映射、mmap、缺页异常、COW 以及可能的内存优化机制。
14. 易混点总结
14.1 直接映射不是普通用户程序使用的映射
直接映射主要供内核使用。它帮助内核把物理地址转换成内核虚拟地址,从而访问物理内存和页表页。
14.2 get_next_pgtbl_page 和直接映射相连
因为页表项中保存的是下一级页表的物理地址,而内核要继续访问下一级页表,必须先通过直接映射得到可访问的内核虚拟地址。
14.3 延迟映射不是一次分配整个区域
延迟映射通常按页分配。访问哪一页,就为哪一页分配物理页并建立映射。
14.4 Page Fault 不等于 Segmentation Fault
Page Fault 是硬件触发的异常入口;Segmentation Fault 是操作系统判断访问非法后给进程的结果。
14.5 COW 不是不复制,而是推迟复制
写时拷贝不是永远不复制,而是在写入发生时才复制对应页面。
14.6 大页不是一定更好
大页可以降低 TLB 压力,但如果程序只使用大页中的很小一部分,就可能浪费内存。
❗ 本节核心结论: 本讲最容易混淆的是“物理地址与内核虚拟地址”“Page Fault 与 Segmentation Fault”“延迟映射与立即映射”“共享与写时复制”这几组概念。
15. 复习清单
学完《虚拟内存管理 II》后,应能回答以下问题:
- 为什么操作系统自己运行时也使用虚拟地址?
- 页表项中保存的是虚拟地址还是物理地址?
- 为什么内核不能直接把物理地址当作指针使用?
- 直接映射的公式是什么?
- 直接映射为什么通常使用连续线性映射?
- 一个进程的虚拟地址空间通常有哪些区域?
.text、.rodata、.data、.bss、heap、stack 分别保存什么?add_mapping的作用是什么?- 多级页表中,如果下一级页表不存在,操作系统需要做什么?
get_next_pgtbl_page为什么要调用paddr_to_vaddr?- 删除页表映射后为什么可能需要刷新 TLB?
- 立即映射和延迟映射有什么区别?
- 延迟映射下,
mmap时操作系统做了什么? - 第一次访问延迟映射区域时,为什么会触发缺页异常?
- 合法缺页和非法访问有什么区别?
- Page Fault 和 Segmentation Fault 有什么区别?
- 写时拷贝如何利用页表权限和缺页异常?
- 内存去重和写时拷贝有什么关系?
- 内存压缩相对于 swap 的优势是什么?
- 2MB 大页和 1GB 大页分别是怎么来的?
- 大页为什么能提高 TLB 命中率?
- 大页可能带来什么问题?
结论
《虚拟内存管理 II》的核心,是把虚拟内存从“地址翻译机制”推进到“操作系统如何管理地址空间”。
这一讲可以压缩为一句话:
操作系统通过直接映射访问物理内存,通过虚拟内存区域描述进程地址空间,通过页表建立虚拟页到物理页的映射,通过缺页异常按需补上映射,并在此基础上实现写时拷贝、内存去重、内存压缩和大页等优化机制。
真正理解这一讲,关键不是背诵某个函数名,而是形成一个整体直觉:
- 进程看到的是虚拟地址空间;
- 页表决定虚拟页是否能翻译到物理页;
- 操作系统负责维护这些映射;
- 延迟映射让物理页按需分配;
- 缺页异常不是单纯的错误,而是操作系统介入内存管理的重要入口;
- COW、大页、压缩、去重,都是围绕“减少开销、节省内存、提高性能”展开的优化。
因此,虚拟内存不是“假内存”,而是一套由硬件翻译、操作系统策略和异常处理共同实现的精密抽象。




