本系列博文是《现代操做系统(英文第三版)》(Modern Operating Systems,简称MOS)的阅读笔记,定位是正文精要部分的摘录理解和课后习题精解,所以不会事无巨细的全面摘抄,仅仅根据我的状况进行记录和推荐。因为是英文版,部份内容会使用英文原文。html
课后习题的选择标准:尽可能避免单纯的概念考察(如:What is spooling?)或者简单的数值计算,而是可以引发思考加深理解的题目。为了保证解答的正确性,每道题都会附上原书解答,而中文部分会适当加入本身的看法。原书答案下载地址(需注册) 程序员
注:本文部份内容须要读者对页式、段式、段页式内存管理有基本了解。 算法
交换技术(Swapping):内存紧缩、用于内存管理的位图和链表、匹配算法:首次匹配、下次匹配、最佳匹配、最坏匹配;缓存
虚拟内存:前身是手工完成的覆盖技术(overlays);内存管理单元MMU及地址转换:页表、TLB(转换检测缓冲区,也称为关联存储器,俗称快表)、多级页表;app
页面调度算法less
共享库(shared libraries)/动态连接库(DLL,Dynamic Link Libraries)dom
段式内存管理、段页式ide
若是以前学习过国内的操做系统教材,TLB通常被称为快表,而不是转换检测缓冲区(Translation Lookaside Buffer)或关联存储器(associative memory)。对于国内常见的称呼,容易知道它是作为整个页表的一个部分缓存,从而加快虚地址向实地址转换的速度。所以,它和页表的条目结构很相似。具体解释见于P195~197,另外段式存储管理中也可使用TLB加速访问。对于不一样地方出现的TLB,它缓冲的内容与其应用场景有关(MULTICS、Pentium等等)。能够看出,这个译名比较形象,不过原名更具体些。学习
本科时学习“操做系统”的"基本分页存储管理方式"时,对于64位计算机、单个页面4KB(即212B),两级页表已通过大而不能装入内存。汤子瀛版《计算机操做系统》介绍了2种处理方式:使用三级或以上的页表、将寻址空间下降到45位左右而不是64位。前者仍然比较繁琐,后者可行性比较高。那时我就对前者心存疑惑:虽然可以将须要用的页表调入内存,但是它们总大小仍然很大,有没有更好的实现?接下来看一看《现代操做系统》提供的方式:倒排页表。下面这部份内容取自原书P200~201的整理。this
先简述下问题所在:64位计算机中,若是页面大小为4KB,那么64位寻址空间须要252个页表项。假设每一个页表项大小为8B,那么须要30PB的空间,在目前计算机发展阶段显然是不现实的。
倒排页表是一种解决方案,正如其名所揭示的:普通的页表是将虚地址映射成物理地址,提供的是将页号(page number)转化为页框号(page frame number)的对应;这个转化由MMU完成;而倒排页表正相反,提供的是将页框号转化为页号的对应。页框号是实际内存的大小/页面大小,所以1GB内存只须要262,144个项便可,远远小于252这个数字。
我的认为,这个解决思路的妙处在于:32位下,页表项总和比较少,至多两级页表也已够用;而64位下,内存的大小(也即物理地址空间)与寻址空间(虚地址空间)相比反而显得小了,这样干脆来个倒转,不失为一个很好的方法。
固然这种解法虽然节约了大量的存储空间,可是内存管理中须要的是将虚地址转化成物理地址的机制,而不是正相反啊?这种映射是单向的,总不能每次转化都把这个表遍历一次吧?那样作的开销实在是太大了。
一种解决方法是使用TLB,可是TLB也有失效(miss)的时候,这时仍是须要进行查找。一个可行的方法是将全部用到的虚地址hash掉,造成一个hash表来加速查找,一样哈希值的虚地址造成一个链。若是hash表的槽数与物理内存的页框数同样多,那么哈希表各表项平均长度为1,这样提升了查找速度。这个解决方法的演变能够见下图3-14。
LRU,汤子瀛《计算机操做系统》译为“最近最久未使用”,也即在缓冲的全部页面中,缺页中断发生时,将最久未被使用的页面置换出去。不过按照字面意思,Least Recently Used彷佛应是《现代操做系统》中译版的“最近最少使用”,彷佛是须要统计页面使用频率的。这里有必要先探讨下这个翻译问题。这个翻译的区别在于,副词least修饰的是recently仍是used。P206原文:
A good approximation to the optimal algorithm is based on the observation that pages that have been heavily used in the last few instructions will probably be heavily used again in the next few. Conversely, pages that have not been used for ages will probably remain unused for a long time. This idea suggests a realizable algorithm: when a page fault occurs, throw out the page that has been unused for the longest time. This strategy is called LRU (Least Recently Used) paging.
这前半段和后半段意思并非很一致。按照前半段的意思,用的最多的(heavily used)最应该保留;然后半段,也即LRU的定义,反而是指“最久未使用”。假设这样一种状况,内存只能容纳两个页,若是考察的时间跨度大于2,对于0,0,...,0,1的页面访问序列,此时访问页面2,前半段会认为0的使用频率最高,应该保留0,然后半段认为0是最久未被使用的,应该保留1。这样就产生了矛盾。不过既然是一个近似,按后半段更合适一些,这样反而显得“最近最久未使用”是一个更合适的译法。《现代操做系统》提到的3种算法,其实都是符合定义的:
一种实现是用一个特殊链表,将最近最多使用的放在表头,最近最少使用的放在表尾,每次使用到的页面若是在链表中,就把它取出并放到表头。不过这个实现一方面很耗时,而且其实是“最近最久未使用”。
一种硬件实现是使用一个计数器,每次执行指令自增1,每一个页表项中提供一位来容纳这个值,每次访问时就把计数器的值存到访问的页表项中。淘汰页面时选择最小的便可。虽然很符合“最近最少使用”的含义,缺点是消耗了不少存储空间;另外,计数器的溢出也是个问题。一样是“最近最久未使用”。
另外一种硬件实现则比较精巧。对于n个页框的机器,提供一个n*n的矩阵,初始化为全0。当访问页框k时,将这个矩阵第k行全设为1,第k列全设为0,此时(k,k)是0。在任意时刻,哪一行的二进制数最小,那么它就是将被淘汰的页面。能够发现,这种作法中,最后被访问的页面会把它所在的行变为最大的(k列全为0,表明此列的大小影响不计;仅有k行全1,必然最大)。同时,页面的使用频率越高,那么它就能“保持”的较大。即便上文中探讨的做为0,0,...0,1这种序列,仍然是保持1替换0,仍是“最近最久未使用”。原书对这个实现的图3-17:
LRU的实现比较复杂,并且可能须要借助特殊的硬件。一种软件实现被称为NFU(Not Frequently Used,最不经常使用),每一个页面使用一个计数器,每次时钟中断时,将页面的R位(是否被引用,0或1)加到计数器上。缺页中断时置换计数器值最小的。
这种实现的坏处是它“从不忘记任何事情”,简单地说就是在以前计数器值比较高的页面,即便再也不访问,仍然会保持这个值;而别的页面在后续始终没法超过。对NFU作一个小修改,就能够很好的模拟LRU:将R位增长前先计数器右移、R位增长到计数器左边的最高位而不是右边的最低位。修改后的算法称为老化(aging)算法。图3-18是一个运行实例,其蕴含的特征是:越高位越新,最近的使用权重最大;早期的使用记录会随着右移而舍弃。
从中也可看出NFU与LRU的第一个区别:对于(e),LRU只能从3和5中二选一,3和5都在2个时钟前访问过,而NFU会明确地唤出3。另外一个区别是NFU只能追踪有限次(相较于LRU的前两种实现,要少一些),好比图中的8次。前第9次和前1000次的访问状况是可有可无的。
如何肯定页的大小?之前我只知道应该综合考虑,可是如何考虑?原书P219~220是一个很好的借鉴。
假设页面大小为p字节,内存中共有n个段。
首先考虑页内碎片。平均来看页内碎片占了页的1/2大小,那么一共浪费了np/2的空间。
页面越小,进程运行时所需内存越小,只需把足够的页面装载如内存便可;反之则越大。然而,页面越小,须要的页表项越多,页表也越大。在页传输这个数量级时,磁盘传输的时间主要花在寻道和旋转延迟上,页面大小不是关键因素,好比,装入64个512B页面须要64*10ms,而4个8KB可能只须要4*12ms。
进程切换时,也表也须要从新装载,页面越小,装入页表越耗时。
最后一点能够用数学分析。若是进程大小平均s字节,页面大小p字节,每一个页表项须要e字节,那么进程须要的页数为s/p,占用了se/p的页表空间, 页内碎片在最后一页浪费的是p/2,那么因为页表和页内碎片一共的所有开销为:
\[overhead = se/p + p /2\]
利用求导的方法,能够解出最小化开销overhead的p值:
\[p = \sqrt{2se}\]
对于s = 1MB和页表项为8B,最优页面大小是4KB。
这是我本科时学习操做系统的又一个疑惑。其实从技术角度来讲,是能够实现的;读完《现代操做系统》有了新的体会:页式更接近于硬件底层,对于程序员是透明的,你不多感觉到它的存在;而段这个概念就比较熟悉了,数据段、代码段、段保护机制这些常常被提起。段是逻辑抽象,更贴近人的思考方式而不是计算机的运做方式。页式、段式、段页式都是在现实中经常使用的,并且《现代操做系统》的做者Andrew提到:Pentium的设计者面对相互冲突的目标:纯页式、纯段式、段页式管理,高效而简洁的实现,至关值得称赞("
All in all, one has to give credit to the Pentium designers. Given the conflicting goals of implementing pure paging, pure segmentation, and paged segments,
while at the same time being compatible with the 286, and doing all of this efficiently, the resulting design is surprisingly simple and clean",如何使用段页式管理系统提供纯段式和纯页式请参考原书3.7.3节)
能够看出,对于软件设计,应该让逻辑层在较高的位置,贴近硬件的机制在较低的位置——这就是段页式的处理;而不是相反,也就没有了使用所谓的页段式的必要了。
译:
为何VAX上的TLB没有R位?
Answer:
The R bit is never needed in the TLB. The mere presence of a page there means the page has been referenced; otherwise it would not be there. Thus the bit is completely redundant. When the entryis written back to memory, how-ever, the R bit in the memory page table is set.
分析:
题目提到VAX这个机型实际上是误导。通常页表中都有R位(Referrence,最近是否访问过)和M位(Modified,最近是否修改过),这两位在一些调度算法中要用到;而TLB中必然保存的是最近引用过的页,任何机型的TLB都没有R位的必要。
译:
对于一个页面访问序列,会有一个长的重复序列0,1,...,511,末尾一个随机数字,所以这个序列形如0, 1, ... , 511,431, 0, 1, ... , 511, 332, 0, 1, ... 。问题(a)为何在负载小于重复序列的时候,标准的页面置换算法(LRU、FIFO、Clock)对于这种序列很是低效?(b)若是这个程序只有500个页框,请描述一种好的页面置换算法。
Answer:
(a) Every reference will page fault unless the number of page frames is 512,the length of the entire sequence.
(b) If there are 500 frames, map pages 0–498 to fixed frames and vary only one frame.
分析:
(a)就再也不赘述了,对于(b),实际上是彻底与提到的标准算法不同的,更接近于最优算法的实现,也即在可以预计将来的状况下进行页面管理。
译:
一条机器指令,其功能是把一个32位字的数据装入寄存器,指令自己包含了要装入的字所在的32位地址。这个过程最多会引发几回缺页中断?
分析:
原文稍有点拗口,须要仔细分析。首先装载指令时,若是它是跨页的,会引发两次缺页中断;其次,若是数据所在地址也跨页了,又将引发两次。若是数据必须对齐,那么后者只有一次中断;可是32位的指令未必要对齐,包括Pentium上也是这样。
1.P250习题19,"a 256-KB main memory"应为256-MB,这个推论根据原书答案的计算过程而来。