1
如果我是一个图书管理员,书架(物理内存)只有10个位子,但学生们想看100种不同的书(进程需求),我该如何设计一张借书卡(页表),让每个学生都觉得整个图书馆的书都在他手边?
用有限资源模拟近乎无限的逻辑空间
页表
每个学生进馆时,你发给他们一张一模一样的清单(虚拟地址空间),上面印着 100 本书的编号。你告诉他们:“想要哪本就填单子给我。”
- 学生手里的是“虚”的编号(虚拟地址),你手里有一张对照表(页表);
- 当学生要看 5 号书时,你查表发现 5 号书其实放在书架的第 3
个格子里(物理地址); - 学生不需要知道书架在哪,他们只负责看自己的清单。
请求分页
- 书架只有 10 个位置,你不可能把 100 本书都摆出来;
- 只有学生真的填单子要看某本书时,你才去后台仓库(磁盘)把书拿出来摆到书架上。
- 异常处理(缺页中断),如果学生要的书不在书架上,你会说:“同学等一下,书在仓库,我去取。”( 非常耗时)
页面置换
当书架 10 个位置全满了,又有新学生要看新书时,你必须把架子上的某本书送回仓库。那么会选谁?
- FIFO: 谁先上架就先踢谁。(简单但愚蠢,可能踢掉大家都在看的书)
- LRU (Least Recently Used): 查一下借阅记录,谁最久没被翻过了就踢谁。 (聪明)
- 缓存(Cache)——把最活跃的数据留在离用户最近的地方。
隔离与保护
如果两个学生都要看“5 号”怎么办?
因为他们手上的虚拟地址空间是一样的,例如,“程序总是从 0x00400000 这个地址开始启动”“数据总是存在 0x00000005 这个位置”。
每个程序都这么想,那么当你同时运行“网易云音乐”和“Chrome浏览器”时,它们内部一定会有大量的虚拟地址冲突。两个不同的程序(你的浏览器和你的音乐播放器)都认为自己在访问地址为 0x0005 的数据。
管理员给“网易云音乐”发了一张清单,给“Chrome”也发了一张清单,两张清单上都有一个位置叫“5号”:网易云音乐 在它的“5号”位置存了一段音乐封面图片。Chrome 在它的“5号”位置存了一段网页密码。
在对照表上动手脚。
当 A 说“我要看 5 号书”,管理员查 A 的表,带他去 1 号格。当 B 说“我要看 5 号书”,管理员查 B 的表,带他去 2 号格。
也就是说为了和平共处,图书管理员(操作系统)必须对每个学生(进程)撒谎。他手里不是只有一张表,而是为每个学生(进程)专门准备了一张表。
“网易云页表”,它的5号对应物理内存的第1024格
“Chrome页表”,它的5号对应物理内存的第8888格
但是,如果他们真的想看“同一本书”呢?(共享内存)
管理员在 A 的页表里把“10 号书”指向物理书架的第 9 格;同时在 B 的页表里把“99 号书”也指向物理书架的第 9 格。
学生 A 的页表(清单对照表)
学生看到的编号 (虚拟地址),书架真实的格子 (物理地址),状态
5 号书,第 1 格,在架上
6 号书,第 8 格,在架上
10号书,第9格,在架上
学生 B 的页表(清单对照表)
学生看到的编号 (虚拟地址),书架真实的格子 (物理地址),状态
5 号书,第 2 格,在架上
6 号书,(仓库),缺页
10号书,第9格,在架上
这样每个学生都觉得自己拥有整个图书馆(4GB 或 8GB 空间),且互不干扰。
AI 运行慢,往往不是因为 CPU/GPU 算不完,而是因为“图书管理员”去仓库(硬盘)取书影响了速度
页表切换
每个进程都有自己的页表,在x86架构的CPU里,有一个专门的寄存器叫CR3(页表基址寄存器),存储着当前运行进程的页表在物理内存中的起始地址。当操作系统决定把CPU从“网易云音乐”切换到“Chrome”时(上下文切换):修改CR3寄存器,让它指向chrome的页表。
这意味着扔掉当前的清单,重新加载新清单,并清空 TLB。管理员不用在书架之间奔跑,只是换一副眼镜,戴上A的眼镜,看到的5号书就是1024格,戴上B的眼镜,看到的5号书,就是8888格。补充:其实这种“洗脑”过程也很耗时。
页表本身也是数据,也要占据物理内存,那么:如果页表太多了,把内存占满了怎么办?为了防止地图比城市还大,操作系统采用了多级页表,按需建立。
页表让每个进程都在“平行宇宙”,但有一个地方是所有进程都共同可见的:内核空间。
它存在于每个学生的视野里(虚拟地址的高位,“常驻嘉宾”),当进程需要发送一个数据包或者读一个文件时,它就跳进内核里去运行,系统调用syscall更快。
那我能修改内核的数据吗?不能
页表标记:页表里的每一项都有一个U/S位(User/Supervisor)
硬件分级:x86架构中,CPU的状态有Ring 0 (最高权限,内核态)到 Ring 3(最低权限,用户态)。
Ring 3只能访问标记为U的区域,如果强行访问标记为S(内核)的区域,CPU硬件瞬间产生一个陷阱,直接终止你的程序。Ring 0 可以访问所有区域。
【补充
在 Meltdown 漏洞之前,无论 CPU 在运行你的程序还是运行内核,用的都是同一张页表,内核空间就像是图书馆里一个透明的玻璃办公室,虽然你进不去(Ring 0 限制),但你能看到里面所有的资料摆在那。
Meltdown 之后,为了安全,管理员(操作系统)引入了 KPTI (Kernel Page Table Isolation,内核页表隔离) 技术,为每个进程准备了两张清单。
用户态页表,当你的程序正常运行时,使用这张表。虚拟地址高位原本存放完整内核的地方,现在大部分变成了空白或无效,只保留下了极其少量的内核代码映射,让 CPU 知道怎么进入内核(比如处理中断或系统调用)
内核态页表,只有当 CPU 切换到 Ring 0 模式时,才会换上这张表。这张表依然拥有完整的全局视角,能看到全部物理内存。
当你在跑程序时,管理员把大部分内核清单都收起来了,只有要办事的时候才拿出来。计算机的性能因此下降了 5% 到 30%(尤其遇到频繁读写硬盘、网络收发这种系统调用密集型任务时)。这就是“安全”与“可见性”之间的惨烈博弈。
切页表会修改 CR3 、清空极速缓存 TLB(快表) ,后来的 CPU 引入了 PCID(TLB 里存着“进程A | 0x5”和 “进程B | 0x5”。切到进程 B 时,进程 A 的记录不用删,暂时置灰),告诉 CPU:“我现在是在进程B的内核态页表,但是之前A缓存别扔,待会儿回来还能用。”。用这种方式尽可能抵消 KPTI 带来的性能损失。
ARM 架构,与“CR3”对应的功能由 TTBR0/TTBR1 (Translation Table Base Register) 承担。TTBR0 负责用户空间,TTBR1 负责内核空间。
RISC-V 架构: 与“CR3”对应的寄存器叫 satp (Supervisor Address Translation and Protection)
为什么“多线程”程序比“多进程”程序快得多? 回到我们的图书馆。多进程是给每个学生发一张独立清单(换页表/换眼镜);而多线程则是几个学生共享同一张清单(共享同一个页表),多线程切换时,不修改CR3,不清空TLB,不需要重新加载内存,CPU只是简单修改“当前执行到哪行代码”的指针,几乎没有性能损耗。
所以高性能服务器,比如Nginx、数据库,都极度依赖多线程。当然,速度也是有代价的,共享必定有混乱,那么就引入锁机制。
互不相干的任务,浏览器的不同的插件,各过各的,安全,只是“换眼镜比较累”,用多进程。
共享但容易打架的,频繁交换数据的核心逻辑,比如大模型的推理计算,用多线程。
用进程保命,用线程提速。
】
2
如果 10 个学生同时冲进来,每个人都要看 10 本不同的书,而你的书架依然只有 10 个位子,你的图书馆会发生什么?
- 学生 A 要看书,你跑到仓库把书拿出来,放上书架。
- 学生 B 马上也要看书,书架满了,你不得不把学生 A 的书撤下来放回仓库,再把学生 B 的书搬上来。
- 学生 A 还没翻开书,发现书没了,又填单子要。你只好再把学生 B 的书撤下来……
整天都在仓库和书架之间跑来跑去(频繁 I/O),汗流浃背,但没有任何一个学生真正读到了一页书。CPU 利用率极低(因为都在等磁盘搬运数据),而磁盘/总线忙碌率 100%。
方案1 排队
既然 10 个人玩不转,那就只让 2 个人玩。
把其中 8 个学生赶到休息室,锁上门。只给剩下的 2 个学生提供服务,每个人分 5 个位子。虽然响应慢了(有人在排队),但至少图书馆动起来了
方案2 增加资源
申请经费,把书架从 10 个位子扩展到 100 个位子。
这是“钞能力”, AI 大模型时代,这也是为什么大家都在疯抢 H100 显卡(更大的显存/书架)。
方案3 优化工作集
你通过观察发现,虽然学生要看 10 本书,但其实 80% 的时间都在看其中 2 本核心参考书(局部性原理)
策略: 你规定,每个人必须先申报自己的核心书单。如果书架放不下所有人的核心书单,就不允许开始。
保证每个进程最活跃的那部分页面(书)都在内存里,系统才不会抖动。
3
Python/Java,有“清洁工”的图书馆
在 Java 或 Python 的图书馆里,你作为学生(程序员)只管看书,看完随手一丢。 图书馆雇了一个清洁工(垃圾回收器GC )。他会定期走出来,看看哪本书没人看了,再把它们搬回仓库。用 Python 写代码,你不需要懂内存,AI 帮你写,GC 帮你收。
清洁工干活的时候,为了不搬错,有时会让所有学生必须停止看书(程序暂停),等他清理完才能继续。在处理超大规模数据流或实时 AI 推理时,这种“突然的停顿”是要命的。
Rust“借书守则”
Rust 图书馆不雇清洁工,因为请人干活又贵又慢。它把责任推给学生,进门前就把规矩定死。
所有权规则: 一本书在同一时刻只能被一个学生真正拥有。如果学生 A 把书给了学生 B,那学生 A 手里就立刻变空了,他不能再读这本书。这样,当这个学生离开座位时,他手里拿的那本书立刻就能被放回仓库。不需要清洁工,内存是“即时释放”的。
在 Java 或 Python 的世界里,你把一个变量赋值给另一个变量,本质上是两个人同时抓住了同一本书。如果学生 A 觉得书旧了,撕掉两页,学生 B 读到那里时就会一脸懵逼(数据污染/悬垂指针)。如果两人同时想撕书,书就坏了。
借用规则: 解决计算机科学经典的问题:读写冲突。只要书没在改,多少人看都行。只读借书证 可以发给很多人,大家坐在一起看,相安无事。修改借书证只能发给一个人,且他拿证期间,别人连看的资格都没有。代码,最容易死在“一个人在读,另一个人在改”导致的逻辑混乱上。Rust 强制你在写代码时就想清楚:这个时刻,谁有权改?谁有权看?
如果你想写一段代码,把一个局部变量的引用传给全局变量,Rust 编译器会直接报错。它强迫你思考:这个数据到底能活多久?想要安全,忍受 Java/Python 的慢;想要快,忍受 C++ 的危险(内存泄漏)。Rust :只要你脑子够清楚,你可以既要安全,又要极性能
C++ 智能指针像“自律”,你偷偷翻墙进仓库拿书(用裸指针),管理员发现不了,直到你出事;Rust 像“他律”: 你根本没法做出违规动作,因为重力(编译器)不允许你飞起来。
当前,重要系统软件(飞机的控制系统、浏览器的核心、加密货币的账本)的新代码、关键模块以及安全敏感组件中,Rust 正逐渐成为 C++ 的首选替代方案:Chrome 正在关键的沙盒机制和第三方库中逐步引入 Rust 模块;加密货币与分布式账本,Rust 已经成为开发高性能区块链底层的首选语言;航空航天与关键控制系统通常使用 C 和 Ada,工程师在社交媒体和公开招聘中多次提到对 Rust 在安全控制链路中的探索;Rust是 Linux 诞生 30 多年来,除了 C 语言外唯一获准进入内核的编程语言。

被折叠的 条评论
为什么被折叠?



