操作系统 | 虚拟存储器从请求分页到抖动预防全解析嗨之前在计组那篇文章里我们从硬件视角把虚拟存储器拆了个底朝天——局部性原理、页表地址转换、TLB加速、Cache协同……走的是CPU内部怎么干活的路线。但虚拟存储器可不是硬件一个人在战斗操作系统才是幕后那个真正调度全局的大管家。这篇文章我们切换到操作系统视角把请求分页、页面置换、抖动与工作集、请求分段这些内容一次性讲透。和计组那篇对照着看效果翻倍。一、虚拟存储器概述复习一下它到底是什么在计组那篇文章里我们已经知道了虚拟存储器的本质是把外存当内存来使用让程序从外存按需调入内存运行。它的目的也很明确——程序过大无法一次全部装入主存所以需要扩大逻辑内存容量。程序员写代码的时候再也不用操心我的程序放不放得下这个问题了因为操作系统给你制造了一个内存无限大的幻觉。这一切的理论基础是局部性原理包括两个维度时间局部性刚刚访问过的数据很可能马上再被访问和空间局部性刚刚访问过的地址附近的数据很可能马上被访问。正因为程序在运行中具有这种恋旧的特性我们才敢只把一部分页面放在内存里剩下的扔在磁盘上——反正大部分时间程序只在几个页面上反复横跳。虚拟存储器有三种分类页式虚拟存储器、段式虚拟存储器和段页式虚拟存储器。这三种我们在计组那篇已经简单提过接下来在操作系统视角下我们要深入到每一种的请求机制和硬件支持层面。二、请求分页存储管理方式操作系统的核心武器这是虚拟存储器中最主流、最重要的实现方式。它的核心思想是作业不必一次性全部装入内存只装入一部分即可运行当需要访问的页面不在内存中时再由操作系统把缺少的页面从磁盘调入。2.1 硬件支持请求页表机制在计组那篇文章里我们认识的页表只有虚页号→物理块号的映射关系。但在请求分页系统中页表的功能要丰富得多——它不仅是一张地址映射表还是一张状态监控表。每个页表项PTE包含以下字段页号虚拟地址空间中的页面编号作为页表的索引。物理块号该页面当前在主存中对应的物理块编号。如果页面不在内存中这个字段就没有意义。状态位有效位/存在位这是最关键的一位它表示该页是否在主存中。状态位为1说明页面在内存里可以直接访问状态位为0说明页面在磁盘上访问它就会触发缺页中断。你可以把它理解为在/不在的开关。访问字段记录该页在调入主存后被访问的次数或频率。这个信息是给页面置换算法用的——操作系统需要知道哪些页面经常被翻牌子哪些已经被打入冷宫很久了以便在需要腾出空间时做出聪明的淘汰决策。修改位脏位表示该页在调入主存后是否被修改过。如果修改位为1说明这个页面的内容和磁盘上的副本不一样了变脏了。当这个页面被换出时必须把新内容写回磁盘否则数据就丢了。如果修改位为0说明它和磁盘上的副本一模一样直接丢弃就行省了一次磁盘写操作——这在性能上差别巨大因为磁盘写入是很慢的。外存地址指出该页在外存磁盘上的存储位置。当需要把这个页面调入内存时操作系统就根据这个地址去磁盘上找它。2.2 缺页中断机构这是请求分页系统中最精妙的机制之一。当CPU访问一个页面而该页面的状态位为0不在内存中时硬件会产生一个缺页中断。缺页中断的处理流程大致如下第一步中断产生CPU执行指令时发现所需页面不在内存硬件触发缺页中断信号。注意缺页中断和一般的中断不同——它属于内中断陷入型异常是在指令执行过程中产生的而且可能在一条指令执行期间产生多次缺页中断比如一条指令的操作数跨越了两个页面如下图。第二步中断处理CPU暂停当前指令的执行转而进入操作系统的缺页中断处理程序。操作系统查看请求的页面在磁盘上的位置从页表项的外存地址字段获取然后在主存中寻找一个空闲物理块。第三步判断是否需要页面置换如果主存中有空闲物理块直接分配给它然后把页面从磁盘读入。但如果主存已经满了——没有空闲物理块了——那就得按照某种页面置换算法选择一个倒霉蛋页面把它换出去腾出空间给新来的页面。如果那个倒霉蛋被修改过修改位为1还得先把它写回磁盘才能腾出物理块。第四步更新页表新页面调入后更新对应的页表项——填入物理块号状态位设为1修改位清零访问字段清零。第五步重新执行缺页处理完成后CPU重新执行那条因为缺页而被中断的指令。这次页面已经在内存里了一切顺利。2.3 地址变换机构在计组那篇文章里我们已经详细分析了地址变换的过程这里从操作系统角度做一个精简回顾CPU发出虚拟地址MMU从中提取虚页号。先查快表TLB如果命中直接拿到物理块号与页内偏移拼接得到物理地址速度极快。如果快表未命中再去主存中查慢表页表。在慢表里除了拿到物理块号还要检查状态位。如果状态位为1——页面在主存中正常完成地址转换。同时把这次查到的页表项更新到快表中供以后快速访问。如果状态位为0——缺页触发缺页中断进入上面描述的缺页中断处理流程。等操作系统把页面调入内存、更新页表之后CPU重新执行被中断的指令这次地址转换就能顺利完成了。计组那篇文章重点讲的是硬件怎么完成地址转换TLB、Cache、主存、磁盘的完整访存路径而操作系统这里更关注的是缺页之后怎么决策——选哪个页面换出去、怎么管理外存地址、怎么维护页表状态。两门课看的其实是同一个系统的不同侧面合起来才是全貌。2.4 请求分页的内存分配知道了缺页中断怎么处理之后还有一个更上层的问题每个进程该分到多少个物理块分配多了能装的进程就少分配少了缺页频繁还可能引发抖动。这是操作系统必须做的资源分配决策。内存分配原则分为三种策略固定分配局部置换为每一个进程分配一组固定的物理块在进程整个运行期间不再改变。如果发生了缺页只能在该进程自己的物理块范围内进行页面置换——选自己的一个页面换出去腾出空间给新页面。这种方式简单、可预测但灵活性差一个进程可能暂时不需要那么多物理块比如进入了I/O密集阶段但它的块被锁死了别的进程也用不了。可变分配全局置换为每个进程分配的物理块数量在运行期间可以变化。当发生缺页时操作系统可以从系统的任何空闲物理块中选取一个来分配——甚至可以抢占其他进程的物理块。这种方式灵活性高但可能导致某个贪吃的进程不断侵占其他进程的资源引发全局性的抖动。可变分配局部置换这是一种折中方案——每个进程分到的物理块数量可以动态调整操作系统根据系统负载和进程需要增减分配但发生缺页时仍然只能在自己的物理块范围内进行置换。既保证了一定的灵活性又避免了进程之间互相抢占。还有一个重要概念是最小物理块数这是能保证程序正常运行所需的最少物理块数量与计算机的硬件结构有关。如果分配的物理块少于这个最小值某些指令根本无法执行——比如一条指令需要访问多个页面如果同时能放在内存里的页面不够多这条指令就会反复触发缺页永远无法完成。物理块分配算法有三种常见方式平均分配算法把系统中可用的物理块总数除以进程数每个进程分到相同数量的物理块。简单粗暴但完全没考虑进程的实际需求——一个需要大量内存的数据处理进程和一个只做简单计算的小进程拿到的块数一样多显然不合理。按比例分配算法根据每个进程的大小所需页面数按比例分配物理块。进程越大分到的物理块越多。比如进程A有100页进程B有20页系统有60个可用物理块那A分到50个B分到10个。这比平均分配合理得多。考虑优先权的分配算法在上述基础上还考虑进程的优先权。优先级高的进程可以多分配一些物理块减少其缺页次数从而提高其响应速度。优先级低的进程少分配一些缺页多一些运行慢一些——这在实时系统或有明确优先级区分的场景中很有用。2.5 页面调入策略解决了分多少块的问题后接下来是页面怎么调入内存。何时调入页面有两种策略预调页策略在页面还没被访问之前就根据预测将其提前调入内存。理论依据是局部性原理——如果程序刚访问了第5页很可能紧接着就要访问第6页、第7页。操作系统可以把这些预计不久会被访问的页面提前加载进来减少缺页次数。但预调页的难点在于预测的准确性预测错了就白占了物理块还做了无用的磁盘I/O。请求调入策略最简单直接的方式——只有在真正发生缺页中断时才把需要的页面调入内存。不预测不提前加载需要什么才去拿什么。实现简单但每次缺页都要付出一次磁盘I/O的代价第一次访问时延迟较大。大多数现代操作系统采用请求调入为主、预调页为辅的混合策略。从何处调入页面取决于系统对换区的空间情况系统拥有足够的对换区空间时所有需要的页面都从对换区调入。对换区是磁盘上专门划出来做页面交换的区域访问速度比文件区快因为连续分配不需要像文件区那样追踪碎片化的存储空间。系统缺少足够的对换区空间时对于不会被修改的文件比如只读的程序代码直接从文件区换入换出——反正不会被修改不需要在对换区备份。对于会被修改的数据页面仍然从对换区调入调出。未运行过的页面都从文件区调入——这是UNIX方式。UNIX系统的设计思想是程序第一次运行时的页面直接从可执行文件文件区读入之后如果被换出到对换区后续再从对换区调入。这避免了一开始就把整个可执行文件拷贝到对换区的开销。页面调入的过程当发生缺页时操作系统按照如下流程操作首先检查所需的页面在磁盘上的位置从页表的外存地址字段获取然后在内存中寻找空闲物理块或按置换算法淘汰一个页面腾出空间接着启动磁盘I/O将页面读入物理块最后更新页表项填入物理块号、状态位置1、修改位清零并唤醒等待该页面的进程。缺页率的计算缺页率是衡量虚拟存储器性能的关键指标。定义上缺页率 缺页中断次数 / 总页面访问次数。在计组那篇文章里我们分析过缺页时CPU要等待磁盘I/O时间是正常内存访问的十万到百万倍所以即使缺页率很低比如1%也会显著拖慢有效访问时间。有效访问时间的计算公式可以表达为有效访问时间 (1 - p) × 内存访问时间 p × (缺页处理时间)其中p就是缺页率。这个公式直观地说明了为什么操作系统要千方百计地降低缺页率——每降低一点点p性能提升都是巨大的。三、页面置换算法谁该被踢出去当主存已满、必须换出一个页面时选谁就成了核心问题。选得好缺页率低、性能高选得差频繁换进换出系统可能比蜗牛还慢。这就是页面置换算法要解决的问题。3.1 最佳置换算法OPTOPT算法的思路堪称上帝视角选择那个将来最长时间不会被访问的页面淘汰。换句话说它所选择的被淘汰页面是以后再也不用了或者最久以后才会用到的。这个算法理论上是最优的能保证最低的缺页率。但它不可能实现——因为没有人能预知未来操作系统不可能提前知道一个页面什么时候会被再次访问。它的存在意义是作为一把标尺其他所有算法的实际缺页率都拿来和OPT比较看看离理论最优差多远。3.2 先进先出页面置换算法FIFOFIFO非常简单粗暴总是淘汰最先进入内存的页面。谁在内存里待得最久谁就走人。实现上只需要维护一个队列新调入的页面排在队尾要淘汰时就把队头的页面踢出去。逻辑清晰代码好写。但FIFO有个臭名昭著的问题——Belady异常Beladys Anomaly给进程分配的物理块数增加了缺页率反而升高了这完全反直觉——更多的内存怎么反而更差举个例子3个物理块时缺页9次4个物理块时反而缺页10次。这是因为FIFO不考虑页面的使用频率可能把正在频繁使用的老页面给换出去了。FIFO在实际中很少单独使用因为它的性能实在不够看。3.3 最近最久未使用算法LRULRU是工程实践中最常用的算法之一选择最近最久没有被访问过的页面淘汰。它的理论基础还是局部性原理——如果一个页面很久没被访问了那它未来被访问的可能性也较小。LRU比FIFO聪明的地方在于FIFO看的是谁先来LRU看的是谁最近没人理。一个很老的页面如果一直被频繁访问LRU不会动它而FIFO会毫不犹豫地把它踢掉。LRU需要硬件支持来记录每个页面的最近访问时间或访问顺序。常见的硬件实现方式有两种移位寄存器方式每个页面对应一个移位寄存器每次访问页面时把寄存器最高位置1然后定期右移一位。这样最近访问过的页面其寄存器值较大最久未访问的页面寄存器值最小。置换时选寄存器值最小的那个。栈方式维护一个页面访问栈每次访问一个页面就把它移到栈顶。栈底的页面就是最近最久未使用的置换时直接选它。这种方式的好处是栈底的页面始终是最该被淘汰的不需要遍历。LRU的性能接近OPT但硬件开销不小。实际系统中常用的是它的近似实现——Clock算法。3.4 最少使用算法LFULFU的思路和LRU不同选择近期被访问次数最少的页面淘汰。它关注的不是最后一次访问是什么时候而是总共被访问了多少次。LFU的逻辑是一个被频繁访问的页面大概率是重要的应该留在内存里一个只被访问过一两次的页面换出去损失不大。实现上每个页面对应一个计数器每访问一次就加1。置换时选计数器值最小的页面。但LFU有个问题一个页面可能在过去被频繁访问但现在不用了它的计数器值仍然很高导致LFU迟迟不淘汰它——这叫计数器老化问题。解决办法是定期将计数器右移一位除以2让老的访问记录逐渐衰减。3.5 简单的Clock算法最近未使用算法 NRUClock算法是LRU的一种近似实现也叫最近未使用算法。它只需要每个页表项有一个访问位0或1不需要复杂的计数器或栈。工作原理是这样的把所有在内存中的页面组织成一个循环链表想象一个钟表盘配一个指针。当需要置换时从指针当前位置开始扫描如果当前页面的访问位为0——恭喜它就是被淘汰的对象指针前进一位。如果当前页面的访问位为1——说明它最近被访问过饶它一命把访问位清零指针继续往前找。这样转一圈之后所有被访问过的页面的访问位都被清零了第二轮扫描时它们就可能被淘汰。本质上是用访问位来粗略地近似最近是否使用过。3.6 改进型Clock算法简单Clock只看访问位但有一个重要信息被忽略了——这个页面有没有被修改过前面说过没被修改过的页面换出去时不需要写回磁盘直接丢弃就行而被修改过的页面必须写回磁盘开销大得多。改进型Clock算法同时考虑访问位A和修改位M把所有页面分成四类(0, 0)最近未访问、未修改——最佳淘汰对象换出去零开销。(0, 1)最近未访问、但修改过——不太好的选择因为需要写回磁盘。(1, 0)最近访问过、未修改——可能很快又会被访问不太想动它。(1, 1)最近访问过、且修改过——最差的选择既要写回磁盘又可能马上再被用到。算法按优先级进行多轮扫描第一轮找(0,0)找不到就第二轮找(0,1)并写回磁盘再不行就第三轮找(1,0)……依此类推。改进型Clock减少了磁盘I/O次数优先淘汰不需要写回的页面但算法的开销增加了需要多轮扫描。这是操作系统中经典的用CPU时间换I/O时间的权衡。3.7 页面缓冲算法页面缓冲算法在页面置换的基础上引入了两个额外的链表空闲页面链表和修改页面链表。空闲页面链表系统预先维护一批空闲物理块。当需要调入新页面时直接从空闲链表中取一个物理块而不是先去淘汰一个旧页面再腾出空间。这样做的好处是新页面可以立即调入不需要等淘汰操作完成提高了响应速度。被淘汰的页面的物理块则被加入空闲链表末尾供以后使用。修改页面链表当被换出的页面被修改过时脏页不立即写回磁盘而是先挂到修改页面链表上。系统在空闲时或修改链表积累到一定数量时再批量写回磁盘。批量写入比逐个写入效率高得多减少了磁盘I/O的次数。页面缓冲算法的核心思想是把置换的开销摊薄——调入快有空闲块预备着写回快脏页攒一批再写。3.8 访问内存的有效时间在引入虚拟存储器之后CPU访问内存的有效时间变得复杂了因为不再是简单的查表→取数据两步走而是可能出现多种情况。在计组那篇文章里我们分析过TLBCache的完整访存路径。这里从操作系统的角度来总结三种典型情况情况一快表命中 数据在内存中——这是最快的情况。TLB命中后直接拿到物理块号然后访问主存或Cache获取数据。有效访问时间 ≈ 快表访问时间 一次内存访问时间。情况二快表未命中慢表命中 数据在内存中——TLB没找到得去主存里查页表慢表多了一次内存访问。有效访问时间 ≈ 快表访问时间 慢表访问时间一次内存访问 一次数据内存访问。情况三慢表也未命中缺页——这是最糟糕的情况需要触发缺页中断操作系统介入进行磁盘I/O。磁盘访问的时间是内存访问的十万到百万倍有效访问时间中缺页处理的开销占据了绝对主导地位。所以虚拟存储器的性能关键就在于缺页率。缺页率越低有效访问时间越接近前两种情况缺页率一旦飙升性能就会断崖式下跌——这就引出了下一个话题抖动。四、抖动与工作集当虚拟存储器抽风的时候4.1 多道程序度与处理机利用率操作系统通过多道程序设计来提高资源利用率——让多个进程同时驻留在内存中一个进程等I/O的时候CPU可以切换去执行另一个进程。多道程序度同时驻留在内存中的进程数越高理论上CPU利用率越高。确实一开始随着进程数目增加处理机的利用率急剧增加——CPU几乎不闲着总有活干。但当进程数增加到某个点之后CPU利用率到达最高点然后不是继续上升而是缓慢下降。这个下降就是抖动Thrashing的信号。4.2 产生抖动的原因抖动的根本原因是系统中运行的进程太多分配给每一个进程的物理块太少导致每个进程都在频繁发生缺页中断。想象一下你有100个物理块放5个进程每个分到20个块勉强够用。但如果你放了50个进程每个只能分到2个块——连程序的基本工作集都放不下。于是每个进程刚调入一个页面马上又要换出另一个页面来腾地方。CPU大部分时间都在处理缺页中断、做页面的换进换出真正执行用户指令的时间少得可怜。系统就陷入了忙得团团转但什么也没干的状态——这就是抖动的直观表现。就像你在图书馆同时复习5门课但桌上只有1本书的位置你得不停地去书架换书最后一下午过去了每门课都翻了个封面。4.3 工作集工作集Working Set是解决抖动问题的理论工具。它的定义是在某段时间内进程实际所需访问页面的集合。简单说就是——这个进程此刻真正需要的那几页。工作集理论和局部性原理紧密相关一个进程在某段时间内可能集中访问某几个页面形成工作集过了这段时间又转向另一组页面。一个重要的观察是进程发生缺页的时间间隔与进程所获得的物理块数有关。当分配的物理块数很少时缺页频繁发生随着物理块数增加缺页间隔迅速变长。但当物理块数超过某一阈值后再增加物理块对缺页率的影响就不再明显了——因为工作集已经全部装下了多给物理块也只是浪费。所以操作系统应该按照工作集的大小来分配物理块——给够了就行给多了浪费给少了抖动。4.4 抖动的预防方法操作系统有几种策略来预防或减轻抖动采用局部置换策略每个进程只能在自己的物理块范围内进行置换不能抢占其他进程的物理块。这样即使某个进程在频繁缺页也不会偷走别人的物理块导致连锁反应。与之相对的是全局置换——所有进程共享一个物理块池谁需要谁抢容易引发雪崩式的抖动。把工作集算法融入到处理机调度中调度器在决定是否让一个新进程进入内存之前先估算它的工作集大小检查当前剩余物理块是否足够。如果不够就不让新进程进来或者先挂起一个当前进程。这样保证每个在运行的进程都有足够的物理块。利用LS准则调节缺页率LS准则是一种动态调节策略。其中L是缺页间隔的平均时间S是处理一次缺页中断的平均时间。如果L S说明缺页不频繁可以增加多道程序度如果L S说明缺页太频繁了处理缺页的时间比不缺页的时间还长必须减少多道程序度。这个准则让系统自动寻找一个平衡点。选择暂停的进程当检测到抖动时操作系统可以选择挂起某些优先级较低的进程回收它们的物理块分配给其他进程。等抖动缓解后再恢复被挂起的进程。抖动的后果不仅仅是CPU利用率下降——抖动后缺页中断处理时间延长会增加就绪队列的等待时间延长其他进程的缺页中断处理时间形成恶性循环。所以预防抖动是操作系统存储管理中的重要课题。五、请求分段存储管理方式按逻辑来管理分段存储管理在之前的存储器管理文章中已经介绍过。这里讲的是它的升级版——请求分段也就是把分段和虚拟存储器结合起来。请求分段的核心思想和请求分页一样不需要把整个程序的所有段都装入内存只需要当前需要的段在内存中即可运行需要某个段时再从磁盘调入。5.1 硬件支持请求段表机制请求分段的页表——哦不段表——比基本分段系统的段表要丰富得多。每个段表项包含以下字段段号段的编号作为段表的索引。段长该段的长度字节数用于越界检查。段在内存中的起始地址如果段在内存中这个字段记录它的物理起始地址。存取方式规定该段的访问权限——是只读、读写、还是可执行。这为段级保护提供了基础。访问字段和请求分页中的访问字段类似记录该段被访问的次数或频率供置换算法参考。修改位表示该段调入内存后是否被修改过。换出时修改过的段需要写回磁盘没修改过的直接丢弃。存在位和请求分页中的状态位完全一样——表示该段是否在内存中。存在位为1则在内存为0则在磁盘上。增补位这是分段独有的一个字段因为段的长度是可以动态增长的比如数据段可能越来越大增补位用来标记该段在调入内存后是否被扩充过。如果被扩充了换出时需要把扩充后的完整段写回磁盘而不是原来那个短版本。这个字段是分段系统特有的分页系统不需要因为页面大小是固定的。外存地址该段在磁盘上的存储位置调入时根据此地址去磁盘上找。5.2 缺段中断机制和请求分页中的缺页中断类似请求分段系统中有缺段中断当CPU访问一个段而该段的存在位为0不在内存中时硬件产生缺段中断通知操作系统把需要的段从磁盘调入内存。处理流程与缺页中断类似保存CPU现场→查找该段在磁盘上的位置→在内存中分配空间可能需要淘汰其他段来腾地方→从磁盘读入该段→更新段表存在位置1填入起始地址→恢复CPU现场重新执行被中断的指令。一个有趣的区别是分页系统中换进换出的单位是固定大小的页面而分段系统中换进换出的单位是大小不一的段。这意味着分段系统在分配内存空间时需要处理可变大小的分配问题——和连续分配存储管理中的那些算法首次适应、最佳适应等又产生了联系。分段的地址变换过程也与分页类似5.3 分段的共享和保护分段的一大优势是天然适合共享和保护——因为段是按逻辑意义划分的一个函数库可以单独放在一个段里供多个进程共享。共享段表系统维护一张共享段表记录当前被多个进程共享的段的信息。每个共享段表项包含共享进程计数记录有多少个进程正在共享这个段。只有当共享计数降为0时所有进程都不用了这个段才会被真正回收。存取控制字段规定每个共享进程对该段的访问权限——有的进程可以读写有的只能读有的只能执行。不同进程对同一个共享段可以有不同的权限这非常灵活。段号在不同的进程中同一个共享段可以有不同的段号因为每个进程有自己的段表。共享段表记录了各个进程使用的段号与共享段的对应关系。共享段的分配和回收当第一个进程请求使用某个共享段时系统把它从磁盘调入内存并在共享段表中登记共享计数设为1。后续其他进程请求同一个共享段时不需要再调入只需在共享段表中找到它共享计数加1并在该进程的段表中建立指向共享段的表项。回收时共享计数减1只有减到0时才真正从内存中移除。5.4 分段保护分段系统提供了多层次的保护机制越界检查每次地址变换时硬件都会检查段内偏移是否超过了该段的段长。如果超过产生越界中断——你的程序想访问别人的地盘了系统立刻拦截。这和在计组那篇文章中讲的页号越界检查道理相同但分段系统中每个段的长度不同所以每个段都需要单独做越界检查。存取控制检查在越界检查通过后硬件还要检查当前操作是否符合该段的存取方式。比如对一个只读段执行写操作就会被拦截。这防止了程序意外或恶意修改不该修改的数据。环保护机构这是一种更高级的保护机制源自Multics系统的设计思想。系统把程序的执行环境分成若干个保护环Ring内层环Ring 0权限最高外层环权限递减。操作系统的内核运行在Ring 0用户程序运行在Ring 3。不同环之间的访问有严格的规则——内层可以访问外层的数据外层不能直接访问内层的数据。段表中的存取方式字段可以配合环保护来实现精细的权限控制。六、分页 vs 分段 vs 段页式三者的联动对比到这里我们把虚拟存储器的三种实现方式都讲完了。来做一个系统性的对比这也是和计组那篇文章的一个联动总结。6.1 请求分页 vs 基本分页计组那篇文章讲的页式管理更偏理想态——假设页表已经在内存中重点分析地址转换和TLB加速。而操作系统这里讲的请求分页是现实态——页面可能不在内存中需要处理缺页中断、页面置换。请求分页在基本分页的基础上增加了状态位、修改位、访问字段、外存地址等字段以及缺页中断机构和页面置换算法。6.2 分页 vs 分段的本质区别在之前的存储器管理文章中我们已经做过一次对比这里从虚拟存储器角度再补充几点置换粒度不同分页系统换进换出的是固定大小的页面比如4KB分段系统换进换出的是大小不一的段。固定大小的页面更容易管理分配、回收都简单但不利于共享和保护可变大小的段逻辑清晰、便于共享但内存管理更复杂有外部碎片问题。共享粒度不同分页系统的共享以页为单位——你想共享一个函数但它可能横跨两个页面共享了页面又可能泄露同页的其他数据。分段系统的共享以段为单位——一个函数一个段精确共享干净利落。缺页 vs 缺段分页系统产生缺页中断分段系统产生缺段中断。处理流程类似但由于段的大小不固定缺段中断后在内存中为调入段分配空间的操作更复杂。6.3 段页式虚拟存储器段页式把分段和分页结合在一起先将程序分成若干段按逻辑再把每个段分成若干页按固定大小。系统同时维护段表和页表段表记录每个段的页表起始地址页表记录每页对应的物理块号。地址变换过程需要更多的内存访问先查段表找到该段对应页表的起始地址再查页表得到物理块号最后拼接块内偏移得到物理地址。这意味着至少需要两次内存访问段表页表才能真正取到数据比纯分页或纯分段都多一次。段页式需要更多的硬件支持段表寄存器、页表基址寄存器、TLB等速度上较慢。但它综合了两者的优点既有分段的共享和保护能力又有分页的消除外部碎片能力。在实际的操作系统中如早期的Multics和某些Unix变体段页式确实被采用过。现代主流操作系统Linux、Windows主要采用的是纯请求分页方案因为页表机制已经足够成熟加上多级页表和反向页表的技术分页系统在共享和保护方面也能做得很好。段的概念更多地体现在程序的逻辑结构中代码段、数据段、栈段而不是内存管理的物理层面。七、写在最后两门课的拼图回顾一下这两篇文章的关系计组那篇文章我们从硬件出发讲了CPU发出的虚拟地址如何经过MMU查页表、经过TLB加速、结合Cache完成数据访问的完整路径。我们画出了TLB命中Cache命中的最短路径和TLB缺失Cache缺失缺页的最长路径。重点在底层机制和性能分析。操作系统这篇文章我们从软件出发讲了操作系统如何管理页表请求页表的各个字段、如何处理缺页中断、如何分配物理块固定分配、可变分配、平均/比例/优先权算法、页面调入策略预调页、请求调入、对换区vs文件区、如何选择置换页面八种置换算法、如何预防抖动工作集理论、以及请求分段的共享和保护机制。重点在管理策略和决策算法。这两门课看的是同一个系统的不同层面。硬件负责怎么找到数据操作系统负责数据该不该在内存里。没有硬件的地址变换机构虚拟存储器无法实现没有操作系统的置换策略和管理机制虚拟存储器没有意义。下次你写代码时想想你访问一个变量背后经历了多少层关卡虚拟地址→快表→页表→缺页中断→磁盘I/O→物理地址→Cache→数据。而操作系统在背后默默做的所有决策——换谁出去、留谁进来、怎么防抖动——你完全感知不到。这就是工程的浪漫把复杂藏在底层把简单留给用户。觉得有用的话别忘了点赞、在看、转发三连~ 和上一篇计组版的虚拟存储器搭配食用效果更佳有问题欢迎在评论区讨论