内存管理就是要将各种媒介组建成一体,形成一个巨无霸似的虚拟存储系统。
基址极限管理模式的问题
已经介绍了几种基本的内存管理方法,分别是固定加载地址的内存管理、固定分区的内存管理、非固定分区的内存管理和交换内存管理。固定加载地址的内存管理只适合于单道编程,而其他3种则可用于多道编程。这3种适用于多道编程的内存管理模式均使用同一种实现机制:基址与极限。
基址与极限的工作原理是将程序发出的虚拟地址加在基址而获得物理地址。如果地址超过指定的极限,则视为地址出界而禁止访问,否则访问正常进行。
在我们介绍过的3种多道编程的内存管理模式里,以交换内存管理最为灵活和先进。它可以解决因程序所需空间增长而无法继续运行的困难,又可以实现动态多道编程
事实上,这种策略存在很多重大问题,而其中最重要的两个问题是空间浪费和程序大小受限。
空间浪费问题
随着程序在内存与磁盘间的交换,内存将变得越来越碎片化,即内存将被不同程序分割成尺寸大小无法使用的小片空间
这种散布在进程之间的闲置空间称为外部碎片。这是因为从进程的粒度来看,这种碎片处于进程空间的外面。这种碎片化过程也称为“外部碎片化”。
图12-2显示的是外部碎片化的示意图。在图12-2f的情况下,内存空间形成碎片,无法容纳新的进程H,尽管内存总闲置空间可以容纳进程H。
随着进程的进进出出,外部碎片将浪费大量的内存空间。我们可以采取一些措施来降低外部碎片的危害
不管是最佳适用还是最先适用,这些算法都不能消除外部碎片。当然,如果实在不行,我们可以进行碎片整理,即通过移动进程在内存里面的位置将空闲空间连成一片。但是这种操作需要将进程导出到磁盘上,再重新加载,效率十分低下。在进行碎片整理的过程中,系统的响应延迟将显著增加。因此,这种方法并不让人感觉兴奋。
程序受限问题
除了外部碎片外,交换的内存管理模式还存在几个重大问题:地址空间增长困难。这有两层意思:一是指空间增长效率低下;二是空间增长存在天花板限制。
过交换,可以让程序的大小增长。即先把程序倒到磁盘上,再在内存寻找一片更大的空间将程序倒进来,从而扩展程序所占的空间。由于磁盘操作耗时,这种交换出去,再找一片更大的空间来增长程序空间的做法效率非常低。
但是空间增长的低效率还不是交换所面临的唯一问题。即使我们可以忍受效率低下,但还有另外一个问题:交换所能带来的空间增长有限。这个限制就是单一程序不能超过物理内存空间(减去操作系统所占部分),尽管多个程序的总空间可以超过物理内存空间。
解决之道
空间碎片化的根源是每个程序的大小不一样,这样在空间分配时不存在一致性。解决的办法自然是将空间按照某种规定的大小进行分配。只要将虚拟内存与物理内存都分成大小一样的部分,我们称为页,然后按页进行内存分配,就可以克服外部碎片的问题
程序增长有限则是因为一个程序需要全部加载到内存才能运行。而解决的办法就是使一个程序无须全部加载就可以运行。用分页也可以解决这个问题:只将当前需要的页面放在内存里,其他暂时不用的页面放在磁盘上,这样一个程序同时占用内存和磁盘,其增长空间就大大增加了。而且,分页后,如果一个程序需要更多的空间,给其分配一个新页即可(而无须像之前介绍的那样将程序倒出倒进,从而大大提高空间增长效率)。
分页内存管理
为了解决交换系统存在的缺陷,出现了分页系统。分页系统的核心就是将虚拟内存空间和物理内存空间皆划分为大小相同的页面
并以页面作为内存空间的最小分配单位,一个程序的一个页面可以存放在任意一个物理页面里。这样,由于物理空间是页面的整数倍,并且空间分配以页面为单位,将不会再产生外部碎片。同时,由于一个虚拟页面可以存放在任何一个物理页面里,空间增长也容易解决:只需要分配额外的虚拟页面,并找到一个闲置的物理页面存放即可。
在分页系统下,一个程序发出的虚拟地址由两部分组成:页面号和页内偏移值
为了解决程序比内存大的问题,我们可以允许一个进程的部分虚拟页面存放在物理页面之外,也就是磁盘上。在需要访问这些外部虚拟页面时,再将其调入物理内存。由此,交换系统的所有缺陷均被克服。
地址翻译
分页系统要能够工作的前提是:对于任何一个虚拟页面,系统知道该页面是否在物理内存中,如果在的话,其对应的物理页面是哪个;如果不在的话,则产生一个系统中断(缺页中断),并将该虚页从磁盘转到内存,然后将分配给它的物理页面号返回。也就是说,页面管理系统要能够将虚拟地址转换为物理地址。该翻译过程如下所示。
if(虚拟页面非法、不在内存或者被保护){
陷入到操作系统错误服务程序
}else{
将虚拟页面号转换为物理页面号
根据物理页面号产生最终物理地址
}
因此,分页系统的核心是页面的翻译,即从虚拟页面到物理页面的映射。而这个翻译过程由内存管理单元(MMU)完成。MMU接收CPU发出的虚拟地址,将其翻译为物理地址后发送给内存。内存单元按照该物理地址进行相应访问后读出或写入相关数据,如图
那么内存管理单元是通过什么手段完成这种翻译的呢?当然是查页表。
随着虚拟页面进出物理内存,页表的内容页不断发生变化。
页表
页表在分页内存管理系统的地位十分关键。页表的根本功能是提供从虚拟页面到物理页面的映射。因此,页表的记录条数与虚拟页面数相同。
页表记录内容举例
缓存禁止位用来指示该页面是否允许存放在缓存里。
物理页面号则是该虚拟页面对应的物理页面(如果该虚拟页面在物理内存的话)。
访问位和修改位是内存管理单元进行页面置换时依赖的信息。
一个记录条通常还会有一个保留区(reserve area),图没有给出该保留区。
但它通常是存在的。设置保留区的目的是为以后有需要时增加信息用的。
由于页表的特殊地位决定了它由硬件直接提供支持,即页表是一个硬件数据结构。
从该页表可以看出,第0个虚拟页面在物理的第2个页面里。如果我们访问虚拟页面0,则实际访问的物理页面2。页表里面的X表示该虚拟页面不在内存里。如果访问的页面不在内存,那么将产生缺页中断。例如,如果我们访问虚拟页面6,则将发生缺页中断。缺页中断服务程序将负责在磁盘上找到需要的虚拟页面,并在物理内存里面寻找一个闲置的页面来存放该虚拟页面,然后更新页表。之后,对该虚拟页面的访问就可以正常进行了。
页面翻译过程
内存管理单元的虚拟到物理页面翻译过程
分页系统的优缺点
优点:
分页系统不会产生外部碎片
可以共享小的地址,即页面共享
缺点:
第一个缺点就是页表很大,占用了大量的内存空间。
例如,对于32位寻址、页面尺寸为4KB的分页系统来说,页表将有1048576个记录。每个记录又要占用多个字节,这样,一个页表所占的内存空间相当大。
那么有没有办法减少页表的尺寸呢?可以选择多级页表的方式。既然一个程序可以分解为一个个页面,并将部分页面存放在磁盘上而降低其内存占用空间,页表也可以采用同样的方式处理,即将页表分为一个个页面,不需要的页面也放在磁盘上。内存里只存放需要的页面。
多级页表
多级页表结构下,页表根据存放的内容可分为:顶级页表、一级页表、二级页表、三级页表等。一个程序在运行时其顶级页表常驻内存,而次级页表则按需要决定是否存放在物理内存。
如果使用两层页表的话,虚拟地址的前10位可作为顶级页表的索引,中间10位可作为次级页表的索引,最后12位可作为页内偏移值如图
多级页表占用的内存空间少。因为大部分次级页表会放到磁盘上,而放在内存里面的页表较少。因此,内存占用少。
多级页表有什么缺点呢?它降低了系统的速度。因此每次内存访问都变成多次内存访问。对于二级页表,一次内存访问变成了三次内存访问。如果次级页表不在内存,还需要加上一次磁盘访问。这样,系统的速度将大为下降。对于级数更多的页表来说,内存访问速度额下降将更加明显。
反转页表
那有没有可能在不增加页表级数的情况下降低页表所占的空间呢?有,使用反转页表。
正常的页表存放的是从虚拟页面到物理页面的映射;而反转页表存放的是物理页面到虚拟页面的映射。这样,由于物理内存比虚拟内存小很多,页表的尺寸将大为减少。
由于反转页表存放的是物理地址到虚拟地址的映射,而CPU发出的地址却是虚拟地址,这就造成页表查找困难。不过,这个问题可以通过散列来解决,即将虚拟页号散列到物理页号,然后以这个散列出来的物理页号作为索引在反转页表里面查找。但由于虚拟页面远多于物理页面的缘故,多个虚拟页号将很可能散列到同一个物理页号对应的记录里。这样,在散列后,仍然需要检查该虚拟页面是否在物理内存内,而这种检查需要进行多次内存访问。如果使用开放式散列,则散列表的尺寸将随着程序使用的虚拟页面数的增加而增加。
翻译速度
地址翻译因增加了内存访问次数而降低了系统效率。
由于内存访问是每条指令都需要执行的操作,这样将造成整个系统效率的下降。那有没有什么办法改善翻译的速度呢?有。因为程序的运行呈现所谓的时空局域性,即在一段时间内,程序所要访问的地址空间有一定的空间局域性。如果一个页面被访问,则该页面的其他地址可能也将被随后访问。这样,我们可以将该页面的翻译结果存放在缓存里,而无须在访问该页面的每个地址时再翻译一次。这样就可以大大提高系统的执行效率。
这种存放翻译结果的缓存称为翻译快表(Translation Look-Aside Buffer,TLB)。TLB里面存放的是从虚拟页面到物理页面的映射,其记录的格式与内容和正常页表的记录格式与内容一样
TLB通常由CPU制造商提供,但TLB的更换算法则有可能由操作系统提供。
缺页中断处理
缺页中断服务程序负责将需要的虚拟页面找到并加载到内存。那么缺页中断程序是如何知道虚拟页面在磁盘的什么地方呢?它当然不知道。但它知道产生缺页中断进程所对应的源程序文件名和产生缺页中断的虚拟地址。这样,缺页中断服务程序首先根据虚拟地址计算出该地址在相应程序文件里面的位移量或偏移量(off-set),然后要求文件系统在这个偏移量的地方进行文件读操作。那么知道读多少内容吗?当然知道了,读一个页面的数据!而文件系统当然知道如何根据程序的文件名和偏移值来读取数
下面是缺页中断的处理步骤:
1)硬件陷入内核。
2)保护通用寄存器。
3)操作系统判断所需的虚拟页面号。
4)操作系统检查地址的合法性。
5)操作系统选择一个物理页面来存放将要调入的页面。
6)如果选择的物理页面包含有未写磁盘的内容,则首先进行写盘操作。
7)操作系统将新的虚拟页面调入内存。
8)更新页表。
9)发生缺页中断的程序进入就绪状态。
10)恢复寄存器。
11)程序继续。
锁住页面
如果发生缺页中断,就需要从磁盘上将需要的页面调入内存。如果内存没有多余的空间,就需要在现有的页面里选择一个页面进行替换。使用不同的更换算法,页面更换的顺序将各不相同。
如何把页面锁在内存里呢?很简单,只需要对该页面做出特殊标记即可。即我们在页表的相应记录项里增加一项标志。如果该标志被设置,缺页中断服务程序在选择被替换的页面时将跳过该页面。
页面尺寸
在平均情况下,最后一个页面有半个页面被浪费。这种浪费称为内部碎片,即一个进程内部的碎片空间。(还记得外部碎片吗?)这样,页面越大,内部碎片就越大,浪费就越多。
可变页面页面策略的缺点也十分明显,首先是操作系统对内存的管理将更为复杂。其次,我们也很难正确地判断每个程序使用何种页面尺寸最为合适。最后,可变页面尺寸也不是一定就能消除内部碎片的。因此,可变页面尺寸策略听上去动听,实际上并不中用。历史上的Multic操作系统就因为支持可变页面尺寸而受人诟病。
内存抖动
在最坏情况下,每次新的访问都是对一个不在内存的页面进行访问,即每次内存访问都产生一次缺页中断,这样每次内存访问皆变成一次磁盘访问,而由于磁盘访问速度比内存可以慢一百万倍,因此整个系统的效率急剧下降。这种现象就称为内存抖动,或者抽打、抽筋(tras-hing)。
我们可以通过仔细设计页面更换算法来降低内存抖动的概率,但却不能完全避免。
《操作系统之哲学原理第2版》邹恒明
精彩摘抄
?