这篇文档是对三种常见的操作系统内核虚拟内存设计方案的详细对比和评估,以指导内核进程内存空间的设计。
| 方案编号 | 方案描述 | SATP 切换频率 |
| 方案 1 | 内核独立页表:整个内核独立使用一张页表(satp 指向内核页表),用户空间页表只映射用户态地址。 | 每次陷阱进入/返回都需要切换 satp。 |
| 方案 2 | 内核与用户共享页表(相同 VA 映射):内核态和用户态用同一张页表,所有内核态物理地址映射到相同的虚拟地址 (VA) 上。 | 仅在切换到不同进程时切换 satp。 |
| 方案 3 | 内核与用户共享页表(不同 VA 映射):内核态和用户态用同一张页表,但是每张页表的内核态映射的虚拟地址都不同。 | 仅在切换到不同进程时切换 satp。 |
| 优点 (Pros) | 缺点 (Cons) | 评价 (Evaluation) |
| 安全性高 | 性能开销大: 每次从用户态进入内核态(中断/系统调用)或返回用户态时,都必须写入 satp 切换页表。 | 古老且低效: 类似于早期 x86 系统中的设计。频繁的 satp 切换和随之而来的 TLB 刷新会导致巨大的性能损失。不推荐用于高性能系统。 |
| 隔离性强 | TLB 污染: 每次切换都会导致用户态 TLB 条目失效,增加开销。 | |
| 设计简单 | 内存开销: 内核页表和用户页表通常都需要映射完整的虚拟地址空间,但只有一半有用。 | |
- 设计: 每一个进程的页表都包含两部分:用户空间映射 + 统一的内核空间映射。
| 优点 (Pros) | 缺点 (Cons) | 评价 (Evaluation) |
效率最高 (零陷阱开销): 陷阱进入/退出内核时,无需修改 satp,这是性能的关键。 | 安全性低(TLB 侧信道): 由于所有进程的内核 VA 相同,易受 Meltdown/Spectre 等侧信道攻击。 | 现代主流设计: 这是目前大多数高性能 OS 的标准做法。性能最佳, 适用于对系统调用和中断延迟敏感的系统。 |
| TLB 缓存友好: 切换用户进程时,内核的 TLB 缓存可以保留。 | KPTI 成本: 为了缓解侧信道攻击,需要引入如 KPTI 等隔离技术,这会部分牺牲性能。 | |
| 实现简单 | 地址空间冲突: 必须确保用户进程不会触及内核使用的虚拟地址区域。 | |
- 设计: 每个进程的页表都包含两部分:用户空间映射 + 针对该进程定制的内核空间映射。
| 优点 (Pros) | 缺点 (Cons) | 评价 (Evaluation) |
| 高安全性 | 复杂性高: 每次创建进程时,需要动态生成或链接一份定制的内核映射,增加了内核页表管理的复杂性。 | 定制安全场景: 适用于对内核空间安全有极致要求的系统,或需要实现内核地址空间随机化(KASLR)的系统。 |
| 地址随机化: 可以实现针对每个进程的内核虚拟地址空间布局随机化。 | 内存开销: 每个进程都需要存储一份独立的内核映射页表结构,增加了内核页表占用的物理内存。 | |
中断低开销: 陷阱进入/退出内核时,仍无需修改 satp。 | 调试困难: 内核代码在不同进程中运行在不同的虚拟地址上,使调试和日志记录更加复杂。 | |
| 评价维度 | 方案 1 (独立) | 方案 2 (共享/相同 VA) | 方案 3 (共享/不同 VA) |
| 陷阱/系统调用延迟 | 最差 (需切换 satp) | 最佳 (无需切换 satp) | 好 (无需切换 satp) |
| 切换进程开销 | 差 (双重 satp 切换) | 最佳 (仅在进程间切换 satp) | 好 (仅在进程间切换 satp) |
| 安全性/隔离性 | 高 | 低 (易受侧信道攻击) | 最高 |
| 实现复杂性 | 低 | 低 | 高 |
| 性能 | 低 | 高 | 中/高 |
因性能需求,在目前的内核中,我们采用方案2