每个进程都有一份自己的页表
概要:说明“每个进程一份页表”是进程隔离的核心手段:用户空间低地址映射各自独立,内核空间高地址映射共享,并通过修改 %cr3 寄存器实现页表切换。
在绝大多数现代操作系统中,每个进程(应用)都拥有自己独立的一份页表。
这是操作系统实现“进程隔离”最核心的手段。如果所有应用共用一份页表,那 A 应用就能轻而易举地读写 B 应用的数据,电脑就会变成一个大乱炖,毫无安全性可言。
1. 独立的地基:用户空间 (User Space)
每个进程的页表在低地址部分(从 0 开始往上)的映射是完全不同的。
- 进程 A 的页表:把虚拟地址
0x1000映射到物理内存的第 50 号页框。 - 进程 B 的页表:把虚拟地址
0x1000映射到物理内存的第 90 号页框。
结果:虽然 A 和 B 都在操作 0x1000 这个地址,但由于它们使用的是不同的页表,硬件最终访问的是物理内存中完全不同的两个地方。
2. 共享的天空:内核空间 (Kernel Space)
虽然每个进程都有独立的页表,但这些页表有一个共同点:它们的高地址部分(0x80000000 以上)的映射通常是一模一样的。
- 为什么要一样? 因为内核只有一份。无论当前运行的是哪个进程,只要它发起系统调用进入内核,看到的内核代码和硬件驱动都应该是同一套。
- 如何实现? 当操作系统创建一个新进程时,它会把“内核页表”的内容直接复制到新进程页表的高地址项中。
比喻:每个进程都是一座独立的“孤岛”(用户空间),但所有孤岛抬头看到的都是同一片“天空”(内核空间)。
3. 硬件是如何切换的? (%cr3 寄存器)
既然有这么多份页表,CPU 怎么知道现在该用哪一份呢?
这就是内核中 “进程切换(Context Switch)” 的工作了:
- 当 CPU 从进程 A 切换到 进程 B 时。
- 内核会找到进程 B 的
struct proc结构体,取出其中的p->pgdir(这是进程 B 页表的物理首地址)。 - 内核执行一条特殊的指令,把这个地址加载到 CPU 的
%cr3寄存器中。 - 一旦
%cr3被更新,整个世界的翻译规则瞬间改变。从那一刻起,所有的虚拟地址都会按照进程 B 的页表进行翻译。
总结
| 维度 | 情况 | 目的 |
|---|---|---|
| 页表数量 | 每个进程一份 | 确保每个进程有独立的地址空间。 |
| 映射内容 | 低地址独有,高地址共享 | 独有部分实现隔离,共享部分实现内核统一访问。 |
| 切换机制 | 修改 %cr3 寄存器 |
让硬件实时跟随当前运行的进程进行翻译。 |
冷知识:这种“每个进程一份页表”的设计虽然安全,但也有代价。如果系统里有 1000 个进程,就要存 1000 份页表,这会消耗不少内存。所以现代 CPU 引入了多级页表(Multi-level Page Table)来节省空间,只给真正用到的地址分配页表项。
留下评论