存储器即主存、内存 memory
,分为两大部分:系统区(供操作系统使用)和用户区(划分为一个或多个区域,供用户进程使用)。存储器是计算机系统的重要组成部分,是计算机系统中的一种宝贵而紧要的资源。操作系统本身和用户进程都需要驻留在内存之中才能运行。虽然现代计算机的主存容量不断增大,但软件功能的增强和软件数量的增加,对主存的需求也在不断增多。操作系统中的存储器管理主要是指对内存的管理,它是操作系统的重要功能之一。存储器管理的优劣直接影响到操作系统的性能。
逻辑地址 (
Logical address
) :用户编程时所使用的地址。又称相对地址、虚地址。
地址空间:逻辑地址的集合。
物理地址(Physical address
):内存中的地址。又称绝对地址、实地址。
主存空间:物理地址的集合。
操作系统存储器管理的任务,就是动态实现用户区的管理,以便将尽可能多的进程装入存储器中,提高存储器的利用率。存储器管理要实现的目标是:为用户提供方便、安全和充分大的存储空间。为实现存储管理的目标,存储管理应具有以下功能:
- 分配和回收。由操作系统完成内存空间的分配和管理,使程序设计人员摆脱存储空间分配的麻烦,提高编程效率。为此系统应能记住内存空间的使用情况;实施内存的分配;回收系统或用户释放的内存空间。
- 抽象和映射。主存储器被抽象,使得进程认为分配给它的是地址空间。在多道程序环境下,地址空间中的逻辑地址与内存中的物理地址不可能一致,因此存储管理必须提供地址映射功能,将逻辑地址转换为物理地址。
- 内存的扩充。借助于虚拟存储技术或其他自动覆盖技术,为用户提供比实际内存空间大的地址空间,从而实现从逻辑上扩充内存容量的目的。
- 存储的保护。保证进入内存的各道作业都在自己的存储空间内运行,互不干扰。既要防止一道作业由于发生错误而破坏其他作业,也要防止破坏系统程序。这种保护一般由硬件和软件配合完成。
本章主要介绍存储管理的基本概念、分区存储管理、分页存储管理、分段存储管理。
5.1 存储管理的基本概念
5.1.1 程序的装入
在多道程序环境下,只有当先为某个程序创建进程后,该程序才能运行。而创建进程,首先要将程序和数据装入内存。将一个用户源程序变成一个可在内存中执行的程序,通常要经过以下几步:
- 编译。由编译程序将用户源代码编译成若干个目标模块。
- 链接。由链接程序将编译后形成的目标模块、以及它们所需的库函数链接在一起,形成一个整体装入模块。
- 装入。由装入程序将装入模块装入内存。
在早期阶段,编译、链接、装入、执行这几步是依次执行的。随着计算机技术的发展,为了提高内存利用率,引入了动态装入方式,上述几个步骤往往交织在一起。下面我们讨论现有的程序装入方式。
1. 绝对装入方式
由装入程序根据装入模块中的地址,将程序和数据装入内存。绝对装入程序知道程序驻留在内存的位置,按照装入模块中的地址,将程序和数据装入内存。装入模块被装入内存后,不需对程序和数据的地址进行修改,程序中所使用的是绝对地址。该绝对地址既可在编译或汇编时给出,也可由程序员直接赋予。但由程序员直接给出绝对地址,不仅要求程序员熟悉内存的使用情况,而且一旦程序或数据被修改后,可能要改变程序中的所有地址。
这种装入方式的特点如下:
- 知道程序驻留在内存的位置,故编译时产生绝对地址的编译代码。
- 装入模块后,由于程序的逻辑地址与实际内存地址完全相同,不须对程序和数据的地址进行修改,即不需对地址进行变换。
- 只能将目标模块装入「内存中事先指定的位置」,只适用于单道程序环境。
- 通常在程序中采用符号地址,然后在编译或汇编时,再将这些符号地址转换成绝对地址。
2. 可重定位装入方式
在多道程序环境下,由于编译程序不能预知目标模块在内存中的位置,因此目标模块的起始地址通常都是从 0 0 0 开始,程序中的所有其他地址,都是相对于起始地址计算的。此时不可能再用绝对装入方式,而应该采用可重定位装入方式。
一个应用程序经编译后,通常会形成若干个目标程序,这些目标程序再经过链接而形成可装入执行的程序。这些程序的地址都从 0 0 0 开始编址,程序中的其他地址都相对于该起始地址计算;由这些地址所形成的地址范围称为地址空间,其中的地址称为逻辑地址。逻辑地址也称为相对地址、虚地址。
存储空间也称为内存空间,是指内存中一系列存储信息的物理单元的集合,其中的地址称为物理地址。物理地址又称为绝对地址、实地址。简单地说,地址空间是逻辑地址的集合;存储空间是物理地址的集合。一个是虚的概念,一个是实的物体。一个编译好的程序存在于它自己的地址空间中,当它需要在计算机上运行时,才把它装入存储空间。
一般情况下,作业装入内存时分配的存储空间与它的地址空间不一致,因此作业所要访问的指令及数据的实际地址与地址空间中的地址不同,如图5.1所示。如果在作业装入时或在它执行时,不对有关地址部分进行调整,则将导致错误的结果。
例如,图5.1给出了一个程序装入内存前后的情况。在地址空间
100
100
100 号单元处有一条指令 mov r1, [500]
,其功能是将
500
500
500 号单元中的数据
1234
1234
1234 装入到寄存器
r
1
r_1
r1 中。现在该程序存放在内存单元
1000
1000
1000 开始的区域中,若不对该指令的地址部分进行调整,那么程序执行时取出的数据不正确。
可以看出,程序装入内存后的起始地址为
1000
1000
1000 。也就是说,程序的逻辑地址
0
0
0 与内存中的物理地址
1000
1000
1000 对应。相应的逻辑地址
100
100
100 对应的物理地址为
1100
1100
1100 ,逻辑地址
500
500
500 对应的物理地址为
1500
1500
1500 。当程序执行 mov
指令时,应该从物理地址
1500
1500
1500 中取出数据存入寄存器
r
1
r_1
r1 。也就是说,为了保证程序执行的正确性,需要对程序中的地址进行变换,这种将逻辑地址变换成物理地址的过程称为地址重定位,也称为地址映射或地址变换。可重定位装入方式的特点如下:
- 多道程序环境下,所得到的目标模块的起始地址通常是从 0 0 0 开始的,程序中的其他地址也都是相对于起始地址计算的。
- 在采用可重定位装入程序将装入模块装入内存后,会使装入模块的所有逻辑地址与实际装入内存的物理地址不同。
- 实际装入内存的物理地址=逻辑地址+程序起始地址。
- 编译时产生相对地址的目标代码,由装入程序根据内存当时的实际使用情况,将装入模块装入到内存的适当地方。地址变换通常是在装入时一次完成的,以后不再改变,故称为静态重定位。
- 不需硬件支持,但程序运行时不能在内存移动,程序需要连续存储空间,难以共享。
3. 动态运行时装入方式
虽然可重定位装入方式可将装入模块装入内存中任何允许的位置,但并不允许程序在内存中移动位置。因为程序在内存中移动,意味着它们的物理地址都发生了变化,此时必须对程序和数据的绝对地址进行修改,才能正常运行。当程序在内存中的位置需要改变时,就应该采用动态运行时装入方式。
动态运行时装入程序,在把装入模块装入到内存后,并不立即把装入模块中的相对地址转换为绝对地址,而是把这种地址转换推迟到程序要真正执行时才进行。因此,装入内存后的所有地址仍都是相对地址。为了不影响指令的执行速度,这种方式需要特殊的硬件支持。
我们把这种地址变换方式称为动态重定位。动态重定位是在程序执行过程中,每当访问指令或数据时,将要访问程序或数据的逻辑地址转换成物理地址。由于重定位过程是在程序执行期间随着指令的执行逐步完成的,故称为动态重定位。
动态重定位的实现要依靠硬件地址变换机构,最简单的实现方法是利用一个重定位寄存器。当某个作业开始执行时,操作系统负责把「该作业在内存中的起始地址」送入重定位寄存器中,之后,在作业的整个执行过程中,每当访问内存时,系统会自动地将重定位寄存器的内容加到逻辑地址中去,从而得到了该逻辑地址对应的物理地址。图5.2给出了动态地址变换过程的示例。
在该例中,作业被装入到内存中
1000
1000
1000 号单元开始的一片存储区中,当该作业执行时,操作系统将重定位寄存器设置为
1000
1000
1000 。当程序执行到
1100
1100
1100 号单元中的 mov
指令时,硬件地址变换机构自动地将这条指令中的取数地址
500
500
500 、加上重定位寄存器的内容,得到物理地址
1500
1500
1500 。然后以
1500
1500
1500 作为访问内存的物理地址,将数据
1234
1234
1234 送入寄存器
r
1
r_1
r1 中。
动态重定位的特点是:
- 可以将程序分配到不连续的存储区中;
- 在程序运行之前只需装入它的部分代码即可投入运行,然后在程序运行期间,根据需要动态申请分配内存;
- 便于程序段的共享;
- 可以向用户提供一个比内存的存储空间大得多的地址空间,可以实现虚拟存储。
- 但动态重定位需要附加的硬件支持,且实现存储管理的软件算法比较复杂。
与静态重定位相比,动态重定位的优点是显而易见的,因此现代计算机系统中主要采用动态重定位方法。
5.1.2 程序的链接
链接程序的功能,是将「经过编译或汇编后得到的目标模块」以及所需的库函数,装配成一个完整的装入模块。实现链接的方法有三种:静态链接、装入时动态链接和运行时动态链接。
1. 静态链接
通常由编译程序产生的所有目标模块,其起始地址都为
0
0
0 ,每个模块中的地址都是相对于
0
0
0 的。在链接成一个装入模块后,有些模块的起始地址不再是
0
0
0 ,而要(对模块中的地址)进行修改。这种先进行链接所形成的一个完整的装入模块,又称为可执行文件。静态链接是在程序运行之前、生成可执行文件时进行的。在目标模块中记录符号地址 symbolic address
,而在可执行文件中改写为指令直接使用的数字地址。
可执行文件生成后,通常不再拆开它,要运行时可直接将它装入内存。这种事先进行链接,以后不再拆开的链接方式,称为静态链接方式。
2. 装入时动态链接
用户源程序经编译后得到的目标模块,是由装入程序边装入边链接的,即在装入一个目标模块时,若发生一个外部模块调用,将引起装入程序去寻找相应的外部目标模块,并将它装入内存,同时还有修改目标模块中的相对地址。装入时动态链接的优点是:便于软件版本的修改和更新;便于实现目标模块的共享。
3. 运行时动态链接
虽然前面所介绍的动态装入方式,能将一个装入模块装入到内存任何允许的地方,但装入模块的结构是静态的,主要表现在以下两个方面:
- 在进程的整个执行期间,装入模块是不改变的。
- 每次运行时,装入模块都是相同的。实际上每次运行的模块可能是不相同的,但由于无法预知每次需要运行哪些模块,所以将所有可能要运行到的模块,在装入时全部链接在一起,使每次执行的装入模块都是相同的。显然这种方式并不高效。
为了提供效率,将某些目标模块的链接推迟到执行时才进行。即在执行过程中,若发现一个被调用模块尚未装入内存,则由操作系统去找到该模块,将它装入内存,并将其链接到调用者模块上。例如,先将主程序模块装入运行,当发生了外部访问时,进行动态链接。特点是加快了程序装入,节省了内存。
5.1.3 内存保护
在多道程序环境下,操作系统必须提供内存保护机制,存储保护是为了防止一个进程有意或无意破坏操作系统进程或其他用户进程。常用的存储保护方法如下:
- 界限保护(界限寄存器)。通过对每个进程设置一对界限寄存器来防止越界访问,达到存储保护的目的。采用界限寄存器方法实现存储保护又有两种实现方式:上、下界寄存器方法和基址、限长寄存器方法。
- 用上、下界寄存器分别存放进程存储空间的结束地址和开始地址。在进程运行过程中,将每一个访问内存的地址都同这两个寄存器的内容进行比较,在正常情况下,这个地址应大于或等于下界寄存器且小于上界寄存器的内容。若超出了上下界寄存器的范围,则产生越界中断信号,并停止进程的运行。
- 用基址和限长寄存器分别存放进程存储空间的起始地址及进程地址空间长度。当进程执行时,将每一个访问内存的相对地址和这个限长寄存器比较,若逻辑地址超过限长,则产生越界中断信号,并停止进程的运行。
- 访问方式保护(保护键)。通过保护键匹配来判断存储访问方式是否合法。对于允许多个进程共享的存储区域,每个进程都有自己的访问权限。存储保护键方法是给每个存储块分配一个单独的保护键,它相当于一把锁。存储块不同于分区,一个分区由若干存储块组成,每个存储块大小相同,一个分区的大小必须是存储块的整倍数。此外,进入系统的每个进程也赋予一个保护键,它相当于一把钥匙。当进程运行时,检查钥匙和锁是否匹配,如果二者不匹配,则系统发出保护性中断信号,停止进程运行。
- 环保护。处理器状态分为多个环
ring
,分别具有不同的存储访问特权级别privilege
,通常是级别高的在内环,编号越小,级别越高。操作系统核心处于 0 0 0 环,某些重要实用程序和操作系统服务处于中间环,一般应用程序占据外环。环保护的基本原则是:一个程序可访问驻留在同环或更低级别环的数据;可调用驻留在同环或更高级别环的服务。
除上述保护方案外,还有四种存取权限:禁止做任何操作、只能执行、只能读、读/写。
5.2 单一连续分配
单一连续分配是一种最简单的存储管理方式,通常只能用于单用户、单任务的操作系统中。这种存储管理方式将内存分为两个连续存储区域,其中一个存储区域固定分配给操作系统使用,另一个存储区域给用户作业使用。通常,用户作业只占用所分配空间的一部分,剩下的一部分存储区域实际上浪费掉了。例如,一个容量为256KB的内存中,操作系统占用32KB,剩下的224KB全部分配给用户作业,如果一个作业仅需要64KB,那么就有160KB的存储空间没有被利用,如图5.3所示。
单一连续分配方式主要采用静态分配,即作业一旦进入内存,就要等到它执行结束后才能释放内存。单一连续分配方式的主要特点是管理简单,只需要很少的软件和硬件支持,且便于用户了解和使用。但由于采用这种存储分配方式,内存中只能装入一道作业,从而使各类资源的利用率都不高。
5.3 分区分配
分区分配方式是满足多道程序设计需要的一种最简单的存储管理方法。在分区分配方式中,内存被划分成若干个分区,分区的大小可以相等也可以不相等,除操作系统占用一个分区之外,其他区域由并发进程共享,每个分区可以容纳一个用户作业。按分区数目的变化情况,可以将分区存储
管理进一步划分为固定分区存储管理和动态分区存储管理。
5.3.1 固定分区
固定分区 fixed-sized partition
存储管理方法是最早使用的、一种可以运行多道程序的存储管理方法,它将内存空间划分为若干个固定大小的分区,每个分区中可以装入一道程序。分区的大小可以不等,但事先必须确定,分区的位置及大小在运行期间不能改变。
为了实现固定分区分配,系统需要建立一张分区说明表,以记录分区的使用情况,其中包括分区号、分区大小、分区起始地址及状态等信息。例如,将内存的可用区域划分为五个分区,如图5.4所示。图5.4(a)为固定分区说明表,图5.4(b)为内存空间分配情况。
当某个用户程序要装入内存时,由内存分配程序检索分区说明表,从表中找出一个能满足要求的空闲分区分配给该程序,然后修改分区说明表中相应表项的状态信息;若找不到满足其大小要求的空闲分区,则拒绝为该程序分配内存。当程序执行完毕不再需要内存资源时,释放程序占用的分区,管理程序只需将对应分区的状态信息设置为未分配即可。
由于作业的大小并不一定与某个分区大小相等,因此,在绝大多数已分配的分区中,都有一部分存储空间被浪费掉了,由此可见,采用固定分区分配存储管理方法,内存不能得到充分利用,存在内存碎片。
5.3.2 动态分区分配
动态分区分配又称为可变分区分配 variable-pattition
,是一种动态划分存储器的分区方法,这种分配方法并不预先设置分区的数目和大小,而是在作业装入内存时,根据作业的大小动态建立分区,使分区大小正好满足作业的需要。因此系统中分区的大小是可变的,分区的数目也是可变的。
1. 分区分配中的数据结构
为了实现动态分区分配,系统中也需要设置相应的数据结构来记录内存的使用情况,常用的数据结构形式有空闲分区表和空闲分区链两种。
- 空闲分区表。空闲分区表的格式如图5.5所示,内存中的每个空闲分区占用一个表项,每个表项包含分区号、分区起始地址、分区大小及状态等信息。其表项类似于固定分区。
- 空闲分区链。空闲分区链使用链表指针将内存中的空闲分区链接起来,构成如图5.6所示的空闲分区链。为此应在每个空闲分区的起始若干字节中,存放分区的相关信息,包括空闲分区的大小和指向下一个空闲分区的指针。
2. 分区分配算法
为了将一个作业装入内存,应按照一定的分配算法从空闲分区表(或空闲分区链)中,选出一个满足作业需求的分区分配给作业,如果这个空闲分区的容量比作业申请的空间容量要大,则该分区一分为二,一部分分配给作业,剩下的一部分仍然留在空闲分区表(或空闲分区链)中,同时还需要对空闲分区表(或空闲分区链)中的有关信息进行修改。
目前常用的分区分配算法有以下四种:首次适应算法、循环首次适应算法、最佳适应算法及最坏适应算法。
-
首次适应算法
first fit
:又称最先适应算法,该算法要求空闲分区按地址递增的次序排列。在进行内存分配时,从空闲分区表(或空闲分区链)首开始顺序查找,直到找到「第一个能满足其大小要求的空闲分区」为止。然后,再按照作业大小,从该分区中划出一块内存空间分配给请求者,余下的空闲分区仍然留在空闲分区表(或空闲分区链)中。
该算法的特点是:优先利用内存低地址部分的空闲分区,从而保留了高地址部分的大空闲区。但由于低地址部分不断被划分,致使低地址端留下许多难以利用的小空闲分区,而每次查找又都是从低地址部分开始的,这无疑增加了查找可用空闲分区的开销。 -
循环首次适应算法
next fit
:它是由首次适应算法演变而来的。在为作业分配内存空间时,不再每次从空闲分区表(或空闲分区链)首开始查找,而是从「上次找到的空闲分区的下一个空闲分区」开始查找,直到找到「第一个能满足其大小要求的空闲分区」为止。然后,再按照作业大小,从该分区中划出一块内存空间分配给请求者,余下的空闲分区仍然留在空闲分区表(或空闲分区链)中。
该算法的特点是:使存储空间的利用更加均衡,不致使小的空闲分区集中在存储区的一端。但这会导致缺乏大的空闲分区。 -
最佳适应算法
best fit
:要求空闲分区按容量大小递增的次序排列。在进行内存分配时,从空闲分区表(或空闲分区链)首开始顺序查找,直到找到「第一个能满足其大小要求的空闲分区」为止。按这种方式为作业分配内存,就能把既满足作业要求、又与作业大小最接近的空闲分区分配给作业。如果该空闲分区大于作业的大小,则需要从该分区中划出一块内存空间分配给请求者,剩余空闲分区仍然留在空闲分区表(或空闲分区链)中。
最佳适应算法的特点是:若存在与作业大小一致的空闲分区,则它必然被选中;若不存在与作业大小一致的空闲分区,则只划分比作业稍大的空闲分区,从而保留了大的空闲分区。但空闲分区一般不可能正好和作业申请的内存空间大小相等,因而将其分割成两部分时,往往使剩下的空闲分区非常小,从而在存储器中留下许多难以利用的小空闲分区。 -
最坏适应算法
worst fit
:要求空闲分区按容量大小递减的次序排列。在进行内存分配时,先检查空闲分区表(或空闲分区链)中的第一个空闲分区,若第一个空闲分区小于作业要求的大小,则分配失败;否则从该空闲分区中划出与作业大小相等的一块内存空间分配给请求者,余下的空闲分区仍然留在空闲分区表(或空闲分区链)中。
最坏适应算法的特点是:总是挑选满足作业要求的最大分区分配给作业,这样使分给作业后剩下的空闲分区也比较大,于是也能装下其他作业。但由于最大的空闲分区总是因首先分配而划分,当大作业到来时,其存储空间的申请往往得不到满足。
如何衡量分配算法的好坏:对于某一个作业序列来说,若某种分配算法能将该作业序列中所有作业安置完毕,则称该分配算法对这一作业序列合适,否则称为不合适。所以关键在于实际的应用效果。
【分区分配算法例题1】下表给出了某系统的空闲分区表,系统采用可变式分区存储管理策略。现有以下作业序列:96K
、20K
、200K
。若用首次适应算法和最佳适应算法来处理这些作业序列,试问哪一种算法可以满足该作业序列的请求?
答:采用最佳适应算法分配(按容量大小递增的次序)
① 申请 96K
,选中
5
5
5 号分区,
5
5
5 号分区大小与申请空间大小一致,应从空闲分区表中删去该表项;
② 申请 20K
,选中
1
1
1 号分区,分配后
1
1
1 号分区还剩下 12K
;
③ 申请 200K
,选中
4
4
4 号分区,分配后剩下 18K
。
由于最佳适应算法的使用,空闲分区的数目和容量大小会不断改变,因此需要时刻注意空闲分区的重新排列。
采用首次适应算法分配:
① 申请 96K
,选中
4
4
4 号分区,进行分配后
4
4
4 号分区还剩下 122K
;
② 申请 20K
,选中
1
1
1 号分区,分配后剩下 12K
;
③ 申请 200K
,现有的五个分区都无法满足要求,该作业等待。显然采用首次适应算法进行内存分配,无法满足该作业序列的需求。
【分区分配算法例题2】下表给出了某系统的空闲分区表,系统采用可变式分区存储管理策略。现有以下作业序列:申请 150KB
,申请 50KB
,申请 90KB
,申请 80KB
。
若用首次适应算法和最佳适应算法来处理这些作业序列,试问哪一种算法可以满足该作业序列的请求?
答:分析易得,首次适应算法可以满足该作业序列的请求。
以首次适应算法及空闲链表为例,申请分区大小为
x
x
x ,
e
e
e 是规定的不再分割的剩余区大小。
3. 分区的回收
当作业执行结束时,应回收已使用完毕的分区。系统根据回收分区的大小及起始地址,在空闲分区表(或空闲分区链)中检查、是否有与其相邻的空闲分区,如有相邻空闲分区,则应合并成为一个大的空闲区,然后修改有关的分区状态信息。回收分区与已有空闲分区的相邻情况有以下四种:
- 回收分区 r r r 上邻接一个空闲分区,如图5.7(a)所示。此时应将回收区 r r r 与上邻接分区 F 1 F_1 F1 合并成一个连续的空闲分区。合并分区的首地址为空闲分区 F 1 F_1 F1 的首地址,其大小为二者之和。
- 回收分区 r r r 下邻接一个空闲分区,如图5.7(b)所示。此时应将回收区 r r r 与下邻接分区 F 2 F_2 F2 合并成一个连续的空闲分区。合并分区的首地址为回收分区 r r r 的首地址,其大小为二者之和。
- 回收分区 r r r 上、下邻接空闲分区,如图5.7c)所示。此时应将回收区 r r r 与上、下邻接分区合并成一个连续的空闲分区。合并分区的首地址为与 r r r 上邻接的空闲分区 F 1 F_1 F1 的首地址,其大小为三者之和,且应把与 r r r 下邻接的空闲分区 F 2 F_2 F2 从空闲分区表(或空闲分区链)中删去。
- 回收分区
r
r
r 不与任何空闲分区相邻,这时应为回收区单独建立一个新表项,填写分区大小及起始地址等信息,并将其加入到空闲分区表(或空闲分区链)中的适当位置。
5.3.3 可重定位分区分配
在分区存储管理方式中,必须把作业装入到一片连续的内存空间中。如果系统中有若干个小分区,其总容量大于要装入的作业,但由于它们不相邻接,致使作业不能装入内存。如图5.8(a)所示,内存中有四个小分区不相邻接,它们的容量分别为20KB、30KB、15KB和25KB,其总容量为90KB。如果现在有一个作业到达,要求分配40KB的内存空间,由于系统中所有空闲分区的容量均小于40KB,故此作业无法装入内存。
1. 拼接技术
内存中无法利用的存储空间称为碎片 fragmentation
,又称为零头。在分区存储管理方式下,系统运行一段时间后,内存中的碎片会占据相当数量的空间。根据碎片出现的位置,可以将碎片分为内部碎片和外部碎片——内部碎片是指分配给作业的存储空间中未被利用的部分,外部碎片是指系统中无法利用的小存储块。如固定分区分配中存在内部碎片,而动态分区分配中存在外部碎片。
解决碎片问题的办法之一是,将存储器中所有已分配区移动到内存的一端,使本来分散的多个小空闲区连成一个大的空闲区,如图5.8(b)所示。这种通过移动把多个分散的小分区、拼接成一个大分区的方法称为拼接或紧凑 compaction
,也可以称为紧缩。
在拼接过程中,进程需要在内存移动,因此拼接的实现需要动态重定位技术的支持。另外,利用拼接技术消除碎片,需要对分区中的大量信息进行移动,这一工作要耗费大量的CPU时间。为了减少信息移动的数量,拼接后的空闲区放在何处不能一概而论,可以根据拼接时需要移动进程的大小和个数,来确定空闲区究竟应该放在何处,如内存的低地址端、中间或高地址端。
除上述两个问题之外,拼接技术的实现还存在一个拼接时机的问题,这个问题有两种解决方案。第一种方案是在某个分区回收时立即进行拼接,这样在内存中总是只有一个连续的空闲区,但该实现方案因拼接频率过高而使系统开销加大。第二种方案是,当找不到足够大的空闲分区、且空闲分区的总容量可以满足作业要求时进行拼接,该实现方案拼接的频率比上一种方案要小得多,但空闲分区的管理稍为复杂一些。
2. 可重定位分区分配技术
可重定位分区分配算法与动态分区分配算法基本相同,差别仅在于前者增加了拼接功能。在可重定位分区分配算法中,若系统中存在满足作业空间要求的空闲分区,则按照与动态分区分配相同的方式分配内存;若系统中找不到满足作业要求的空闲分区,且系统中空闲分区容量总和大于作业要求,则进行拼接。图5.9给出了可重定位分区分配算法的框图。
可重定位分区分配技术的特点是可以消除碎片,能够分配更多的分区,有助于多道程序设计及提高内存利用率,但拼接要花费大量的CPU时间。
5.4 伙伴系统
由于固定分区存储管理限制了内存中进程的数目,并且可能存在大量的碎片,存储空间的利用率非常低;动态分区虽然可以通过拼接技术较好地解决碎片问题,但拼接需要大量的系统开销。本节介绍介于固定分区、动态分区之间的折中方案,是一种较为实用的动态存储管理方法——伙伴系统 buddy system
。
该系统结合计算机二进制的特点来划分存储空间,使每个块的长度都限制为 2 k ( k = 1 , 2 , … ) 2^k\ (k=1,2,\dots) 2k (k=1,2,…) 。例如,当进程申请 n n n 个单元的存储空间时,设有 2 k − 1 < n ≤ 2 k 2^{k-1} <n ≤2^k 2k−1<n≤2k ,则选择最接近 n n n 、且不小于申请空间量的 2 k 2^k 2k 个单元的块,分配给该进程。
5.4.1 伙伴算法
伙伴系统采用伙伴算法对空闲内存进行管理。该方法通过不断对分大的空闲存储块、来获得小的空闲存储块。当内存块释放时,应尽可能合并空闲块。
- 设系统初始时可供分配的内存空间为 2 m 2^m 2m 个单元。当进程申请大小为 n n n 的空间时,设 2 i − 1 < n ≤ 2 i 2^{i-1}<n≤2^i 2i−1<n≤2i ,则为进程分配大小为 2 i 2^i 2i 的空间。
- 如系统不存在大小为
2
i
2^i
2i 的空闲块,则查找系统中是否存在大于
2
i
2^i
2i 的空闲块,若找到则对其
进行对半划分,直到产生大小为 2 i 2^i 2i 的空闲块为止。当一块被划分成两个大小相等的两块时,这两块被互相称为伙伴。需要注意的是:伙伴是大小相等且相邻的两个物理块;反之,大小相等且相邻的两个物理块不一定是伙伴。只有伙伴才能合并。 - 当进程执行完释放所占用的存储空间时,系统应检查释放块的伙伴是否是空闲块,若是,则与其伙伴合并而得到较大的空闲块,这个较大的空闲块若又有空闲的伙伴,则继续合并,直到找不到空闲的伙伴为止。
设某空闲块的开始地址为
d
d
d ,长度为
2
k
2^k
2k ,其伙伴的开始地址为:
B
u
d
d
y
(
k
,
d
)
=
{
d
+
2
k
,
若
d
m
o
d
2
k
+
1
=
0
d
−
2
k
,
若
d
m
o
d
2
k
+
1
=
2
k
\mathrm{Buddy(k, d) }= \begin{cases} d+2^k,\ \ 若d \bmod 2^{k+1}=0\\ d-2^k,\ \ 若d \bmod 2^{k+1}= 2^k \end{cases}
Buddy(k,d)={d+2k, 若dmod2k+1=0d−2k, 若dmod2k+1=2k
如果参与分配的
2
m
2^m
2m 个单元从
a
(
a
≠
0
)
a\ (a≠0)
a (a=0) 开始,则长度为
2
k
2^k
2k 、开始地址为
d
d
d 的块,其伙伴的开始地址为:
B
u
d
d
y
(
k
,
d
)
=
{
d
+
2
k
,
若
(
d
−
a
)
m
o
d
2
k
+
1
=
0
d
−
2
k
,
若
(
d
−
a
)
m
o
d
2
k
+
1
=
2
k
\mathrm{Buddy(k, d) } =\begin{cases} d+2^k,\ \ 若 (d-a) \bmod 2^{k+1}=0 \\ d-2^k,若 (d-a) \bmod 2^{k +1}= 2^k \end{cases}
Buddy(k,d)={d+2k, 若(d−a)mod2k+1=0d−2k,若(d−a)mod2k+1=2k
设系统中初始内存空间大小为 1MB
,进程请求和释放空间的操作序列为:
- 进程
A
申请200KB
;B
申请120KB
;C
申请240KB
;D
申请100KB
; - 进程
B
释放;E
申请60KB
; - 进程
A
、C
释放; - 进程
D
释放;进程E
释放。
分配过程示意图见图5.10。
伙伴系统的不足在于,分配和回收时需要对伙伴进行分拆及合并;存储空间有浪费。
5.4.2 伙伴系统的二叉树表示
可以用二叉树表示内存分配情况。叶结点表示存储器中的当前分区,如果两个伙伴是叶子,则至少有一个被分配。下图表示 A(200)
、B(120)
、C(240)
、D(100)
分配之后的情况。
5.5 覆盖与交换技术
覆盖与交换技术是在多道程序环境下用来扩充内存的两种方法。 覆盖技术主要用在早期的操作系统中,其目标是在较小的可用内存中运行较大的进程,常与分区管理配合使用;而交换技术则在现代操作系统中仍具有较强的生命力,当内存空间紧张时,将内存中的某些进程暂时移到外存,把需要运行的进程从外存移到内存。交换技术实现了内外存之间的交换,多用于分时系统中。下面我们将讨论这两种技术的实现思想。
5.5.1 覆盖技术
覆盖技术主要用在早期的操作系统中,因为在早期的单用户系统中内存的容量一般少于64KB,可用的存储空间受到限制,某些大作业不能一次全部装入内存中,这就发生了大作业与小内存的矛盾。为了能在一个较小的内存中装入一个较大的程序,就引入了覆盖技术。
所谓覆盖技术就是把一个大程序划分为一系列覆盖,每个覆盖是一个相对独立的程序单位
- 把程序执行时「不要求同时装入内存的覆盖」组成一组,称为覆盖段;
- 将一个覆盖段分配到同一个存储区中,这个存储区称为覆盖区,它与覆盖段一一对应。
- 显然,为了使一个覆盖区能为「相应覆盖段中的每个覆盖」在不同时刻共享,覆盖区的大小应由「覆盖段中最大的覆盖」来确定。
覆盖技术要求程序员把一个程序划分成不同的程序段,并规定好它们的执行和覆盖顺序,操作系统根据「程序员提供的覆盖结构」来完成程序段之间的覆盖。例如,假设某进程由
A
,
B
,
C
,
D
,
E
,
F
A, B, C, D, E, F
A,B,C,D,E,F 共
6
6
6 个程序段构成,它们的大小分别是20KB、50KB、30KB、20KB、40KB、30KB,它们之间的调用关系如图5.11(a)所示。从图5.11(a)中可以看出,程序段
B
B
B 不会调用程序段
C
C
C ,程序段
C
C
C 也不会调用程序段
B
B
B ,因此,程序段
B
B
B 和程序段
C
C
C 不需要同时驻留在内存,它们可以共享同一内存区;同理,程序段
D
,
E
,
F
D, E, F
D,E,F 也可以共享同一内存区。
在图5.11(b)中,整个程序段被分为两个部分,一个是常驻内存部分,称为主程序,它与所有被调用程序段有关,因而不能被覆盖,图中的程序
A
A
A 是主程序;另一部分是覆盖部分,被分为两个覆盖段,程序段
B
,
C
B, C
B,C 组成覆盖段
0
0
0 ,程序段
D
,
E
,
F
D, E, F
D,E,F 组成覆盖段
1
1
1 ,两个覆盖段对应覆盖区的大小分别为50KB与40KB。这样,虽然该程序所要求的内存空间是190KB,但由于采用了覆盖技术,只需110KB的内存空间就可以执行。
需要指出的是,覆盖策略并不唯一。例如图5.11(a)中的调用关系可以使用另一种覆盖结构。另一种覆盖方法如下,一共需要100K内存。
- A A A(20K)占一个分区:20K;
- B B B(50K)、 D D D(20K)和 E E E(40K)共用一个分区:50K;
- C C C(30K)和 F F F(30K)共用一个分区:30K。
覆盖技术要求编程时必须划分程序模块结构,确定程序模块之间的覆盖关系,增加了编程复杂度。从外存装入覆盖文件,是以时间延长来换取空间节省的。覆盖结构由程序员指明,对程序员不透明,这样就增加了程序员的负担。
5.5.2 交换技术
在多道程序环境下,一方面内存中存在一些阻塞进程占据大量的存储空间;另一方面外存上有许多作业因无空闲内存而不能进入内存运行。为此引入了交换。
交换技术也称为对换技术,该技术最早用在MIT的兼容分时系统 CTSS 中,在该系统中任何时刻内存里只装入了一道用户作业,当内存中的用户作业运行一段时间后,就会因为分配给它的时间片用完、或需要其他资源而暂停运行,此时系统就将它交换到外存上,并将另一个作业调入内存运行,如此重复。这样,就可以在存储容量不大的小型机上实现分时运行,早期的一些小型分时系统多数都采用这种交换技术。
交换是指把暂时不能执行的进程部分(或全部)从内存换出到外存中去,以便腾出内存空间,或将「重又具备运行条件的进程」从外存换入到内存中,并将控制权转给它,让其在系统上运行的一种技术。
进程的换出与换入:
- 进程的换出:先选择换出进程(阻塞、优先级低、驻留时间长),再申请对换空间,然后启动磁盘写,若成功则可释放其内存空间并修改数据结构。
- 进程换入:先选择换入进程(就绪、换出时间长),再申请内存空间,然后启动磁盘读。
交换进程由换出和换入两个过程组成,交换空间设置在外存交换区中,采用连续分配方式,使用与动态分区分配类似的数据结构和分配回收算法。交换空间管理的主要目标是提高作业或进程换入/换出速度。换出过程将内存中的数据和程序换出到外存交换区中,而换入过程则将外存交换区中的数据和程序换入到内存中。
交换技术增加并发运行的程序数目,并且给用户提供适当的响应时间;编写程序时不影响程序结构。但对换入和换出的控制增加处理机开销;程序通常整个地址空间都进行传送,没有考虑执行过程中地址访问的统计特性。
与覆盖技术相比:
- 交换技术由操作系统自动完成,不需要用户参与,不要求程序员给出程序段之间的覆盖结构;而覆盖技术需要专业的程序员给出作业各部分之间的覆盖结构,并清楚系统的存储结构;
- 交换技术主要是在作业或进程之间进行,交换单位通常是整个进程的地址空间;而覆盖则主要在同一个作业或进程内部进行。另外,覆盖只能覆盖与覆盖程序段无关的程序段。
- 覆盖技术主要在早期的操作系统中采用,而交换技术在现代操作系统中仍具有较强的生命力。
5.6 分页存储管理
在分区存储管理中,要求将作业存放在一片连续的存储区域中,因而会产生内存碎片问题。尽管通过拼接技术可以解决碎片问题,但拼接非常耗时,这种解决方案的代价较高。如果能取消作业对存储区的连续性要求、将一个作业存放到多个不相邻接的内存区域中,就可以避免拼接,并有效地解决碎片问题。基于这一思想引入了分页存储管理,分页存储管理也可以称为页式存储管理。
5.6.1 分页实现思想
在分页存储管理中,将作业的地址空间划分成若干大小相等的区域,称为页或页面。相应地,也将内存空间划分成与页大小相等的区域,称为块或物理块。在为作业分配内存空间时,总是以块为单位来分配,可以将「作业中的某一页」放到「内存的某一个空闲块」中。
在调度作业运行时,必须将它的所有页面一次调入内存。若内存中没有足够的物理块,则作业等待。这样的存储管理方式称为简单分页存储管理方式或纯分页存储管理方式。分页系统中是否有碎片?有。就是页内碎片——由进程最后一页未装满而形成的碎片。
在分页存储管理系统中,页面大小的选择非常重要。如果选择的页面较小,则可以使页内碎片较小并减少内存碎片的总量,有利于提高内存利用率;但另一方面,也会使每个进程中包含的页面数较多,从而导致页表过长,占用大量内存空间,同时还会降低页面换入/换出的效率。如果选择的页面较大,则可以减少页表长度,提高页面换入/换出的效率,但却又使页内碎片增大。因此,页面大小应该选择适中,通常为 2 2 2 的幂,一般在512B到8KB之间。
分页存储管理系统中的逻辑地址结构如图5.11所示,它包含两部分,前一部分为页号 page number
P
P
P ,后一部分为页内位移 page offset
W
W
W ,也称作页内地址。在如图5.12所示的地址结构中,逻辑地址的长度为
32
32
32 位,其中
0
∼
11
0\sim 11
0∼11 位为页内位移,即每页大小为4KB,
12
∼
31
12\sim 31
12∼31 位为页号,即地址空间最多允许有1M个页面。
一般来说,特定机器的地址结构是确定的。如果某个逻辑地址为 A A A ,页面大小为 L L L ,则页号 P P P 和页内位移 W W W 可按下述公式求得:
- 页号: P = int(A/L) \text{P = int(A/L)} P = int(A/L)
- 页内位移: W = A % L \text{W = A \% L} W = A % L
其中:页号 P P P 等于逻辑地址除以页面大小所得的商;页内位移等于逻辑地址除以页面大小的余数。例如,某系统页面大小为2KB,则逻辑地址 3000 3000 3000 对应的页号 P = 1 P= 1 P=1 ,页内位移 W = 952 W= 952 W=952 。
5.6.2 页表
在分页存储管理系统中,进程的各页面分散存放在内存中,为了便于在内存中找到「进程的各个页面所对应的物理块」,系统为每个进程建立一张页面映象表,简称页表,记录各页面在内存中对应存放的物理块号。页表一般存放在内存中,图5.13给出了一个页表的例子,从图中可以看出,页面 2 2 2 存放的内存块号是 7 7 7 。
注意:英文
Page frame
在目前国内操作系统教材中翻译的有物理块(块)、页框、页架、页帧和帧等,读者应加以注意,它表示的是与一个逻辑页相对应的一个物理块。
页面大小的选择:页面的大小应适中。若页面太大,以至和一般进程大小相差无几,则页面分配退化为分区分配,同时页内碎片也较大;若页面太小,虽然可减少页内碎片,但会导致页表增长。因此,页面大小应适中,通常为
2
2
2 的幂,一般在 512B
到 64KB
之间。
页表一般存放在内存中。也可以在页表中设置存取控制字段,以实现存储保护。
- 存储分块表:用来记录内存中各物理块的使用情况及未分配物理块总数。存储分块表可用下述方式表示:
- 位示图:利用二进制的一位表示一个物理块的状态,
1
1
1 表示已分配,
0
0
0 表示未分配。所有物理块状态位的集合构成位示图。
- 空闲存储块链:将所有的空闲存储块用链表链接起来,利用空闲物理块中的单元存放指向下一个物理块的指针。
- 位示图:利用二进制的一位表示一个物理块的状态,
1
1
1 表示已分配,
0
0
0 表示未分配。所有物理块状态位的集合构成位示图。
存储空间的分配及回收:
- 页面分配:计算进程所需页面数,然后在请求表中登记进程号、请求页面数等。如存储分块表中有足够的空闲块可供进程使用,则在系统中取得页表始址,并在页表中登记页号及其对应的物理块号。否则无法分配。
- 页面回收:将存储分块表中相应的物理块改为未分配(或将回收块加入到空闲存储块链中),并释放页表,修改请求表中的页表始址及状态。
5.6.3 基本地址变换机构
在分页存储管理系统中,逻辑地址到物理地址的变换要借助页表来实现,即将逻辑地址中的页号转换为内存中的物理块号。由于页表通常存放在内存中,为了实现上的方便,系统中设置了一个页表寄存器,用来存放页表在内存的起始地址和页表的长度。进程未执行时,页表的起始地址和长度存放在进程控制块中。当进程执行时,才将页表起始地址(简称页表始址)和长度存入页表寄存器中。
当进程要访问「某个逻辑地址中的指令或数据」时:
- 分页地址变换机构自动地将逻辑地址分为页号和页内位移,然后以页号为索引去检索页表;
- 在执行检索之前,先将页号与页表长度进行比较,如果页号超过了页表长度,则表示本次所访问的地址已超越进程的地址空间,系统产生地址越界中断;
- 若未出现越界,则由页表始址和页号计算出相应页表项的位置,从中得到该页的物理块号;
- 最后,将物理块号与逻辑地址中的页内位移拼接在一起,就形成了访问主存的物理地址。
图5.14给出了分页存储管理系统中的地址变换机构。
在图5.14中,假定页面大小为1KB,则逻辑地址
2500
=
2
×
1024
+
452
2500 =2\times 1024+452
2500=2×1024+452 的页号为
2
2
2 ,页内
位移为
452
452
452 。由页表可知第
2
2
2 页对应的物理块号为
8
8
8 。将块号
8
8
8 与页内位移
452
452
452 拼接(
8
×
1024
+
452
=
8644
8\times 1024+452=8644
8×1024+452=8644 ),得到物理地址为
8644
8644
8644 。
【分页地址变换例1】一分页系统中逻辑地址长度为
16
16
16 位,页面大小为1KB,且第
0
,
1
,
2
,
3
0,1,2,3
0,1,2,3 页依次存放在物理块
3
,
7
,
11
,
10
3, 7, 11, 10
3,7,11,10 中。现有一逻辑地址 0A6FH
,其二进制表示如下:
页号 页内地址
000010 10 0110 1111
由此可知逻辑地址 0A6FH
的页号为
2
2
2 ,该页存放在第
11
11
11 号物理块中,用十六进制表示块号为
B
B
B,所以物理地址为:1011 1001101111
,即 2E6FH
。
5.6.4 具有快表的地址变换机构
从上面介绍的地址变换过程可知,若页表全部放在内存中,则存取一个数据或一条指令至少需要两次访问内存:一次是访问页表,确定所存取的数据或指令的物理地址;第二次才根据所得到的物理地址存取数据或指令。显然,这种方法比通常执行指令的速度慢了一半。
为了提高地址变换的速度,可以在地址变换机构中、增设一个具有并行查找能力的高速缓冲存储器(又称联想存储器或快表),将页表放在这个高速缓冲存储器中。高速缓冲存储器一般是由半导体存储器实现的,其工作周期与CPU的周期大致相同,但其造价较高。为了降低成本,通常是在快表 translation look-aside buffer
中存放正在运行作业当前访问的那些页表项,页表的其余部分仍然存放在内存中。
引入联想存储器以后的地址变换过程为:
- 当CPU给出逻辑地址后,地址变换机构自动将页号与联想存储器中的所有页号进行并行比较,若其中有与之匹配的页号,则表示所要访问的页表项在联想存储器中,于是取出该页对应的物理块号,与页内位移拼接形成物理地址。
- 若联想存储器中的所有页号与所查找页号不匹配,则还需再访问内存中的页表,从页表中取出物理块号,与页内位移拼接形成物理地址。如果地址变换是通过查找内存中的页表完成的,则还应将这次所查到的页表项存入联想存储器中,若联想存储器已满,则需要按照某种原则淘汰出一个表项、以腾出位置。
图5.15给出了具有快表的地址变换机构。在联想存储器中,找到指定页号的次数与总搜索次数的比称做命中率。由于成本关系,联想存储器不可能设置的太大,一般由几十个单元组成。由于程序运行的局部性,联想存储器的命中率可以达到
80
%
∼
90
%
80\%\sim 90\%
80%∼90% 。
有效内存访问时间
- 设快表命中率为 p p p ,内存访问时间为 m m m ,快表访问时间为 n n n ,假定忽略快表更新时间
- 则内存有效访问时间 = p ( m + n ) + ( 1 − p ) ( 2 m + n ) p (m+n) +(1-p)(2m+n) p(m+n)+(1−p)(2m+n)
- 若 p = 0.8 p=0.8 p=0.8, m = 100 n s m=100ns m=100ns, n = 20 n s n=20ns n=20ns,则内存有效访问时间 = 0.8 ∗ 120 + 0.2 ∗ 220 = 140 n s 0.8*120+0.2*220=140ns 0.8∗120+0.2∗220=140ns
5.6.5 多级页表和倒置页表
现代计算机已普遍使用的逻辑地址空间为 2 32 ∼ 2 64 2^{32}\sim 2^{64} 232∼264 。采用页式存储管理时,页表会相当地大。以Windows 2000/XP为例,其运行的Intel x86 CPU具有 32 32 32 位地址,使用 2 32 2^{32} 232 逻辑地址空间的分页系统,规定页面4KB时,那么,4GB的虚地址空间由1M个页组成。若以每个页表项占用 4 4 4 个字节计算,则需要占用4MB连续内存空间存放页表,这显然是不现实的。为解决这一问题,许多操作系统采用多级页表的方法:用离散方式存储页表,仅将当前需要的部分页表项放在内存,其余放在磁盘上,需要时调入。
我们以在32位地址空间中使用两级页表为例,说明多级页表的实现方法。
32
32
32 位地址可以划分为如图5.16所示的地址结构。
具有两级页表的地址变换过程如图5.17所示。利用逻辑地址中的一级页号作为索引访问一级页表,找到第二级页表的起始地址;两级页表地址变换需三次访问主存,一次访问一级页表、一次访问二级页表、一次访问指令或数据,访问时间加了两倍。随着
64
64
64 位地址出现,三级、四级页表也已被引入系统。
由于计算机逻辑空间越来越大,页表占用的内存空间也越来越多,页表尺寸与虚地址空间成正比增长。为了减少内存空间的开销,不得不使用多级页表。多级页表的实现方式与两级页表类似。目前操作系统中有四级页表的实现。一般,
32
32
32 位机器大多采用两级分页,在
64
64
64 位机器中,有些采用
3
3
3 级分页,有些采用
4
4
4 级分页。
- 如
HP
的 alpha 工作站,页面大小8KB
,寻址使用位数43=10+10+10+13
- 如
ppc64
,基于 Motorola-IBM PowerPC 的 64 64 64 位工作站,页面大小4KB
,寻址使用位数41=10+10+9+12
例:为满足 2 64 2^{64} 264 地址空间的作业运行,采用多级分页存储管理方式,假设页面大小为4KB,页表中每个页表项需占8字节,则为了满足系统的分页管理至少应采用多少级页表?
答:页面大小=4KB= 2 12 2^{12} 212B,每个页表项为8字节= 2 3 2^3 23B,所以一个页面中可以存放 2 12 / 2 3 = 2 9 2^{12} /2^3=2^9 212/23=29 个页表项。设有 n n n 级分页,则 64 64 64 位逻辑地址形式为:
其中,页面大小为 2 12 2^{12} 212 字节,所以页内偏移量(块内偏移)占 12 12 12 位。由于最高级页表占一页,每页可以存放下 2 9 2^9 29 个表项,因此分页级数: 52 / 9 = 6 52/9=6 52/9=6 。所以为了满足系统的分页管理至少应采用 6 6 6 级页表(?)。
但也有许多机器和操作系统如IBM AS/400、Mac OS 等,采用了称为倒置页表 inverted page table
的方法,IPT
维护了一个页表的倒置页表,它为内存中的每一个物理块建立一个页表并按照块号排序,该表的每个表项包含正在访问该页框的进程标识、页号及特征位和哈希链指针等,用来完成内存页框到访问进程的页号,即物理地址到逻辑地址的转换。图5.18是倒置页表及地址转换,其地址转换过程如下:
- 利用进程标识号及页号检索倒置页表,若找到相应的页表项,则将其物理块号与页内地址拼接;
- 否则请求调入该进程相应页,在无调页功能的系统中则出错。
由于倒置页表中没有存放「进程中尚未调入页」,因此必须为每个进程建立一张传统页表、并存放在外存中,当所访问页不在内存时使用这张页表。页表中包含各页在外存的地址。
虽然 IPT
能减少页表占用内存,如一个128MB的内存空间,若页面大小为1KB,则 IPT
只需128KB。然而,IPT
仅包含了调入内存的页面,不包含未调入内存的页面,所以,仍需要为进程建立传统的页表,不过这种页表不再放在内存中,而存在磁盘上。当发生缺页中断时,把所需页面调入内存要多访问一次磁盘,这时的速度是较慢的。
反向页表的不足是反向页表查找慢——因为进程号及页号不能作为索引,查找时必须在整个反向页表中进行。解决办法是,将常用页表项存入快表;用散列函数存放反向页表。
5.7 分段存储管理
前面介绍的几种存储管理技术中,都有一个共同的特点,就是用户的逻辑地址空间是一个线性连续的地址空间。而通常情况下,一个作业是由多个程序段和数据段组成的,这就要求编译链接程序将它们按一维线性地址排列,没有考虑程序段的逻辑完整性,从而给程序及数据的共享和保护带来了困难。另外,动态链接及段的动态增长,也要求以逻辑上完整的程序段为单位管理
另外,程序员一般希望按逻辑关系将作业分段,每段有自己的名字,可以根据名字来访问相应的程序段或数据段,分段存储管理能较好地解决上述问题。分段存储管理也可以称为段式存储管理。
5.7.1 分段实现思想
在分段存储管理系统中,作业的地址空间由若干个逻辑分段组成,每个分段是一组逻辑意义相对完整的信息集合,每个分段都有自己的名字,每个分段都从
0
0
0 开始编址、并采用一段连续的地址空间。因此,整个作业的地址空间是二维的。图5.19给出了分段地址空间示例。分段存储管理中以段为单位分配内存,每段分配一个连续的内存区,但各段之间不要求连续。内存的分配与回收类似于动态分区分配。
分段存储管理系统的逻辑地址结构由段号
S
S
S 和段内位移
W
W
W(也称作段内地址)组成,其结构如图5.20所示。
段号 S S S 通常是从 0 0 0 开始的连续正整数。当逻辑地址结构中段号和段内位移、占用的二进制位数确定之后,一个作业地址空间中「允许的最大段数」和「各段的最大长度」也就确定了。例如,在图5.17中,段号占用的二进制位数为 16 16 16 位,段内位移占用的二进制位数也为 16 16 16 ,则一个作业最多可以有 65536 65536 65536 段,最大段长为 64 K 64\textrm{K} 64K 字节。
5.7.2 段表及地址变换
与分页存储管理类似,为了实现从逻辑地址到物理地址的变换,系统为每个进程建立一个段表,其中每个表项描述一个分段的信息,表项中包含段号、段长和该段的内存起始地址。段表一般存放在内存中。
为了便于实现地址转换,系统中设置了段表寄存器,用于存放段表起始地址(简称始址)和段表长度。
- 在进行地址变换时,系统将逻辑地址中的段号与段表长度进行比较,若段号超过了段表长度,则表示段号越界,于是产生越界中断信号;
- 若未越界,则根据段表起始地址和段号计算出该段对应段表项的位置,从中读出该段在内存的起始地址;
- 然后,再检查段内位移是否超过该段的段长。若超过,则同样发出越界中断信号;
- 若未越界,则将该段的起始地址与段内位移相加,从而得到了要访问的物理地址。
为了提高内存的访问速度,也可以使用快表。图5.21给出了分段存储管理系统的地址变换机构。
在图5.21中,逻辑地址中的段号为
2
2
2 ,段内位移为
100
100
100 。由段表可知第2段在内存的起始地址为
8
KB
8\textrm{KB}
8KB ,将起始地址与段内位移
100
100
100 相加(
8
×
1024
+
100
=
8292
8×1024+100=8292
8×1024+100=8292 )得到物理地址为
8292
8292
8292 。
5.7.3 分段与分页的主要区别
分页存储管理与分段存储管理有许多相似之处。例如,二者都采用离散分配方式,且都
要通过地址变换机构来实现地址变换。但二者在概念上也有很多区别,主要表现如下:
- 页是信息的物理单位,分页是为了实现离散分配方式,以减少内存的碎片,提高内存的利用率。或者说,分页仅仅是出于系统管理的需要,而不是用户的需要。段是信息的逻辑单位,它含有一组意义相对完整的信息。分段的目的是为了更好地满足用户的需要。
- 页的大小固定且由系统决定,把逻辑地址划分为页号和页内位移两部分,是由机器硬件实现的。段的长度不固定,且由用户所编写的程序决定,通常由编译系统在对源程序进行编译时、根据信息的性质来划分的。
- 分页系统中作业的地址空间是一维的,即单一的线性地址空间,程序员只需要利用一个值来表示一个地址。分段系统中作业的地址空间是二维的,程序员在标识一个地址时,既要给出段名,又要给出段内位移。
5.8 段页式存储管理
从前面的介绍中可以看出,分页系统能有效地提高内存利用率、并能解决碎片问题,而分段系统能反映程序的逻辑结构、并有利于段的共享。如果将这两种存储管理方式结合起来,就形成了段页式存储管理方式。其基本思想是利用分段方法管理用户的地址空间,利用分页方法管理存储空间。
在段页式存储管理系统中,作业的地址空间首先被分成若干个逻辑分段,每段都有自己的段号,再将每一段分成若干个大小固定的页。对于内存空间的管理仍然和分页存储管理一样,将其分成若干个和页面大小相同的物理块,对内存的分配以物理块为单位。
在段页式存储管理系统中,作业的逻辑地址结构包含三部分:段号
S
S
S 、段内页号
P
P
P 及页内位移
D
D
D ,其结构如图5.22所示。
为了实现地址变换,段页式存储管理系统中需要同时设立段表和页表。系统为每个进程建立一张段表,而每个分段有一张页表。段表表项中至少应包括段号、页表始址和页表长度,其中页表始址指出该段的页表在内存中的起始地址,页表表项中至少应包括页号和块号。此外,为了便于实现地址变换,系统中还需要配置一个段表寄存器,其中存放段表的起始地址和段表长度。
- 在进行地址变换时,首先利用段号 S S S 与段表寄存器中的段表长度进行比较,若小于段表长度则表示未越界。于是利用段表始址和段号求出该段对应段表项的位置,从中得到该段的页表始址。
- 再利用逻辑地址中的段内页号
P
P
P 获得对应页表项的位置,从中读出该页所在的物理块号,然后与页内位移拼接形成物理地址。
在段页式系统中,要想存取访问信息,需要三次访问内存:第一次访问段表,第二次访问页表
第三次访问信息。为了提高访问主存的速度,应考虑使用联想寄存器。
考研题
设有一页式存储管理系统,向用户提供的逻辑地址空间最大为
16
16
16 页,每页
2048
2048
2048 字节,内存总共有
8
8
8 个存储块,试问逻辑地址至少为多少位?内存空间有多大?
答:页号+页内位移。页内位移为
11
11
11 位,逻辑地址空间最大
16
16
16 页,页号占
4
4
4 位,逻辑地址至少为
15
15
15 位。内存
8
8
8 个存储块,在页式中,存储块大小和页面大小相等,内存空间
16
KB
16\textrm{KB}
16KB 。