【操作系统】第四章-存储器管理

四、存储器管理

1.存储器的层次结构

在计算机执行时,几乎每一条指令都涉及对存储器的访问,因此要求对存储器的访问速度能跟得上处理机的运行速度。或者说,存储器的速度必须非常快,能与处理机的速度相匹配,否则会明显地影响到处理机的运行。此外还要求存储器具有非常大的容量,而且存储器的价格还应很便宜。对于这样十分严格的三个条件,目前是无法同时满足的。于是在现代计算机系统中都无一例外地采用了多层结构的存储器系统。

1.1 多层结构的存储器系统

  1. 存储器的多层结构

    对于通用计算机而言,存储层次至少应具有三级:最高层为CPU寄存器,中间为主存,最底层是辅存。在较高档的计算机中,还可以根据具体的功能细分为寄存器、高速缓存、主存储器、磁盘缓存、固定磁盘、可移动存储介质等6层。如下图所示。在存储层次中,层次越高(越靠近CPU),存储介质的访问速度越快,价格也越高,相对所配置的存储容量也越小。其中,寄存器、高速缓存、主存储器和磁盘缓存均属于操作系统存储管理范畴,掉电后它们中存储的信息不再存在。而低层的固定磁盘和可移动存储介质则属于设备管理的管辖范畴,它们存储的信息将被长期保存。

    在这里插入图片描述

  2. 可执行存储器

    在计算机系统的存储层次中,寄存器和主存储器又称为可执行存储器。对于存放于其中的信息,与存放于辅存中的信息相比较而言,计算机所采用的访问机制是不同的,所需耗费的时间也是不同的。进程可以在很少的时钟周期内使用一条load或store指令对可执行存储器进行访问。但对辅存的访问则需要通过I/O设备实现,因此,在访问中将涉及到中断、设备驱动程序以及物理设备的运行,所需耗费的时间远远高于访问可执行存储器的时间,一般相差3个数量级甚至更多。

1.2 主存储器与寄存器

  1. 主存储器

    主存储器简称内存或主存,是计算机系统中的主要部件,用于保存进程运行时的程序和数据,也称可执行存储器。通常,处理机都是从主存储器中取得指令和数据的,并将其所取得的指令放入指令寄存器中,而将所读取的数据装入到数据寄存器中;或者反之,将寄存器中的数据存入到主存储器。早期的内存是由磁芯做成的,其容量一般为数十KB到数百KB。随着VLSI的发展,现在的内存已由VLSI构成,其容量,即使是微系统,也在数十MB到数GB,而且还在不断增加。而嵌入式计算机系统一般仅有几十KB到几MB。CPU与外围设备交换的信息一般也依托于主存储器的地址空间。由于主存储器访问速度远低于CPU执行指令的速度,为缓和这一矛盾,在计算机系统中引入了寄存器和高速缓存。

  2. 寄存器

    寄存器具有与处理机相同的速度,故对寄存器的访问速度最快,完全能与CPU协调工作,但价格却十分昂贵,因此容量不可能做得很大。在早期计算机中,寄存器的数目仅为几个,主要用于存放处理机运行时的数据,以加速存储器的访问速度,如使用寄存器存放操作数,或用作地址寄存器加快地址转换速度等。随着VLSI的发展,寄存器的成本也在迅速降低,在当前的微机系统和大中型机中,寄存器的数目都已增加到数十个到数百个,而寄存器的字长一般是32位或64位;而在小型的嵌入式计算机中,寄存器的数目仍只有几个到十几个,而且寄存器的字长通常只有8位。

1.3 高速缓存和磁盘缓存

  1. 高速缓存

    高速缓存是现代计算机结构中的一个重要部件,它是介于寄存器和存储器之间的存储器,主要用于备份主存中较常用的数据,以减少处理机对主存储器的访问次数,这样可大幅度提高程序执行速度。高速缓存容量远大于寄存器,而比内存约小两到三个数量级左右,从几十KB到几MB,访问速度快于主存储器。在计算机系统中,为缓和内存与处理机速度之间的矛盾,许多地方都设置了高速缓存。

  2. 磁盘缓存

    由于目前磁盘的I/O速度远低于的主存的访问速度,为了缓和两者之间在速度上的不匹配,而设置了磁盘缓存,主要用于暂时存放频繁使用的一部分磁盘数据和信息,以减少访问磁盘的次数。但磁盘缓存与高速缓存不同,它本身并不是一种实际存在的存储器,而是利用主存中的部分存储空间暂时存放从磁盘中读出(或写入)的信息。主存也可以看作是辅存的高速缓存,因为,辅存中的数据必须复制到主存方能使用,反之,数据也必须先存在主存中,才能输出到辅存。

2.程序的装入和链接

用户程序要在系统中运行,必须先将它装入内存,然后再将其转变为一个可以执行的程序,通常要经过以下几个步骤:

(1) 编译,由编译程序(Compiler)对用户源程序进行编译,形成若干个目标模块(Object Module);

(2) 链接,由链接程序(Linker)将编译后形成的一组目标模块以及它们所需要的库函数链接在一起,形成一个完整的装入模块(Load Module);

(3) 装入,由装入程序(Loader)将装入模块装入内存。

下图示出了这样的三步过程。

在这里插入图片描述

2.1 程序的装入

在将一个装入模块装入内存时,可以有如下三种装入方式:

  1. 绝对装入方式(Absolute Loading Mode)

    当计算机系统很小,且仅能运行单道程序时,完全有可能知道程序将驻留在内存的什么位置,此时可以采用绝对装入方式。用户程序经编译后,将产生绝对地址(即物理地址)的目标代码。

  2. 可重定位装入方式(Relocation Loading Mode)

    绝对装入只能将目标模块装入到内存中实现指定的位置,这只适用于单道程序环境。而在多道程序环境下,编译程序不可能预知经编译后所得到的目标模块应放在内存的何处。因此,对于用户程序编译所形成的若干个目标模块,它们的起始地址通常都是从0开始的,程序中其他地址也都是相对于起始地址计算的。此时,不可能再用绝对装入方式,而应采用可重定位装入方式,它可以根据内存的具体情况将装入模块装入到内存的适当位置。

  3. 动态运行时的装入方式(Dynamic Run-time Loading)

    可重定位装入方式可将装入模块装入到内存中任何允许的位置,故可用于多道程序环境,但该方式并不允许程序运行时在内存中移动位置。因为程序在内存中的移动,意味着它的物理位置发生了变化,这时必须对程序和数据的地址(绝对地址)进行修改后方能运行。

    动态运行时的装入程序在把装入模块装入内存后,并不立即把装入模块中的逻辑地址转换为物理地址,而是把这种地址转换推迟到程序真正要执行时才进行。因此,装入内存后的所有地址都扔是逻辑地址。为使地址转换不影响指令的执行速度,这种方式需要一个重定位寄存器的支持,将在第三节中介绍。

2.2 程序的连接

  1. 静态链接(Static Linking)方式

    在程序运行之前,先将各目标模块及它们所需的库函数链接成一个完整的装配模块,以后不再拆开,我们把这种事先进行链接的方式称为静态链接方式。

    将目标模块装配成一个装入模块时,须解决以下两个问题:

    (1) 对相对地址进行修改。

    (2) 变换外部调用符号。

    在这里插入图片描述

  2. 装入时动态链接(Load-time Dynamic Linking)

    这是指将用户源程序编译后所得到的一组目标模块,在装入内存时,采用边装入边链接方式。即在装入一个目标模块时,若发生一个外部模块调用事件,将引起装入程序去找出相应的外部目标模块,并将它装入内存安装上图4-4所示方式修改目标模块中的相对地址。

  3. 运行时动态链接(Run-time Dynamic Linking)

    在许多情况下,应用程序在运行时,每次要运行的模块可能是不相同的。但由于事先无法知道本次要运行哪些模块,故只能是将所有可能要运行到的模块全部都装入内存,并在装入时全部链接在一起。显然这是低效的,因为往往会有部分目标模块根本就不运行。比较典型的例子是作为错误处理用的目标模块,如果程序在整个运行过程中都不出现错误,则显然就不会用到该模块。

    近几年流行起来的运行时动态链接方式,是对上述装入时链接方式的一种改进。这种链接方式是,将对某些模块的链接推迟到程序执行时才进行。亦即,在执行过程中,当发现一个被调用模块尚未装入内存时,立即由OS去找到该模块,并将之装入内存,将其链接到调用者模块上。凡在执行过程中未被用到的目标模块,都不会被调入内存和被链接到装入模块上,这样不仅能加快程序的装入过程,而且可节省大量的内存空间。

3.连续分配存储管理方式

3.1 单一连续分配

在单道程序环境下,当时的存储器管理方式是把内存分为系统区和用户区两部分,系统区仅提供给OS使用,它通常是放在内存的低址部分。而在用户区内存中,仅装有一道用户程序,即整个内存的用户空间由该程序独占。这样的存储器分配方式被称为单一连续分配方式。

3.2 固定分区分配

20世纪60年代出现的多道程序系统,如IBM 360的MFT操作系统,为了能在内存中装入多道程序,且使这些程序之间又不会发生互相干扰,于是将整个用户空间划分为若干个固定大小的区域,在每个分区中只装入一道作业,这样就形成了最早的、也是最简单的一种可运行多道程序的分区式存储管理方式。如果在内存中有四个用户分区,便运行四个程序并发运行。当有一个空闲分区时,便可以再从外存的后备作业队列中选择一个适当大小的作业,装入该分区。当该作业结束时,又可再从后备作业队列中找出另一个作业调入该分区。

  1. 划分分区的方法

    (1) 分区大小相等。

    (2) 分区大小不等。

  2. 内存分配

    为了便于内存分配,通常将分区按其大小进行排队,并为之建立一张分区使用表,其中各表项包括每个分区的起始地址、大小及状态(是否已分配),如下图。当有一用户程序要装入时,由内存分配程序依据用户程序的大小检索该表,从中找出一个能够满足要求的、尚未分配的分区,将之分配给该程序,然后将该表项中的状态置为“已分配”。若未找到大小足够的分区,则拒绝为该用户程序分配内存。

    在这里插入图片描述

3.3 动态分区分配

  1. 动态分区分配中的数据结构

    为了实现动态分区分配,系统中必须配置相应的数据结构,用以描述空闲分区和已分配分区的情况,为分配提供依据。常用的数据结构有以下两种形式:①空闲分区表,在系统中设置一张空闲分区表,用于记录每个空闲分区的情况。每个空闲分区占一个表目,表中包括分区好、分区大小和分区始址等数据项,如下图。②空闲分区链。为了实现对空闲分区的分配和链接,在每个分区的起始部分设置一些用于控制分区分配的信息,以及用于链接各分区所用的前向指针,在分区尾部则设置一后向指针。通过前、后向链接指针,可将所有的空闲分区链接成一个双向链,如下图。为了检索方便,在分区尾部重复设置状态位和分区大小表目。当分区被分配出去以后,把状态位有“0”改为“1”,此时,前、后向指针已无意义。

    在这里插入图片描述

  2. 动态分区分配算法

    为把一个新作业装入内存,须按照一定的分配算法,从空闲分区表或空闲分区链中选出一分区分配给该作业。由于内存分配算法对系统性能有很大的影响,故人们对它进行了较为广泛而深入的研究,于是产生了许多动态分区分配算法。后面介绍。

  3. 分区分配操作

    在动态分区存储管理方式中,主要的操作是分配内存和回收内存。

    (1) 分配内存

    系统应利用某种分配算法,从空闲分区链(表)中找到所需大小的分区。设请求的分区大小为u.size,表中每个空闲分区的大小可表示为m.size。若m.size-u.size≤size(size是事先规定的不再切割的剩余分区的大小),说明多余部分太小,将整个分区分配给请求者。否则(即多余部分超过size),便从该分区中按请求的大小划分出一块内存空间分配出去,,余下的部分仍留着空闲分区链(表)中。然后,将分配区的首址返回给调用者。如下图。

    在这里插入图片描述

    (2) 回收内存

    当进程运行完毕释放内存时,系统根据回收的首址,从空闲区链(表)中找到相应的插入点,此时可能出现以下四种情况之一:

    ①回收区与插入点的前一个空闲分区F1相邻接,如下图(a)。此时应将回收区与插入点的前一分区合并,不必为回收分区分配新表项,而只需修改其前一分区F1的大小。

    ②回收区与插入点的后一空闲分区F2相邻接,如下图(b)。此时也可将两分区合并,形成新的空闲分区,但用回收区的首址作为新空闲区的首址,大小为两者之和。

    ③回收区同时与插入点的前、后两个分区邻接,如下图©。此时将三个分区合并,使用F1的表项和F1的首址,取消F2的表项,大小为三者之和。

    在这里插入图片描述

    ④回收区既不与F1邻接,又不与F2邻接。这时应为回收区单独建立一个新表项,填写回收区的首址和大小,并根据其首址插入到空闲链中的适当位置。如下图示出了内存回收时的流程。

    在这里插入图片描述

3.4 基于顺序搜索的动态分区分配算法

为了实现动态分区分配,通常是将系统中的空闲分区链接成一个链。所谓顺序搜索,是指依次搜索空闲分区链上的空闲分区,去寻找一个其大小能满足要求的分区。基于顺序搜索的动态分区分配算法有如下四种:首次适应算法、循环首次适应算法、最佳适应算法和最坏适应算法。

  1. 首次适应(first fit, FF)算法

    以空闲分区链为例来说明采用FF算法时的分配情况。FF算法要求空闲分区链以地址递增的次序链接。在分配内存时,从链首开始顺序查找,直到找到一个大小能满足要求的空闲分区为止。然后再按照作业的大小,从该分区中划出一块内存空间分配给请求者,余下的空闲分区仍留在空闲链中。若从链首直至链尾都不能找到一个能满足要求的分区,则表明系统中已没有足够大的内存分配给该进程,内存分配失败,返回。

    该算法倾向于优先利用内存中低址部分的空闲分区,从而保留了高址部分的大空闲区。这为以后到达的大作业分配大的内存空间创造了条件。其缺点是低址部分不断被划分,会留下许多难以利用的、很小的空闲分区,称为碎片。而每次查找又都是从低址部分开始的,这无疑又会增加查找可用空闲分区时的开销。

  2. 循环首次适应(next fit, NF)算法

    为避免低址部分留下许多很小的空闲分区,以及减少查找可用空闲分区的开销,循环首次适应算法在为进程分配内存空间时,不再是每次都从链首开始查找,而是从上次找到的空闲分区的下一个空闲分区开始查找,直到找到一个能满足要求的空闲分区,从中划出一块与请求大小相等的内存空间分配给作业。为实现该算法,应设置一起始查寻指针,用于指示下一次起始查寻的空闲分区,并采用循环查找方式,即如果最后一个(链尾)空闲分区的大小仍不能满足要求,则应返回到第一个空闲分区,比较其大小是否满足要求。找到后,应调整起始查寻指针。该算法能使内存中的空闲分区分布得更均匀,从而减少了查找空闲分区时的开销,但这样会缺乏大的空闲分区。

  3. 最佳适应(best fit, BF)算法

    所谓“最佳”是指,每次为作业分配内存时,总是把能满足要求、又是最小的空闲分区分配给作业,避免“大材小用”。为了加速寻找,该算法要求将所有的空闲分区按其容量以从小到大的顺序形成以空闲分区链。这样,第一次找到的能满足要求的空闲分区必然是最佳的。孤立地看,最佳适应算法似乎是最佳的,然而在宏观上却不一定。因为每次分配后所切割下来的剩余部分总是最小的,这样,在存储器中会留下许多难以利用的碎片。

  4. 最坏适应(worst fit, WF)算法

    由于最坏适应分配算法选择空闲分区的策略正好与最佳适应算法相反:它在扫描整个空闲分区表或链表时,总是挑选一个最大的空闲区,从中分割一部分存储空间给作业使用,以至于存储器中缺乏大的空闲分区,故把它称为是最坏适应算法。实际上,这样的算法未必是最坏的,它的优点是可使剩下的空闲区不至于太小,产生碎片的可能性最小,对中、小作业有利。同时,最坏适应分配算法查找效率很高,该算法要求,将所有的空闲分区,按其容量以从大到小的顺序形成一空闲分区链,查找时,只要看第一个分区能否满足作业要求即可。

3.5 基于索引搜索的动态分区分配算法

基于顺序搜索的动态分区分配算法,比较适用于不太大的系统。当系统很大时,系统中的内存分区可能会很多,相应的空闲分区链就可能很长,这时采用顺序搜索分区方法可能会很慢。为了提高搜索空闲分区的速度,在大、中型系统中往往会采用基于索引搜索的动态分区分配算法,目前常用的有快速适应算法、伙伴系统和哈希算法。

  1. 快速适应(quick fit)算法

    该算法又称为分类搜索法,是将空闲分区根据其容量大小进行分类,对于每一类具有相同容量的所有空闲分区,单独设立一个空闲分区链表,这样系统中存在多个空闲分区链表。同时,在内存中设立一张管理索引表,其中的每一个索引表项对应一种空闲分区类型,并记录了该类型空闲分区链表表头的指针。空闲分区的分类是根据进程常用的空间大小进行划分的,如2KB、4KB、8KB等,对于其它大小的分区,如7KB这样的空闲区,既可以放在8KB的链表中,也可以放在一个特殊的空闲区链表中。

  2. 伙伴系统(buddy system)

    该算法规定,无论已分配分区或空闲分区,其大小均为2的k次幂(k为正数,1≤k≤m)。通常2m是整个可分配内存的大小(也就是最大分区的大小)。假设系统的可利用空间容量为2m个字,则系统开始运行时,整个内存区是一个大小为2m的空闲分区。在系统运行过程中,由于不断地划分,将会形成若干个不连续的空闲分区,将这些空闲分区按分区的大小进行分类。对于具有相同大小的所有空闲分区,单独设立一个空闲分区双向链表,这样,不同大小的空闲分区形成了k个空闲分区链表。

  3. 哈希算法

    哈希算法是利用哈希快速查找的优点,以及空闲分区在可利用空闲区表中的分布规律,建立哈希函数,构造一张以空闲分区大小为关键字的哈希表,该表的每一个表项记录了一个对应的空闲分区链表表头指针。

    当进行空闲分区分配时,根据所需空闲分区大小,通过哈希函数计算,即得到在哈希表中的位置,从中得到相应的空闲分区链表,实现最佳分配策略。

3.6 动态可重定位分区分配

  1. 紧凑

    连续分配方式的一个重要特点是,一个系统或用户程序必须被装入一片连续的内存空间中。当一台计算机运行了一段时间后,它的内存空间将会被分割成许多小的分区,而缺乏大的空闲空间。即使这些分散的许多小分区的容量总和大于要装入的程序,但由于这些分区不相邻接,也无法把该程序装入内存。

    若想把大作业装入,可采用的一种方法是:将内存中的所有作业进行移动,使它们全都相邻接。这样,即可把原来分散的多个空闲小分区拼接成一个大分区,可将一个作业装入该区。这种通过移动内存中作业的位置,把原来多个分散的小分区拼接成一个大分区的方法,称为“拼接”或“紧凑”。

  2. 动态重定位

    为使地址的转换不会影响到指令的执行速度,必须有硬件地址变换机构的支持,即须在系统中增设一个重定位寄存器,用它来存放程序(数据)在内存中的起始地址。程序在执行时,真正访问的内存地址是相对地址与重定位寄存器中的地址相加而形成的。下图示出了动态重定位的实现原理。地址变换过程是在程序执行期间,随着对每条指令或数据的访问自动进行的,故称为动态重定位。当系统对内存进行了“紧凑”,而使若干程序从内存的某处移至另一处时,不需对程序做任何修改,只要用该程序在内存的新起始地址去置换原来的起始地址即可。

    在这里插入图片描述

  3. 动态重定位分区分配算法

    动态重定位分区分配算法与动态分区分配算法基本相同,差别仅在于:在这种分配算法中,增加了紧凑的功能。通常,当该算法不能找到一个足够大的空闲分区以满足用户需求时,如果所有的小的空闲分区的容量总和大于用户的要求,这时便须对内存进行“紧凑”,将经“紧凑”后所得到的大空闲分区分配给用户。如果所有的小的空间分区的容量总和仍小于用户的要求,则返回分配失败的信息。下图示出了动态重定位分区分配算法。

    在这里插入图片描述

4.对换(Swapping)

4.1 多道程序环境下的对换技术

  1. 对换的引入

    在多道程序环境下,一方面,在内存中的某些进程由于某些事件尚未发生而被阻塞运行,但它却占用了大量的内存空间,甚至有时可能出现在内存中所有进程都被阻塞,而无可运行中的进程,迫使CPU停下来等待的情况;另一方面,却又有着许多作业,因内存空间不足,一直驻留在外存上,而不能进入内存运行。显然这对系统资源是一种严重的浪费,且使系统吞吐量下降。为了解决这一问题,在系统中又增设了对接(也称交换)设施。所谓“对换”,是指把内存中暂时不能运行的进程或者暂时不用的程序和数据换到外存上,以便腾出足够的内存空间,再把已具备运行条件的进程或进程所需要的程序和数据换入内存。对换是改善内存利用率的有效措施,它可以直接提高处理机的利用率和系统的吞吐量。

  2. 对换的类型

    在每次对换时,都是将一定数量的程序或数据换入或换出内存。根据每次对换时所对换的数量,可将对换分为如下两类:

    (1) 整体对换。

    (2) 页面(分段)对换。

4.2 对换空间的管理

  1. 对换空间管理的主要目标

    (1) 对文件区管理的主要目标

    文件区占用磁盘空间的大部分,用于存放各类文件。由于通常的文件都是较长时间地驻留在外存上,对它访问的频率是较低的,故对文件区管理的主要目标是提高文件存储空间的利用率,然后才是提高对文件的访问速度。因此,对文件区空间的管理采取离散分配方式。

    (2) 对对换空间管理的主要目标

    对换空间只占用磁盘空间的小部分,用于存放从内存换出的进程。由于这些进程在对换区中驻留的时间是短暂的,而对换操作的频率却较高,故对对换空间管理的主要目标,是提高进程换入和换出的速度,然后才是提高文件存储空间的利用率。为此,对对换区空间的管理采取连续分配方式,较少考虑外存中的碎片问题。

  2. 对换区空闲盘块管理中的数据结构

    为了实现对对换区中的空闲盘块的管理,在系统中应配置相应的数据结构,用于记录外存对换区中的空闲盘块的使用情况。其数据结构的形式与内存在动态分区分配方式中所用数据结构相似,即同样可以用空闲分区表或空闲分区链。在空闲分区表的每个表目中,应包含两项:对换区的首址及其大小,分别用盘块号和盘块数表示。

  3. 对换空间的分配和回收

    由于对换分区的分配采用的是连续分配方式,因而对换空间的分配与回收与动态分区方式时的内存分配与回收方法雷同。

4.3 进程的换出与换入

  1. 进程的换出

    对换进程在实现进程的换出时,是将内存中的某些进程调出至对换区,以便腾出内存空间。换出过程可分为以下两步:

    (1) 选择被换出的进程。

    (2) 进程换出过程。

  2. 进程的换入

    对换进程将定时执行换入操作,它首先查看PCB集合中所有进程的状态,从中找出“就绪”状态但已换出的进程。当有许多这样的进程时,它将选择其中已换出带磁盘上时间最久(必须大于规定时间,如2s)的进程作为换入进程,为它申请内存。如果申请成功,可直接将进程从外存调入内存;如果失败,则需先将内存中的某些进程换出,腾出足够的内存空间后,再将进程调入。

    在对换进程成功地换入一个进程后,若还有可换入的进程,则再继续执行换入换出过程,将其余处于“就绪且换出”状态的进程陆续换入,直到内存中再无“就绪且换出”状态的进程为止,或者无足够的内存来换入进程,此时对换进程才停止换入。

5.分页存储管理方式

5.1 分页存储管理的基本方法

  1. 页面和物理块

    (1) 页面。分页存储管理将进程的逻辑地址空间分成若干个页,并为各页加以编号,从0开始,如第0页、第1页等。相应地,也把内存的物理地址空间分成若干个块,同样也为它们加以编号,如0#块、1#块等。在为进程分配内存时,以块为单位,将进程中的若干个页分别装入到多个可以不相邻的物理块中。由于进程的最后一页进装不满一块,而形成了不可利用的碎片,称之为“页内碎片”。

    (2) 页面大小。页面的大小选择应始终,且页面大小应是2的幂,通常为1KB~8KB。

  2. 地址结构

    分页地址中的地址结构如下:

    在这里插入图片描述

  3. 页表

    在分页系统中,允许进程的各个页离散地存储在内存的任一物理块中,为保证进程仍然能够正确地运行,即能在内存中找到每个页面所对应的物理块,系统又为每个进程建立了一张页面映像表,简称页表。在进程地址空间内的所有页(0~n),依次在页表中有一页表项,其中记录了相应页在内存中对应的物理块号,如下图中间部分。在配置了页表后,进程执行时,通过查找该表,即可找到每页在内存中的物理块号。可见,页表的作用是实现从页号到物理块号的地址映射。

    在这里插入图片描述

5.2 地址变换机构

  1. 基本的地址变换机构

    进程在运行期间,需要对程序和数据的地址进行交换,即将用户地址中的逻辑地址变换为内存中的物理地址,由于它执行的频率非常高,每条指令的地址都需要进行变换,因此需要采用硬件来实现。页表功能是由一组专门的寄存器来实现的,页表大多驻留在内存中,在系统中只设置一个页表寄存器PTR(Page-Table Register),在其中存放页表在内存的始址和页表的长度。平时,进程未执行时,页表的始址和页表长度存放在本进程的PCB中。当调度程序调度到某进程时,才将这两个数据装入页表寄存器中。因此,在单处理机环境下,虽然系统中可以运行多个进程,但只需一个页表寄存器。

    当进程要访问某个逻辑地址中的数据时,分页地址变换机构会自动地将有效地址(相对地址)分为页号和页内地址两部分,再以页号为索引去检索页表。查找操作由硬件执行。在执行索引之前,先将页号与页表长度进行比较,如果页号大于等于页表长度,则表示本次所访问的地址已超越进程的地址空间。于是,这一错误将被系统发现,并产生一地址越界中断。若为出现越界错误,则将页表始址与页号和页表项长度的乘积相加,便得到该表项在页表中的位置,于是可从中得到该页的物理块号,将之装入物理地址寄存器中。于此同时,再将有效地址寄存器中的页内地址送入物理地址寄存器的块内地址字段中。这样便完成了从逻辑地址到物理地址的变换。如下图示出了分页系统的地址变换机构。

    在这里插入图片描述

  2. 具有快表的地址变换机构

    为了提高地址变换速度,可在地址变换机构中增设一个具有并行查寻能力的特殊高速缓冲寄存器,又称为“联想寄存器”(Associative Memory),或称为“快表”,在IBM系统中又取名为TLB,用以存放当前访问的那些页表项。此时的地址变换过程如下:

    在这里插入图片描述

5.3 访问内存的有效时间

从进程发出指定逻辑地址的访问请求,经过地址变换,到在内存中找到对应的实际物理地址单元并取出数据,所需要花费的总时间,称为内存的有效访问时间。

5.4 两级和多级页表

  1. 两级页表(Two-Level Page Table)

    针对难以找到大的连续的内存空间来存放页表的问题,可利用将页表进行分页的方法,使每个页面的大小与内存物理块的大小相同,并为它们进行编号,然后离散地将各个页面分别存放在不同的物理块中。同样也要为离散分配的页表再建一张页表,称为外层页表,在每个页表项中记录了页表页面的物理块号。

    在这里插入图片描述

  2. 多级页表

    对于64位的计算机,如果要求它能支持264规模的物理存储空间,则即使是采用三级页表结构也是难以办到的,而在当前的实际应用中也无此必要。故在近两年推出的64位OS中,把可直接寻址的寄存器空间减少为45位长度左右,这样便可利用三级页表结构来实现分页存储管理。

5.5 反置页表(Inverted Page Table)

  1. 反置页表的引入

    为了减少页表占用的内存空间,引入了反置页表。一般页表的页表项是按页号进行排序的,页表项中的内容是物理块号。而反置页表则是为每一个物理块设置一个页表项,并将它们按物理块的编号排序,其中的内容则是页号和其所隶属进程的标识符。

  2. 地址变换

    在利用反置页表进行地址变换时,是根据进程标识符和页号,去检索反置页表。如果检索到与之匹配的页表项,则该页表项的序号i便是该页所在的物理块号,可用该块号与页内地址一起构成物理地址送内存地址寄存器。若检索了整个反置页表仍未找到匹配的页表项,则表明此页尚未装入内存。对于不具有请求调页功能的存储器管理系统,此时则表示地址出错。对于具有请求调页功能的存储器管理系统,此时应产生请求调页中断,系统将把此页调入内存。

6.分段存储管理方式

6.1 分段存储管理方式的引入

为什么要引入分段存储管理方式,可从下面两个方面说明:一方面是由于通常的程序都可分为若干个段,每个段大多是一个相对独立的逻辑单位;另一方面,实现和满足信息共享、信息保护、动态链接以及信息的动态增长等需要,也都是以段为基本单位的。更具体地说,分段存储管理方式更符合用户和程序员如下多方面的需要。

  1. 方便编程
  2. 信息共享
  3. 信息保护
  4. 动态增长
  5. 动态链接

6.2 分段系统的基本原理

  1. 分段

    在分段存储管理方式中,作业的地址空间被划分为若干个段,每个段定义一组逻辑信息。每个段既包含了一部分地址空间,又标识了逻辑关系。其逻辑地址由段号(段名)和短内地址所组成。

    分段地址中的地址具有如下结构:

    在这里插入图片描述

  2. 段表

    在分段式存储管理系统中,是为每个分段分配一个连续的分区。进程中的各个段,可以离散地装入内存中不同的分区中。为保证程序能正常运行,就必须能从物理内存中找出每个逻辑段所对应的位置。为此,在系统中,类似于分页系统,需为每个进程建立一张段映射表,简称“段表”。每个段在表中占有一个表项,其中记录了该段在内存中的起始地址(又称为“基址”)和段的长度,如下图。段表可以存放在一组寄存器中,以利于提高地址转换速度。但更常见的方法是将段表放在内存中。在配置了段表后,执行中的进程可通过查找段表,找到每个段所对应的内存区。可见,段表是用于实现从逻辑段到物理内存区的映射的。

    在这里插入图片描述

  3. 地址变换机构

    为了实现进程从逻辑地址到物理地址的变换功能,在系统中设置了段表寄存器,用于存放段表始址和段表长度TL。在进行地址变换时,系统将逻辑地址中的段号与段表长度TL进行比较。若S>TL,表示段号太大,是访问越界,于是产生越界中断信号。若未越界,则根据段表的始址和该段的段号,计算出该段表项的位置,从中读出该段在内存的起始地址。然后,再检查段内地址d是否超越该段的段长SL。若超过,即d>SL,同样发出越界中断信号。若未越界,则将该段的基址d与段内地址相加,即可得到要访问的内存物理地址。下图示出了分段系统的地址变换过程。

    在这里插入图片描述

  4. 分页和分段的主要区别

    由上述可以看出,分页和分段系统有许多相似之处。比如,两者都采用离散分配方式,且都是通过地址映射机构实现地址变换。但在概念上两者完全不同,主要表现在下述三个方面:

    (1) 页是信息的物理单位。采用分页存储管理方式是为实现离散分配方式,以消减内存的外零头,提高内存的利用率。或者说,分页仅仅只是系统管理上的需要,完全是系统的行为,对用户是不可见的。分段存储管理方式中的段则是信息的逻辑单位,它通常包含的是一组意义相对完整的信息。分段的目的主要在于能更好地满足用户的需要。

    (2) 页的大小固定且由系统决定。在采用分页存储管理方式的系统中,在硬件结构上,就把用户程序的逻辑地址划分为页号和页内地址两部分,也就是说是直接由硬件实现的,因而在每个系统中只能有一种大小页面。而段的长度却不固定,决定于用户所编写的程序,通常由编译程序在对源程序进行编译时,根据信息的性质来划分。

    (3) 分页的用户程序地址空间是一维的。分页完全是系统的行为,故在分页系统中,用户程序的地址是属于单一的线性地址空间,程序员只需利用一个记忆符即可表示一个地址。而分段是用户的行为,故在分段系统中,用户程序的地址空间是二维的,程序员在标识一个地址时,既需给出段名,又须给出段内地址。

6.3 信息共享

分段系统的一个突出优点,是易于实现段的共享,即允许若干个进程共享一个或多个分段,且对段的保护也十分简单易行。

  1. 分页系统中对程序和数据的共享

    在分页系统中,虽然也能实现对程序和数据的共享,但远不如分段系统来到方便。

  2. 分段系统中程序和数据的共享

    在分段系统中,由于是以段为基本单位的,不管该段有多大,我们都只需为该段设置一个段表项,因此使实现共享变得容易。

    可重入代码(Reentrant Code)又称为“纯代码”(Pure Code),是一种允许多个进程同时访问的代码。为使各个进程所执行的代码完全相同,绝对不允许可重入代码在执行中有任何改变。但事实上,大多数代码在执行时都可能有些改变,例如,用于控制程序执行次数的变量以及指针、信号量及数组等。为此,在每个进程中,都必须配以局部数据区,把在执行中可能改变的部分拷贝到该数据区,这样,程序在执行时,只需对该数据区(属于该进程私有)中的内容进行修改,并不去改变共享的代码,这时的可共享代码即成为可重入代码。

6.4 段页式存储管理方式

  1. 基本原理

    段页式系统的基本原理是分段和分页原理的结合,即先将用户程序分成若干个段,再把每个段分成若干个页,并为每一个段赋予一个段名。下图示出了一个作业地址空间的结构。该作业有三个段:主程序段、子程序段和数据段;页面大小为4KB。在段页式系统中,其地址结构由段号、段内页号及业内地址三部分所组成。

    在这里插入图片描述

    在段页式系统中,为了实现从逻辑地址到物理地址的变换,系统中需要同时配置段表和页表。段表的内容与分段系统略有不同,它不再是内存始址和段长,而是页表始址和页表长度。下图示出了利用段表和页表进行从用户地址空间到物理(内存)空间的映射。

    在这里插入图片描述

  2. 地址变换过程

    在段页式系统中,为了便于实现地址变换,须配置一个段表寄存器,其中存放段表始址和段长TL。进行地址变换时,首先利用段号S,将它与段长TL进行比较。若S<TL,表示未越界,于是利用段表始址和段号来求出该段所对应的段表项在段表中的位置,从中得到该段的页表始址,并利用逻辑地址中的段内页号P来获得对应页的页表项位置,从中读出该页所在的物理块号b,再利用块号b和页内地址来构成物理地址。下图示出了段页式系统中的地址变换机构。

    在这里插入图片描述

    在段页式系统中,为了获得一条指令或数据,须三次访问内存。第一次访问是访问内存中的段表,从中取得页表始址;第二次访问是访问内存中的页表,从中取出该页所在的物理块号,并将该块号与页内地址一起形成指令或数据的物理地址;第三次访问才是真正从第二次访问所得的地址中取得指令或数据。

    显然,这使访问内存的次数增加了近两倍。为了提高执行速度,在地址变换机构中增设一个高速缓冲寄存器。每次访问它时,都须同时利用段号和页号去检索高速缓存,若找到匹配的表项,便可从中得到相应页的物理块号,用来与页内地址一起形成物理地址;若未找到匹配表项,则仍需第三次访问内存。由于它的基本原理与分页及分段的情况相似,这里不再赘述。

猜你喜欢

转载自blog.csdn.net/m0_50833438/article/details/114996553