在计算机发展的早期阶段,程序员面临一个极为棘手的难题:程序必须在执行前完整装入物理内存,否则根本无法运行。这一硬性限制带来了多重后果。程序的规模被内存容量死死卡住,任何超出物理内存尺寸的软件都无法执行,开发人员不得不耗费精力将程序手工切割为可以独立加载的覆盖段,编程复杂度急剧攀升。即便物理内存足够容纳单个程序,多道程序的并发执行也会因为内存争抢而捉襟见肘,操作系统被迫频繁地进行进程整体的换入换出,每次切换都伴随大量磁盘读写,系统吞吐量因此受到严重拖累。
虚拟内存技术从根本上打破了这道枷锁。其核心思想简洁而深刻:将程序的逻辑地址空间与物理地址空间彻底解耦。每个进程拥有一片属于自己的、逻辑上连续的完整虚拟地址空间,仿佛整台机器的内存都归它独享。实际上,只有正在被访问的页面才需要驻留在物理内存中,其余绝大部分可以存放在磁盘上,等到真正需要时再按需调入。这种策略使程序规模可以远超物理内存容量,程序员从此不再需要为内存够不够用而焦虑。
这一思想的成功实现依赖于局部性原理的强力支撑。时间局部性指出,曾被访问过的指令或数据在不久的将来极有可能再次被访问。空间局部性揭示,被访问存储单元附近的内容也极可能紧随其后被访问。正是这两种规律,使得操作系统可以大胆地将大部分页面放在慢速磁盘上,只在物理内存中保留一小部分活跃页面,却依然维持接近全内存驻留的性能表现。可以毫不夸张地说,局部性原理是虚拟内存技术得以成立的物理学基础。
虚拟内存还带来了宝贵的附加红利。进程间的地址空间天然实现了硬隔离,每个进程无法通过合法寻址方式窥探或篡改其他进程的数据,系统安全性和稳定性因此获得质的飞跃。共享内存也变得更加优雅,多个进程可将各自的虚拟页面映射到同一物理页框,实现高效数据交换而无须额外复制。共享库同样受益于此,多进程可共享同一份动态链接库的代码页面,大幅节约物理内存。虚拟内存技术不仅是存储管理的里程碑,更是现代操作系统一切高级特性的安全底座。
页式存储管理将虚拟地址空间和物理地址空间都切割为固定大小的基本单元,分别称为虚拟页面和物理页框。页面大小通常为二的整数次幂,最常见的是四千零九十六字节,大内存系统也会采用两兆字节甚至更大的页面。这种规整切割使内存的分配与回收变得简单高效,从根本上告别了动态分区中令人头疼的外部碎片。当进程请求内存时,操作系统只需从空闲页框链表中取出足够数量的页框即可,完全无需关心这些页框在物理上是否连续,因为虚拟地址的连续性通过页表映射来保证。当然,页式管理也付出了内部碎片的代价:如果进程的实际需求不是页面大小的整数倍,最后一个页面中未被使用的部分就会形成浪费。不过在两相权衡之下,内部碎片通常只发生在每个进程的最后一个页面,总量有限,远好于动态分区模式下需要不断执行紧缩操作来解决外部碎片的困境。页面大小的选择本身也是一个需要精密权衡的工程决策:较小页面使内部碎片更少、内存利用率更高,但页表规模更大且TLB覆盖范围更窄;较大页面则相反,在大内存系统上可以通过减少页表遍历次数显著提升性能,Linux内核中的透明大页机制就是在这种背景下诞生的。
页表是页式管理的核心数据结构,本质上是以虚拟页号为下标的数组,每一项记录着对应物理页框号及一组关键控制标志位。有效位是最基础的标志,告知硬件该页面是否实际存在于物理内存中。若有效位为零,意味着页面要么未被分配要么已被换出,任何访问都将触发缺页中断。修改位记录页面自调入后是否被写过,在置换决策中地位举足轻重:若页面未被修改,淘汰时可直接丢弃而无需写回磁盘,省去一次昂贵的输入输出操作。访问位用于追踪页面是否在最近时间窗口内被引用,通常由硬件在每次访问时自动置位、由操作系统周期性清零,是实现近似最近最少使用置换算法的硬件基础。权限位控制页面的读、写和执行属性,是实现内存保护的核心机制。利用权限位可将代码段标记为只读可执行、数据段标记为可读写不可执行,有效抵御缓冲区溢出攻击。写时复制等高级特性也是通过巧妙操纵权限位实现的。此外,在页表项中还可能存在一些平台特定的辅助标志位,比如全局位用于标记那些所有进程共享且从不换出的内核页面,无需在上下文切换时刷新其TLB映射;NX位则在硬件层面禁止将数据页作为代码执行,与操作系统的数据执行保护机制联动,构成了抵御栈溢出和堆喷射攻击的第一道防线。
在三十二位地址空间中,进程的虚拟地址空间可达四千兆字节。若页面大小取四千零九十六字节,页表需包含约一百万个条目,每个条目四字节则一级页表本身需占四兆字节的连续物理内存。当系统同时运行数十上百个进程时,页表开销将膨胀到无法容忍。对六十四位系统而言,地址空间更是天文数字,一级页表完全不可行。多级页表的思路是将页表本身也进行分页处理,只对确实被使用的虚拟地址范围才分配页表页面。以四级页表为例,虚拟地址被拆分为四个索引字段和一个页内偏移,硬件从顶级页目录开始逐级向下查找,直到最后一级页表项给出物理页框号,拼上页内偏移即得完整物理地址。代价是每次完整转换需多次内存访问,但在TLB的强力配合下,绝大多数转换可直接命中TLB,实际性能损耗完全可控。
倒排页表反其道而行,以物理页框号为索引,整个系统只维护一份全局页表,每个条目记录占用该页框的进程标识和虚拟页号。地址转换时需在倒排页表中搜索匹配项,通常借助哈希表加速。这种组织方式在大内存服务器和六十四位系统中展现出显著空间效率,曾在PowerPC和IA至六十四等架构上应用。代价是共享内存实现变得复杂,因为一个物理页框在表中只能有一条记录,多进程共享同一物理页面时需额外机制来协调多对一的映射关系。
虚拟地址到物理地址的转换是处理器最频繁也最关键的任务之一,几乎每条访存指令都要经历此流程。若每次转换都须从顶级页目录逐级查找,单次访存延迟将增加数倍,系统性能急剧恶化。转译后备缓冲器正是为解决这一瓶颈而设计的硬件加速部件。TLB本质上是容量极小但速度极快的内容可寻址存储器,缓存着最近使用过的虚拟页号到物理页框号的映射条目。由于局部性决定了绝大多数内存访问集中在很小的页面集合内,因此绝大部分地址转换可在TLB中直接命中,额外时间开销压缩到几乎可忽略的程度。
当处理器发出虚拟地址访问请求时,内存管理单元先将虚拟地址切分为虚拟页号和页内偏移两部分。接着MMU用虚拟页号对TLB全部表项同时进行并行比较查询,利用内容可寻址存储器的硬件特性在一个周期内得出命中结论。若命中,直接取出物理页框号与页内偏移拼接即可。若未命中,硬件须启动页表遍历流程,从控制寄存器取出顶级页目录基址,逐级读取各级目录和页表条目,直到定位到物理页框号。在六十四位四级页表结构中,一次完整遍历需要四次额外内存访问,开销不可谓不大。遍历完成后,新映射关系被填入TLB,同时根据替换策略淘汰旧条目。现代处理器通常采用多级TLB架构来进一步提升命中率,一级TLB容量极小但速度最快通常只有几十个条目,二级TLB容量稍大可达数百个条目,两者协同工作使得在大多数场景下地址转换延迟不超过一个处理器周期。
TLB一致性维护是容易
本篇完!