在上一篇介绍的几种多道编程的内存管理模式中,以交换内存管理最为灵活和先进。可是这种策略也存在不少重大问题,而其中最重要的两个问题就是空间浪费和程序大小受限。那么有什么办法能够解决交换内存存在的这些问题呢?答案是分页,它是咱们解决交换缺陷的“不二法门”。算法
为了解决交换系统存在的缺陷,分页系统横空出世。分页系统的核心在于:将虚拟内存空间和物理内存空间皆划分为大小相同的页面,如4KB、8KB或16KB等,并以页面做为内存空间的最小分配单位,一个程序的一个页面能够存放在任意一个物理页面里。编程
(1)解决空间浪费碎片化问题数据结构
因为将虚拟内存空间和物理内存空间按照某种规定的大小进行分配,这里咱们称之为页(Page),而后按照页进行内存分配,也就克服了外部碎片的问题。app
(2)解决程序大小受限问题spa
程序增加有限是由于一个程序须要所有加载到内存才能运行,所以解决的办法就是使得一个程序无须所有加载就能够运行。使用分页也能够解决这个问题,只需将当前须要的页面放在内存里,其余暂时不用的页面放在磁盘上,这样一个程序同时占用内存和磁盘,其增加空间就大大增长了。并且,分页以后,若是一个程序须要更多的空间,给其分配一个新页便可(而无需将程序倒出倒进从而提升空间增加效率)。操作系统
(1)虚拟地址的构成翻译
在分页系统下,一个程序发出的虚拟地址由两部分组成:页面号和页内偏移值,以下图所示:设计
例如,对于32位寻址的系统,若是页面大小为4KB,则页面号占20位,页内偏移值占12位。3d
(2)地址翻译:虚拟地址→物理地址指针
分页系统的核心是页面的翻译,即从虚拟页面到物理页面的映射(Mapping)。该翻译过程以下伪代码所示:
if(虚拟页面非法、不在内存中或被保护) { 陷入到操做系统错误服务程序 } else { 将虚拟页面号转换为物理页面号 根据物理页面号产生最终物理地址 }
而这个翻译过程由内存管理单元(MMU)完成,MMU接收CPU发出的虚拟地址,将其翻译为物理地址后发送给内存。内存管理单元按照该物理地址进行相应访问后读出或写入相关数据,以下图所示:
那么,这个翻译是怎么实现的呢?答案是查页表,对于每一个程序,内存管理单元MMU都为其保存一个页表,该页表中存放的是虚拟页面到物理页面的映射。每当为一个虚拟页面寻找到一个物理页面以后,就在页表里增长一条记录来保留该映射关系。固然,随着虚拟页面进出物理内存,页表的内容也会不断更新变化。
页表的根本功能是提供从虚拟页面到物理页面的映射。所以,页表的记录条数与虚拟页面数相同。此外,内存管理单元依赖于页表来进行一切与页面有关的管理活动,这些活动包括判断某一页面号是否在内存里,页面是否受到保护,页面是否非法空间等等。
页表的一个记录所包括的内容以下图所示:
因为页表的特殊地位,决定了它是由硬件直接提供支持,即页表是一个硬件数据结构。
优势:
(1)分页系统不会产生外部碎片,一个进程占用的内存空间能够不是连续的,而且一个进程的虚拟页面在不须要的时候能够放在磁盘中。
(2)分页系统能够共享小的地址,即页面共享。只须要在对应给定页面的页表项里作一个相关的记录便可。
缺点:页表很大,占用了大量的内存空间。
在分页系统中,一个虚拟页面既有可能在物理内存,也有可能保存在磁盘上。若是CPU发出的虚拟地址对应的页面不在物理内存,就将产生一个缺页中断,而缺页中断服务程序负责将须要的虚拟页面找到并加载到内存。缺页中断的处理步骤以下,省略了中间不少的步骤,只保留最核心的几个步骤:
若是发生了缺页中断,就须要从磁盘上将须要的页面调入内存。若是内存没有多余的空间,就须要在现有的页面中选择一个页面进行替换。使用不一样的页面置换算法,页面更换的顺序也会各不相同。若是挑选的页面是以后很快又要被访问的页面,那么系统将很开再次产生缺页中断,由于磁盘访问速度远远内存访问速度,缺页中断的代价是很是大的。所以,挑选哪一个页面进行置换不是随随便便的事情,而是有要求的。
页面置换时挑选页面的目标主要在于下降随后发生缺页中断的次数或几率。
所以,挑选的页面应当是随后至关长时间内不会被访问的页面,最好是不再会被访问的页面。BTW,若是可能,最好选择一个没有修改过的页面,这样替换时就无须将被替换页面的内容写回磁盘,从而进一步加快缺页中断的响应速度。
因此,为了达到这个目的,先驱们设计出了各类各样的页面置换算法,下面就来看看这些算法。
在须要替换页面的时候,产生一个随机页面号,从而替换与该页面号对应的物理页面。遗憾的是,随机选出的被替换的页面不太多是随后至关长时间内不会被访问的页面。也就是说,这种算法难以保证最小化随后的缺页中断次数。事实上,这种算法的效果至关差。
顾名思义,先进先出(FIFO,First In First Out)算法的核心是更换最先进入内存的页面,其实现机制是使用链表将全部在内存中的页面按照进入时间的迟早连接起来,而后每次置换链表头上的页面就好了,而新加进来的页面则挂在链表的末端,以下图所示:
FIFO的优势是简单且容易实现,缺点是若是最早加载进来的页面是常常被访问的页面,那么就可能形成被访问的页面替换到磁盘上,致使很快就须要再次发生缺页中断,从而下降效率。
因为FIFO只考虑进入内存的时间,不关心一个页面被访问的频率,从而有可能形成替换掉一个被常常访问的页面而形成效率低下。那么,能够对FIFO进行改进:在使用FIFO更换一个页面时,须要看一下该页面是否在最近被访问过,若是没有被访问过,则替换该页面。反之,若是最近被访问过(经过检查其访问位的取值),则不替换该页面,而是将该页面挂到链表末端,并将该页面进入内存的时间设置为当前时间,并将其访问位清零。这样,对于最近被访问过的页面来讲,至关于给了它第二次机会。
例如,当A页面最近被访问过,即其访问位R的值为1,则使用第二次机会算法以后,链表的格局以下图所示:
第二次机会算法简单、公平且容易实现。可是,每次给予一个页面第二次机会时,将其移动到链表末端须要耗费时间。此外,页面的访问位只在页面替换进行扫描时才可能清零,因此其时间局域性体现得很差,访问位为1的页面多是好久之前访问的,时间上的分辨粒度太粗,从而影响页面替换的效果。
为了改善第二次机会算法的缺点,先驱们提出了时钟算法。时钟算法的核心思想是:将页面排成一个时钟的形状,该时钟有一个针臂,每次须要更换页面时,咱们从针臂所指的页面开始检查。若是当前页面的访问位为0,即从上次检查到此次,该页面没有被访问过,将该页面替换。反之,就将其访问位清零,并顺时针移动指针到下一个页面。重复这些步骤,直到找到一个访问位为0的页面。
例以下图所示的一个时钟,指针指向的页面是F,所以第一个被考虑替换的页面是F。若是页面F的访问位为0,F将被替换。若是F的访问位为1,则F的访问位清零,指针移动到页面G。
从表面上看,它和第二次机会算法相似,都是访问位为0就更换,反之则再给一次机会。可是,它和第二次机会算法仍是有几点不一样:
(1)他们的数据结构不同,第二次机会使用的是链表,时钟算法使用的是索引(整数指针)。这样,其使用的内存空间不同。
(2)第二次机会须要使用额外的内存,而时钟算法能够直接使用页表。使用页表的好处是无需额外的空间,更大的好处是页面的访问位会按期自动清零,这样将使得时钟算法的时间分辨粒度较第二次机会算法高,从而取得更好的页面替换效果。
时钟算法的精髓是第二次机会,其缺点也就和第二次机会算法同样:过于公平,没有考虑到不一样页面调用频率的不一样,有可能换出不该该或不能换出的页面,还可能形成无限循环。
PS:至此,随机、FIFO、第二次机会与时钟算法的介绍就到此结束,这四种算法都是属于“公平算法”,即全部的页面都或多或少地给予公平待遇,没有页面得到特殊待遇。可是这种公平实现方式,会使效率受到必定影响,这时由于个体对于整个系统的贡献没有被区别对待,形成贡献大的和贡献小的待遇同样,天然会影响整个系统的效率。
咱们知道,最理想的页面替换算法是选择一个不再会被访问的页面进行替换。若是不存在这样的页面,那至少选择一个在随后最长时间内不会被访问的页面进行替换。这样,咱们就能够保证在随后发生缺页中断的次数最小或几率最低,这种算法就是最有替换算法。
可是,咱们无法知道一个页面随后多长时间不会被访问,所以最优更换算法在实际中无法实现,那么为何要介绍最有更换算法呢?这是为了定义一个标杆,以此来评判其余算法的优劣。
顾名思义,NRU就是选择一个在最近一段时间内没有被访问过的页面进行替换,这是基于程序访问的时空局域性。由于根据时空局域性原理,一个最近没有被访问的页面,在随后的时间里也不太可能被访问,而NRU的实现方式就是利用页面的访问和修改位。
每一个页面都有一个访问位和一个修改位,凡是对页面进行读写操做时,访问位被设置为1。当进程对页面进行读写操做时,修改位设置为1。根据这两个位的状态来对页面进行分类的话,能够分红如下四种页面类型:一、二、三、4。
有了这个分类,NRU算法就按照这四类页面的顺序依次寻找能够替换的页面。若是全部页面皆被访问和修改过,那也只能从中替换掉一个页面,所以NRU算法老是会终结的。
固然,这种分类比较笼统,在同一类页面里,咱们没有办法分辨出哪一类被访问的时间更近一些。即在某些状况下,咱们替换的可能并非最近没有被使用的页面。
与NRU算法相比,LRU算法不只考虑最近是否用过,还要考虑最近使用的频率。这里是基于过去的数据预测将来:若是一个页面被访问的频率低,那么之后极可能也用不到。
LRU算法的实现必须以某种方式记录每一个页面被访问的次数,这是个至关大的工做量。最简单的方式就是在页表的记录项里增长一个计数域,一个页面被访问一次,这个计数器的值就增长1。因而,当须要更换页面时,只须要找到计数域值最小的页面替换便可,该页面便是最近最少使用的页面。另外一种简单实现方式就是用一个链表将全部页面连接起来,最近被使用的页面在链表头,最近未被使用的放在链表尾。在每次页面访问时对这个链表进行更新,使其保持最近被使用的页面在链表头。
LRU算法虽然很好,可是实现成本高(须要分辨出不一样页面中哪一个页面时最近最少使用的),而且时间代价大(每次页面访问发生时都须要更新记录)。所以,通常的商业操做系统都没有采纳LRU页面更新算法。
因为不可能精确地肯定那个页面是最近最少使用的,那就干脆不花费这个力气,只维持少许的信息使得咱们选出的替换页面不太多是立刻又会使用的页面便可。这种少许的信息就是工做集信息。
工做集概念来源于程序访问的时空局限性,即在一段时间内,程序访问的页面将局限在一组页面集合上。例如,最近k次访问均发生在某m个页面上,那么m就是参数为k时的工做集。咱们用w(k,t)来表示在时间t时k次访问所涉及的页面数量。
显然,随着k的增加,w(k,t)的值也随之增加;可是当k增加到某个数值以后,w(k,t)的值将增加极其缓慢甚至接近停滞,并维持一段时间的稳定,以下图所示:
由上图能够看出,若是一个程序在内存里面的页面数与其工做集大小相等或者超过工做集,则该程序可在一段时间内不会发生缺页中断。若是其在内存的页面数小于工做集,则发生缺页中断的频率将增长,甚至发生内存抖动。
所以,工做计算法的目标就是维持当前的工做集的页面在物理内存里面。每次页面更换时,寻找一个不属于当前工做集的页面替换便可。这样,咱们再寻找页面时只须要将页面分离为两大类便可:当前工做集内页面和当前工做集外页面。如此,只要找到一个飞当前工做集的页面,将其替换便可。
工做集算法的优势:实现简单,只须要在页表的每一个记录增长一个虚拟时间域便可。并且,这个时间域不是每次发生访问时都须要更新,而是在须要更换页面时,页面更换算法对其进行修改,所以时间成本也不大。
工做集算法的缺点:每次扫描页面进行替换时,有可能须要扫描整个页表。然而,并非全部页面都内存里,所以扫描过程当中的一大部分时间将是无用功。另外,因为其数据结构是线性的,会形成每次都按一样的顺序进行扫描,显得不太公平。
鉴于工做集算法的缺点,先驱们将工做集算法与时钟算法结合起来,设计出了工做集时钟算法,即使用工做集算法的原理,可是将页面的扫描顺序按照时钟的形式组织起来。这样每次须要替换页面时,从指针指向的页面开始扫描,从而达到更加公平的状态。并且,按时钟组织的页面只是在内存里面的页面,在内存外的页面不放在时钟圈里,从而提升实现效率。
鉴于其时间与空间上的优点,工做集时钟算法被大多商业操做系统所采纳。
邹恒明,《操做系统之哲学原理》,机械工业出版社