配套教材:
Operating Systems: Three Easy Pieces Remzi H. Arpaci-Dusseau Andrea C. Arpaci-Dusseau Peter Reiher
参考书目:
1、计算机操作系统(第4版) 汤小丹 梁红兵 哲凤屏 汤子瀛 编著 西安电子科技大学出版社
在线阅读:
http://pages.cs.wisc.edu/~remzi/OSTEP/
University of Wisconsin Madison 教授 Remzi Arpaci-Dusseau 认为课本应该是免费的。
————————————————————————————————————————
这是专业必修课《操作系统原理》的复习指引。
需要掌握的概念在文档中以蓝色标识,并用可读性更好的字体显示 Linux 命令和代码。代码部分语法高亮。
文档下载地址:
链接:https://pan.baidu.com/s/1bo5CoLYPmBeVZ9i_9nOPMA
提取码:0000
六 交换
当内存不足以放置所有进程的地址空间时,就需要将一部分页面移动到硬盘上。早期的系统中,程序员需要手动移动暂时不需要的页面。也就是说,每次调用函数或访问数据时,都要手工检查被访问的内容是否在内存中。现代操作系统会自动帮我们完成这项工作。硬盘上的一部分空间被保留下来,专门用于暂存不太紧要的内存页,这部分空间称为交换区(swap space)。
页表中有一位存在位(present bit),用于刻画该页是否正位于内存中。如果试图访问一个不在内存中的页,就会发生页错误(page fault),也称页缺失。OS会运行页错误处理程序(page-fault handler)进行处理。一般地,OS会把位于交换区的页移回内存。
为了确保顺利进行页交换,OS就需要记录处于交换区的页的磁盘地址(disk address)。这个地址可以记在页表里,也可以在磁盘上建立类似于页表的结构来记录。页从磁盘回到内存时,OS就更新页表中相应虚拟地址的物理页号,然后重试指令。这次重试可能会引发TLB缺失,于是TLB中的相应内容也被更新。再一次重试时,就能直接从TLB中取得回到内存中的页的物理地址了。I / O的开销相比内存内的操作大得多,因此OS一般会将进程I / O和进程切换尽量同时处理,以充分利用硬件。
页缺失的代价是非常大的,可达数百万个CPU周期。
顺带说一下,虚拟内存中的一些术语可能有些令人迷惑。例如,page fault有时可能也会指非法访问(尝试写入受保护的页面,等等)。尝试访问一个在硬盘交换区而不在内存中的页面,并不是非法操作,所以称之为page fault有些奇怪。我们怀疑,使用page fault而不是page miss的原因可能是,当尝试访问被移入交换区的页面时,硬件不知道怎么处理,只是产生异常,并简单地将控制权交给操作系统。这与进程做出非法操作时的处理方法相同,所以说,使用page fault这个术语也有一定道理。
硬件一般不处理页错误。虽然硬件工程师对操作系统包揽大部分事务这一点常常持有不信任的态度,但页错误仍被交由操作系统处理。这是因为:
(1)磁盘相比内存是非常慢的。就算OS执行处理页错误的其它命令花费很长时间,一般也远远少于比将请求的页从磁盘调回到内存花费的时间,所以对总体的运行性能影响十分小,无需通过硬件进行专门的加速。
(2)如果要通过硬件来处理,那么硬件工程师势必要弄清楚交换区的具体实现原理,搞清楚磁盘IO怎么处理并实现,以及许多他们一般不知道的细节。总的来说,为了平衡性能和开发的难易程度,就由OS处理页错误。
内存已满的时候,必须进行交换;但是内存未满的时候也可能会交换。许多操作系统一般有两个阈值:High watermark(HW)和Low watermark(LW)。当可用的页数小于LW时,就会有一个后台(background)进程启动,将部分页面移动到交换区,直到可用页数达到HW。这个程序叫做交换守护进程(swap daemon),又称页面守护进程(page daemon)。
缓存缺失(cache miss)分为三种:
(1)冷启动缺失(cold-start miss),也称强制缺失(compulsory miss),是指系统刚启动、缓存为空时必定发生的未命中现象。对一个从未放入缓存的对象进行访问,在缓存中肯定是找不到这个对象的。
(2)容量缺失(capacity miss),是指缓存容量已满时又遇到未访问过的内容造成的未命中。此时必须将缓存中已有的一项移除。
(3)冲突缺失(conflict miss),也称碰撞缺失(collision miss),只在组相联(set-associative)映射和直接映射(direct-mapped)的cache中存在。由于一些内容只能被写入特定的缓存区域,当被指定同一个写入区域的不同内容写入缓存时,先写入的会被移除。下一次访问原来先写入的内容时,记一次冲突缺失。
更多内容参见专业必修课《计算机组成原理》。
当需要把内存页交换到交换区时,应该正确选择交换的页面,否则内存的速率就会被拖慢成磁盘的速率。最理想的状况,自然是交换在未来被使用的次数最少的页(Bélády算法),但是我们没有办法预测未来。所以,这个思路虽然简单,但难以实现。不过,可以以该算法为参照,去评价其它的页面置换算法。这里介绍常见的选择交换页面的几个算法:
(1)先进先出(FIFO)。此方法十分容易实现,但最坏情况的表现显著差于均值。FIFO与通常的页面使用规律不符,因此其实际表现很差,应用极少。甚至,以某些特定的顺序进行访问时,使用FIFO算法的缓存在缓存容量变大(缓存的使用率会相对变小)时,命中率反而会下降(Bélády异常)。对于交换的情形,由于内存的速度比硬盘快若干个数量级,因此可以把内存视为硬盘的“缓存”。FIFO算法产生Bélády异常的原因是:策略死板,没有考虑到程序的执行情况是动态的、可变的。
(2)随机(Random)。这类算法亦属于较容易实现的一类。
(3)最少频率使用(LFU)。该算法尽可能去解决FIFO和随机算法这样的无状态算法可能把常用的页对应的条目踢出去的问题。根据局部性原理,一般认为,最近访问的页被再次访问的可能性比较早访问的页更大;如果数据在最近一段时间内使用次数很少,那么将来被使用的次数也大概率很少。
最近最少使用(LRU)与其类似。不过,LRU淘汰的是最长时间没有被使用(即便该页在最后一次访问之前被频繁访问)的页面,而LFU淘汰的是一段时间内使用次数最少(即便该页刚被使用过)的页面。
此外,LRU和LFU分别都有与其完全相反的算法:最近最多使用(MRU)和最多频率使用(MFU)。自然地,在绝大多数情况下,由于它们违反了局部性原理,因此效果并不好。但对于某些特定的场景,也许可以采用。
在完全随机(不存在局部性)的访问请求下,这几种常见算法的命中率几乎没有区别。但是如果我们将“二八定律”套用过来,即描述一种更接近实际使用的情况:绝大多数(约80 %)的访问只访问少数(约20 %)的数据;少数(约20 %)的访问访问绝大多数(约80 %)的数据。这时候,情况就变得不一样了:
由图可知,LRU算法的命中率表现优于FIFO和随机算法。但是这样的命中率在实际使用中会反映多少提升?答案是:看情况。如果一次未命中带来的重新查找并访问的开销十分大,提升就会更明显。反之,提升就不太大。
但有一种极端情形,LRU和FIFO的表现都很差:循环访问。例如,交换区能记录24页,且当前内存接近耗尽,必须进行交换。如果需要循环访问25页的内容,前24页都已经在交换区,那么在接下来继续执行循环时,程序每访问一页就要把这一页从交换区移至内存,然后在后续的访问中总是把先进内存的移到交换区(或把先到交换区的先移入内存,这种情况中,先进内存或先进交换区的页,正好也是最长时间没有被使用的页),于是命中率就是0 %。这种情况下,随机算法的反而具有大于0的命中率。当然如果内存空间剩余比较多,那么就无需进行交换,所有需要访问的页都在内存中,命中率就达到100 %。值得警惕的是,这样的负载在许多应用(比如数据库)中,都是常见的。
如果想要实现LRU等需要通过历史记录来选定移动哪一页到交换区的算法,那么每次内存访问都要做额外的工作,这是十分影响性能的。为了加速,一般需要硬件支持。另外,我们不一定总是要获得最优解:如果能用更少的时间获得次优解,那么这也不失为一个很好的解决方案。
用一个使用位(use bit)或称引用位(reference bit),来标记一页是否被访问过。硬件不会将该位置零,OS才可以。一种选择次优解的实现方式是时钟算法(clock algorithm),也称最近未使用算法(Not recently used,NRU)。一个指针开始时指向任意的页。当需要将部分页面移到交换区时,检查当前指向的页的引用位是否为1。如果为1,则继续查找其它页,直到找到一个使用位为0的页位置。如果所有的页都被使用过,那么把使用位全部清零。
在80-20工作负载中,一种时钟算法的变体表现不错:
这种变体算法在发现一个使用位为1的位时,就将其使用位清零;如果发现一个使用位为0的位,就将其移到交换区。可以看出它和LRU算法的表现十分接近。
现在操作系统还对时钟算法等LRU近似算法做出了其它的一些调整。比如,扫描阻抗(scan resistance)是许多现代算法的重要组成部分,例如自适应替换缓存(Adaptive replacement cache,ARC)。扫描阻抗算法通常类似于LRU,但也尝试避免LRU的最坏情况。
也可以根据一个页面是否已修改来决定是否将其移至交换区。修改位(modified bit)也称dirty bit,用于描述一个页面是否被修改过。如果否,则优先移至交换区。因为将未修改的一页移至交换区实际上可以优化成:不写入硬盘,而是直接用于其它事务(当以后需要此页面时再重新读入内存)。引入修改位后,时钟算法就可以改进为:先交换未修改且未访问的页面,当无未修改的页面可交换时再交换已修改但(后来)未访问的页面。
多数情况下,在一页被访问时,如果此页在交换区,OS就把它移入内存。但是OS也可以猜测哪些页在接下来更可能被访问,并将它们一起读入内存,这个机制称为预读(prefetching)。例如,当交换区中的某一页被访问时,将其附近的页一并读入内存。同理,在写入交换区的时候,OS一般不会将少数的页立刻写入,而是将写入延迟,直到凑齐一定数量后,才统一写入硬盘。这样就可以大幅改善写入速率。这是因为磁盘写入小文件的速率远远慢于写入大文件(参见第12章)。很多情况下,请求将数据写入硬盘时,写入也会被延迟,直到要求存盘的数据足够多再写入。对某些情况(例如刚刚请求存盘的文件被删除了),就可以直接在快得多的内存中操作,而无需访问硬盘。
当请求建立一组新进程时,如果可用的内存(包括交换区)不足,OS应该拒绝一部分进程的运行。这个机制称为接纳控制(admission control),也称准入控制。无论是在现代操作系统还是在实际生活中,把少量的事情做好,远远好于同时做大量的事情但做得不好。新的系统对内存不足的预防更为严苛。例如,一些Linux版本在内存不足时还会杀掉占用内存较高的进程。当然这个方法并不是没有问题的:有时候X Window System会被干掉,于是所有需要显示位图的进程都无法继续显示。Android系统也是基于Linux的,相信大家常常发现在运行较多程序后,有许多后台进程被杀,需要重新运行,或者进程的一部分内容需要重新载入(举例:浏览器的部分网页)。
页面缓冲算法(Page buffering algorithm,PBA)的主要特点是:
① 显著地降低了页面交换的频率,使磁盘I / O大为减少,因而减少了开销;
② 正是由于换入换出的开销大幅度减少,才能使其采用一种较简单的置换策略,如FIFO。这使它不需要特殊硬件的支持,实现起来非常简单。
页面缓冲算法己在不少系统中采用,下面介绍VAX / VMS操作系统中所使用的页面缓冲算法。该系统为每个进程分配一定数目的物理块,系统保留一部分空闲物理块。为了显著降低页面换进、换出的频率,在内存中设置了如下两张链表:
(1)空闲页面链表
该链表是空闲物理块链表,被系统掌握,用于分配给频繁缺页的进程,降低其缺页率。当这样的进程需要读入一页时,可利用链表中的第一个物理块来装入该页。当有未被修改的页要换出时,并不将它写入外存,而是把所在的物理块挂在空闲链表的末尾。注意:这些挂在空闲链表上的未修改页面中是有数据的。以后需要这些页面中的数据时,可从空闲链表上将它们取下,免除了读磁盘操作,减少了页面换进的开销。
(2)修改页面链表
它是由己修改页面形成的链表。设置该链表的目的是为了减少己修改页面换出的次数。当进程需要将一个己修改的页面换出时,系统并不立即把它换出到外存上,而是将它所在的物理块挂在修改页面链表的末尾。这样做的目的是:降低将己修该页面写回磁盘的频率,降低将磁盘内容读入内存的频率。
总之,PBA算法的本质,是将物理内存划出一部分作为交换区,使得在进行交换时,不用总是访问比内存慢得多的硬盘。
交换机制不但可以以页面为单位进行交换,还可以将整个段或者整个进程在内存中的数据一同进行交换。一般来说,阻塞的进程可能暂时被调入交换区(并且,有多个阻塞的进程可以移动到硬盘交换区时,选择优先级最低的进程进行对换;有的系统还会考虑进程在内存中的驻留时间,防止进程被调入内存后又很快被换出),而进程状态为就绪但存在于交换区的进程,也会在适宜时被重新换入内存。注意:如果一个进程有部分数据为其它进程所需,那么将该进程移入交换区时,需要共享的部分不能被移动。
交换区的空间分配与回收方式,与内存空间的分配与回收方式雷同。
当内存不足时,CPU的利用率会降低,严重时甚至趋于0,这种情况称为“抖动”(“颠簸”)。发生页面抖动的根本原因是:内存不足时,由于同时运行的进程太多,分配给每个进程的物理内存太少,不能满足正常运行的基本要求,致使大量进程在运行时频繁缺页,必须请求将所缺之页调入内存。这会使得在系统中排队等待页面交换的进程数目增加。显然,磁盘访问也会急剧增加,造成每个进程的大部分时间都用于页面的换进换出,而不去做任何有效的工作,从而导致CPU的利用率急剧下降并趋于0的情况。我们称此时的进程是处于“抖动”状态。
“抖动”是很严重的问题,必须采取措施解决。为此有不少学者深入研究,提出了许多非常有效的方法。由于“抖动”的发生与系统为进程分配物理块的多少有关,于是有人提出了“工作集”的概念。
工作集(working set,或驻留集)是指:在某段时间间隔内,进程保留在内存中的页面集合(即给进程分配的物理内存块的集合)。可以把工作集记为w(t, Δt)。Δt为窗口大小(window size)。w(t, Δt)代表进程在时间段(t-Δt, t)内驻留在内存中的属于同一个进程的页面。
看下面的例子:
表格下方记录的内容比上方在时间上更晚;并且,所有画横线的地方代表“同上”。我们没有办法准确预知未来,所以只能根据程序在过去的访问情况,将最近被使用的页面放入工作集,而丢弃最近不被使用的页面。比如说,当窗口大小为3时,t = 3的时刻工作集包含页面18、15、24。在t = 4时,新访问页面23,但工作集已满,于是踢掉最早进入工作集的页面24。t = 7时,工作集包含页面18、17、24。在t = 8时再次访问页24,它包含在工作集内,所以工作集不用改动,继续保留原有页面。
页面抖动会严重影响性能。抖动的预防方法主要有:
(1)采取局部置换策略
如采取的是可变分配方式(驻留集大小可变)的页面分配与置换策略,则可采取局部置换策略:当进程缺页时,只能在分配给自己的内存空间内进行置换,不允许从其它进程去获得新的物理块。这样,即使该进程发生了“抖动”,也不会对其它进程产生影响,于是也许可以把该进程“抖动”所造成的影响限制在较小的范围内。该方法虽然简单易行,但效果不是很好。因为在某进程发生“抖动”后,它还会长期处在磁盘I / O的等待队列中等待页面交换,使队列长度增加,延长了其它进程缺页中断的处理时间,也就是延长了其它进程对磁盘的访问时间。
(2)把工作集算法融入到处理机调度中
当调度程序发现CPU利用率低下时,它将试图从外存调入一个新作业进入内存,来改善利用率。如果在调度中融入了工作集算法,则从外存调入作业之前,必须先检查每个进程在内存的驻留页面是否足够多(即:内存足够大,以至于所有进程需要的全体页面都可以放入内存)。如果足够多,此时便可调入新的作业,且一般不会而导致缺页率增加:反之,如果有些进程的内存页面不足,则应首先为缺页率居高的作业增加新的物理块,并不再调入新的作业。
(3)利用“L = S”准则调节缺页率
Denning于1980年提出了“L = S”的准则。其中L是两次缺页之间的平均时间间隔,S是平均缺页服务时间,即置换一页所需的时间。如果L远比S大,说明很少缺页,磁盘的能力尚未得到充分的利用;如果L比S小,则说明频繁发生缺页,缺页发生率己超过磁盘的处理能力。当L与S接近时,磁盘和处理机都达到它们的最大利用率。当然,实际使用中,不应该将磁盘的全部读写能力都用于处理缺页,因为计算机总是还需要干别的事情的。此外,如果频繁进行交换,对磁盘的寿命也会有影响。
(4)选择暂停的进程
当同时运行的程序过多且己影响到CPU的利用率时,为防止“抖动”,系统必须减少进程数。此时应基于某种原则选择暂停某些当前活动的进程,将它们调出到磁盘上,把腾出的内存空间分配给缺页率偏高的进程。系统通常采取与调度程序一致的策略,即首先暂停优先级最低的进程。当内存还显拥挤时,还可进一步选择暂停一个并不十分重要但较大的进程,以便能释放出较多的物理块,或者暂停剩余执行时间最多的进程等。
页面抖动的最根本原因是内存不足,使得内存的部分内容被迫移动至硬盘的交换区。在对性能要求较高的场景,应该确保内存容量足够,尽量不要引发交换。

本文主要梳理了操作系统中的交换概念,参考了Operating Systems: Three Easy Pieces等教材。内容涵盖交换区的分配与回收,与内存管理的相似性。适合作为《操作系统原理》课程的学习资料,重点概念以蓝色标记,附带Linux命令和代码示例,并提供了文档的下载链接。

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



