内存管理概念

内存地址从 0 开始,每个地址对应一个存储单元:

  • 如果计算机“按字节编址”则每个存储单元大小为 1 字节(1B) ,即 8 个二进制位
  • 如果字长为 16 位的计算机按字编址,则每个有储单元大小为 1 个字,每字的大小为 16 个二进制位

内存管理的基本原理和要求

内存管理(Memory Management)是操作系统设计中最重要和最复杂的内容之一。虽然计算机硬件技术一直在飞速发展,内存容量也在不断增大,但仍然不可能将所有用户进程和系统所需要的全部程序与数据放入主存,因此操作系统必须对内存空间进行合理的划分和有效的动态分配。操作系统对内存的划分和动态分配,就是内存管理的概念。

有效的内存管理在多道程序设计中非常重要,它不仅可以方便用户使用存储器、提高内存利用率,还可以通过虚拟技术从逻辑上扩充存储器。

内存管理的功能有:

  • 内存空间的分配与回收。由操作系统完成主存储器空间的分配和管理,使程序员摆脱存储分配的麻烦,提高编程效率。
  • 地址转换。在多道程序环境下,程序中的逻辑地址与内存中的物理地址不可能一致,因此存储管理必须提供地址变换功能,把逻辑地址转换成相应的物理地址。
  • 内存空间的扩充。利用虚拟存储技术或自动覆盖技术,从逻辑上扩充内存。
  • 存储保护。保证各道作业在各自的存储空间内运行,互不干扰。

在进行具体的内存管理之前,需要了解进程运行的基本原理和要求。

逻辑地址空间与物理地址空间

编译后,每个目标模块都从 0 号单元开始编址,这称为该目标模块的相对地址(或逻辑地址)。当链接程序将各个模块链接成一个完整的可执行目标程序时,链接程序顺序依次按各个模块的相对地址构成统一的从 0 号单元开始编址的逻辑地址空间。用户程序和程序员只需知道逻辑地址,而内存管理的具体机制则是完全透明的,只有系统编程人员才会涉及内存管理的具体机制。不同进程可以有相同的逻辑地址,因为这些相同的逻辑地址可以映射到主存的不同位置。

物理地址空间是指内存中物理单元的集合,它是地址转换的最终地址,进程在运行时执行指令和访问数据,最后都要通过物理地址从主存中存取。当装入程序将可执行代码装入内存时,必须通过地址转换将逻辑地址转换成物理地址,这个过程称为地址重定位。

程序装入和链接

创建进程首先要将程序和数据装入内存。将用户源程序变为可在内存中执行的程序,通常需要以下几个步骤:

  • 编译。由编译程序将用户源代码编译成若干目标模块。
  • 链接。由链接程序将编译后形成的一组目标模块及所需的库函数链接在一起,形成一个完整的装入模块。
  • 装入。由装入程序将装入模块装入内存运行。

程序的链接有以下三种方式。

  • 静态链接。在程序运行之前,先将各目标模块及它们所需的库函数链接成一个完整的可执行程序,以后不再拆开。
  • 装入时动态链接。将用户源程序编译后所得到的一组目标模块,在装入内存时,采用边装入边链接的方式。
  • 运行时动态链接。对某些目标模块的链接,是在程序执行中需要该目标模块时才进行的。其优点是便于修改和更新,便于实现对目标模块的共享。

内存的装入模块在装入内存时,同样有以下三种方式:

  1. 绝对装入。在编译时,若知道程序将驻留在内存的某个位置,则编译程序将产生绝对地址的目标代码。绝对装入程序按照装入模块中的地址,将程序和数据装入内存。由于程序中的逻辑地址与实际内存地址完全相同,因此不需对程序和数据的地址进行修改。绝对装入方式只适用于单道程序环境。另外,程序中所用的绝对地址,可在编译或汇编时给出,也可由程序员直接赋予。而通常情况下在程序中采用的是符号地址,编译或汇编时再转换为绝对地址。
  2. 可重定位装入(静态重定位)。在多道程序环境下,多个目标模块的起始地址(简称始址)通常都从 0 开始,程序中的其他地址都是相对于始址的,此时应采用可重定位装入方式。根据内存的当前情况,将装入模块装入内存的适当位置。装入时对目标程序中指令和数据的修改过程称为重定位,地址变换通常是在装入时一次完成的,所以又称静态重定位。静态重定位的特点是:
    • 一个作业装入内存时,必须给它分配要求的全部内存空间,若没有足够的内存,则不能装入该作业。
    • 此外,作业一旦进入内存,整个运行期间就不能在内存中移动,也不能再申请内存空间
  3. 动态运行时装入(动态重定位)。程序在内存中若发生移动,则需要采用动态的装入方式。装入程序把装入模块装入内存后,并不立即把装入模块中的相对地址转换为绝对地址,而是把这种地址转换推迟到程序真正要执行时才进行。因此,装入内存后的所有地址均为相对地址。这种方式需要一个重定位寄存器的支持。动态重定位的特点如下:
    • 可以将程序分配到不连续的存储区中;
    • 在程序运行之前可以只装入它的部分代码即可投入运行,然后在程序运行期间,根据需要动态申请分配内存;
    • 便于程序段的共享,可以向用户提供一个比存储空间大得多的地址空间。

内存保护

内存分配前,需要保护操作系统不受用户进程的影响,同时保护用户进程不受其他用户进程的影响。内存保护可采取两种方法:

  1. 在 CPU 中设置一对上、下限寄存器,存放用户作业在主存中的下限和上限地址,每当 CPU 要访问一个地址时,分别和两个寄存器的值相比,判断有无越界。
  2. 采用重定位寄存器(基址寄存器)和界地址寄存器(限长寄存器)来实现这种保护。重定位寄存器含最小的物理地址值,界地址寄存器含逻辑地址的最大值。每个逻辑地址值必须小于界地址寄存器;内存管理机构动态地将逻辑地址与界地址寄存器进行比较,若未发生地址越界,则加上重定位寄存器的值后映射成物理地址,再送交内存单元。

当 CPU 调度程序选择进程执行时,派遣程序会初始化重定位寄存器和界地址寄存器。每个逻辑地址都需要与这两个寄存器进行核对,以保证操作系统和其他用户程序及数据不被该进程的运行影响。

实现内存保护需要重定位寄存器和界地址寄存器,因此要注意两者的区别。重定位寄存器是用来“加”的,逻辑地址加上重定位寄存器中的值就能得到物理地址;界地址寄存器是用来“比”的,通过比较界地址寄存器中的值与逻辑地址的值来判断是否越界。

覆盖与交换

覆盖与交换技术是在多道程序环境下用来扩充内存的两种方法。

覆盖

早期的计算机系统中,主存容量很小,虽然主存中仅存放一道用户程序,但存储空间放不下用户进程的现象也经常发生,这一矛盾可以用覆盖技术来解决。

覆盖的基本思想如下:由于程序运行时并非任何时候都要访问程序及数据的各个部分(尤其是大程序),因此可把用户空间分成一个固定区和若干覆盖区。将经常活跃的部分放在固定区,其余部分按调用关系分段。首先将那些即将要访问的段放入覆盖区,其他段放在外存中,在需要调用前,系统再将其调入覆盖区,替换覆盖区中原有的段。

覆盖技术的特点是,打破了必须将一个进程的全部信息装入主存后才能运行的限制,但当同时运行程序的代码量大于主存时仍不能运行,此外,内存中能够更新的地方只有覆盖区的段,不在覆盖区中的段会常驻内存。

必须由程序员声明覆盖结构,操作系统完成自动覆盖。缺点:对用户不透明,增加了用户编程负担。

交换

交换(对换)的基本思想是,把处于等待状态(或在 CPU 调度原则下被剥夺运行权利)的程序从内存移到辅存,把内存空间腾出来,这一过程又称换出;把准备好竞争 CPU 运行的程序从辅存移到内存,这一过程又称换入。中级调度

应该在外存(磁盘)的什么位置保存被换出的进程?

具有对换功能的操作系统中,通常把磁盘空间分为文件区对换区两部分。文件区主要用于存放文件,主要追求存储空间的利用率,因此对文件区空间的管理采用离散分配方式;对换区空间只占磁盘空间的小部分,被换出的进程数据就存放在对换区。由于对换的速度直接影响到系统的整体速度,因此对换区空间的管理主要追求换入换出速度,因此通常对换区采用连续分配方式。总之,对换区的 I/O 速度比文件区的更快。

什么时候应该交换?

交换通常在许多进程运行且内存吃紧时进行,而系统负荷降低就暂停。例如:在发现许多进程运行时经常发生缺页,就说明内存紧张,此时可以换出一些进程;如果缺页率明显下降,就可以暂停换出。

应该换出哪些进程? 可优先换出阻塞进程;可换出优先级低的进程;为了防止优先级低的进程在被调入内存后很快又被换出,有的系统还会考虑进程在内存的驻留时间。(注意:PCB 会常驻内存,不会被换出外存

例如,有一个 CPU 采用时间片轮转调度算法的多道程序环境。时间片到,内存管理器将刚刚执行过的进程换出,将另一进程换入刚刚释放的内存空间。同时,CPU 调度器可以将时间片分配给其他已在内存中的进程。每个进程用完时间片都与另一进程交换。在理想情况下,内存管理器的交换过程速度足够快,总有进程在内存中可以执行。

有关交换,需要注意以下几个问题:

  • 交换需要备份存储,通常是快速磁盘。它必须足够大,并提供对这些内存映像的直接访问。
  • 为了有效使用 CPU,需要使每个进程的执行时间比交换时间长,而影响交换时间的主要是转移时间。转移时间与所交换的内存空间成正比。
  • 若换出进程,则必须确保该进程完全处于空闲状态。
  • 交换空间通常作为磁盘的一整块,且独立于文件系统,因此使用起来可能很快。
  • 交换通常在有许多进程运行且内存空间吃紧时开始启动,而在系统负荷降低时就暂停。

普通的交换使用不多,但交换策略的某些变体在许多系统(如 UNIX 系统)中仍发挥作用。交换技术主要在不同进程(或作业)之间进行,而覆盖则用于同一个程序或进程中。由于覆盖技术要求给出程序段之间的覆盖结构,使得其对用户和程序员不透明,所以对于主存无法存放用户程序的矛盾,现代操作系统是通过虚拟内存技术来解决的,覆盖技术则已成为历史;而交换技术在现代操作系统中仍具有较强的生命力。

连续分配管理方式

连续分配方式是指为一个用户程序分配一个连续的内存空间。譬如某用户需要 1GB 的内存空间,连续分配方式就在内存空间中为用户分配一块连续的 1GB 空间。连续分配方式主要包括单一连续分配、固定分区分配和动态分区分配。

单一连续分配

内存在此方式下分为系统区用户区

  • 系统区仅供操作系统使用,通常在低地址部分;
  • 用户区是为用户提供的、除系统区之外的内存空间。

这种方式无须进行内存保护。因为内存中永远只有一道程序,因此肯定不会因为访问越界而干扰其他程序。(早期的 PC 操作系统 MS-DOS )

  • 优点是简单、无外部碎片,可以采用覆盖技术,不需要额外的技术支持。

  • 缺点是只能用于单用户、单任务的操作系统中,有内部碎片,存储器的利用率极低。

固定分区分配

固定分区分配是最简单的一种多道程序存储管理方式,它将用户内存空间划分为若干固定大小的区域,每个分区只装入一道作业。当有空闲分区时,便可再从外存的后备作业队列中选择适当大小的作业装入该分区,如此循环。固定分区分配在划分分区时有两种不同的方法:

  • 分区大小相等。用于利用一台计算机去控制多个相同对象的场合,缺乏灵活性。
  • 分区大小不等。划分为多个较小的分区、适量的中等分区和少量大分区。

为便于内存分配,通常将分区按大小排队,并为之建立一张分区说明表,其中各表项包括每个分区的始址、大小及状态(是否已分配)。当有用户程序要装入时,便检索该表,以找到合适的分区给予分配并将其状态置为“已分配”;未找到合适分区时,则拒绝为该用户程序分配内存。

这种分区方式存在两个问题:

  • 一是程序可能太大而放不进任何一个分区中,这时用户不得不使用覆盖技术来使用内存空间
  • 二是主存利用率低,当程序小于固定分区大小时,也占用一个完整的内存分区空间,这样分区内部就存在空间浪费,这种现象称为内部碎片。

固定分区是可用于多道程序设计的最简单的存储分配,无外部碎片,但不能实现多进程共享一个主存区,所以存储空间利用率低。固定分区分配很少用于现在通用的操作系统中,但在某些用于控制多个相同对象的控制系统中仍发挥着一定的作用。

动态分区分配

动态分区分配又称可变分区分配,是一种动态划分内存的分区方法。这种分区方法不预先划分内存,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。因此,系统中分区的大小和数目是可变的。

系统要用什么样的数据结构记录内存的使用情况?

有两种常用的数据结构:空闲分区表和空闲分区链。

  • 空闲分区表:每个空闲分区对应一个表项。表项中包含分区号、分区大小、分区起始地址等信息。
  • 空闲分区链:每个分区的起始部分和末尾部分分别设置前向指针和后向指针。起始部分处还可记录分区大小等信息。
当很多个空闲分区都能满足需求时,应该选择哪个分区进行分配?应该用最大的分区进行分配?还是用最小的分区进行分配?又或是用地址最低的部分进行分配?

把一个新作业装入内存时,须按照一定的动态分区分配算法,从空闲分区表(或空闲分区链)中选出一个分区分配给该作业。由于分配算法算法对系统性能有很大的影响,因此人们对它进行了广泛的研究。

如何进行分区的分配与回收操作?假设系统采用的数据结构是“空闲分区表”如何回收?

分配操作:

  1. 如果待分配进程大小小于空闲分区大小,修改空闲分区表的分区大小和起始地址
  2. 如果待分配进程大小等于空闲分区大小,从空闲分区表中删除该表项

回收操作:

  1. 情况一,回收区的后面有一个相邻的空闲分区。两个相邻的空闲分区合并为一个
  2. 情况二,回收区的前面有一个相邻的空闲分区。两个相邻的空闲分区合并为一个
  3. 情况三,回收区的前、后各有一个相邻的空闲分区。三个相邻的空闲分区合并为一个
  4. 情况四,回收区的前、后都没有相邻的空闲分区。新增一个表项。注:各表项的顺序不一定按照地址递增顺序排列,具体的排列方式需要依据动态分区分配算法来确定。

动态分区分配没有内部碎片,但是有外部碎片。

  • 内部碎片,分配给某进程的内存区域中,如果有些部分没有用上。
  • 外部碎片,是指内存中的某些空闲分区由于太小而难以利用。

动态分区在开始分配时是很好的,但之后会导致内存中出现许多小的内存块。随着时间的推移,内存中会产生越来越多的碎片,内存的利用率随之下降。这些小的内存块称为外部碎片,指在所有分区外的存储空间会变成越来越多的碎片,这与固定分区中的内部碎片正好相对。克服外部碎片可以通过紧凑(Compaction)技术来解决,即操作系统不时地对进程进行移动和整理。但这需要动态重定位寄存器的支持,且相对费时。紧凑的过程实际,上类似于 Windows 系统中的磁盘整理程序,只不过后者是对外存空间的紧凑。

动态分区分配算法

在进程装入或换入主存时,若内存中有多个足够大的空闲块,则操作系统必须确定分配哪个内存块给进程使用,这就是动态分区的分配策略。考虑以下几种算法:

  • 首次适应(First Fit)算法。空闲分区以地址递增的次序链接。分配内存时顺序查找,找到大小能满足要求的第一个空闲分区。实现:空闲分区以地址递增的次序排列。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
  • 最佳适应(Best Fit)算法。空闲分区按容量递增的方式形成分区链,找到第一个能满足要求的空闲分区。算法思想:由于动态分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片区域。因此为了保证当“大进程”到来时能有连续的大片空间,可以尽可能多地留下大片的空闲区,即,优先使用更小的空闲区。实现:空闲分区按容量递增次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。缺点:每次都选最小的分区进行分配,会留下越来越多的、很小的、难以利用的内存块。因此这种方法会产生很多的外部碎片。
  • 最坏适应(Worst Fit)算法。又称最大适应(Largest Fit)算法,空闲分区以容量递减的次序链接,找到第一个能满足要求的空闲分区,即挑选出最大的分区。算法思想:为了解决最佳适应算法的问题一即留下太多难以利用的小碎片,可以在每次分配时优先使用最太的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用。实现:空闲分区按容量递减次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。缺点:每次都选最大的分区进行分配,虽然可以让分配后留下的空闲区更大,更可用,但是这种方式会导致较大的连续空闲区被迅速用完。如果之后有“大进程”到达,就没有内存分区可用了。
  • 邻近适应(Next Fit)算法。又称循环首次适应算法,由首次适应算法演变而成。不同之处是,分配内存时从上次查找结束的位置开始继续查找。算法思想:首次适应算法每次都从链头开始查找的。这可能会导致低地址部分出现很多小的空闲分区,而每次分配查找时,都要经过这些分区,因此也增加了查找的开销。如果每次都从上次查找结束的位置开始检索,就能解决上述问题。实现:空闲分区以地址递增的顺序排列(可排成一个循环链表)。每次分配内存时从上次查找结束的位置开始查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。首次适应算法每次都要从头查找,每次都需要检索低地址的小分区。但是这种规则也决定了当低地址部分有更小的分区可以满足需求时,会更有可能用到低地址部分的小分区,也会更有可能把高地址部分的大分区保留下来(最佳适应算法的优点)邻近适应算法的规则可能会导致无论低地址、高地址部分的空闲分区都有相同的概率被使用,也就导致了高地址部分的大分区更可能被使用,划分为小分区,最后导致无大分区可用(最大适应算法的缺点)

在这几种方法中,首次适应算法不仅是最简单的,而且通常也是最好和最快的。在 UNIX 系统的最初版本中,就是使用首次适应算法为进程分配内存空间的,它使用数组的数据结构(而非链表)来实现。不过,首次适应算法会使得内存的低地址部分出现很多小的空闲分区,而每次分配查找时,都要经过这些分区,因此增加了查找的开销。

邻近适应算法试图解决这个问题。但实际上,它常常导致在内存的末尾分配空间(因为在一遍扫描中,内存前面部分使用后再释放时,不会参与分配)分裂成小碎片。它通常比首次适应算法的结果要差。

最佳适应算法虽然称为“最佳”,但是性能通常很差,因为每次最佳的分配会留下很小的难以利用的内存块,会产生最多的外部碎片。

最坏适应算法与最佳适应算法相反,它选择最大的可用块,这看起来最不容易产生碎片,但是却把最大的连续内存划分开,会很快导致没有可用的大内存块,因此性能也非常差。

Knuth 和 Shore 分别就前三种方法对内存空间的利用情况做了模拟实验,结果表明:首次适应算法可能比最佳适应法效果好,而它们两者一定比最大适应法效果好。另外要注意,在算法实现时,分配操作中最佳适应法和最大适应法需要对可用块进行排序或遍历查找,而首次适应法和邻近适应法只需要简单查找;在回收操作中,当回收的块与原来的空闲块相邻时(有三种相邻的情况,比较复杂),需要将这些块合并。在算法实现时,使用数组或链表进行管理。除了内存的利用率,这里的算法开销也是操作系统设计需要考虑的一个因素。

image

非连续分配管理方式

非连续分配允许一个程序分散地装入不相邻的内存分区。在连续分配管理方式中,我们发现,即使内存有超过 1GB 的空闲空间,但若没有连续的 1GB 空间,则需要 1GB 空间的作业仍然是无法运行的;但若采用非连续分配管理方式,则作业所要求的 1GB 内存空间可以分散地分配在内存的各个区域,当然,这也需要额外的空间去存储它们(分散区域)的索引,使得非连续分配方式的存储密度低于连续存储方式的。

非连续分配管理方式根据分区的大小是否固定,分为分页存储管理方式和分段存储管理方式。

在分页存储管理方式中,又根据运行作业时是否要把作业的所有页面都装入内存才能运行,分为基本分页存储管理方式和请求分页存储管理方式。下面介绍基本分页存储管理方式。

基本分页存储管理方式

基本分页存储管理的思想:把内存分为一个个相等的小分区,再按照分区大小把进程拆分成一个个小部分。

固定分区会产生内部碎片,动态分区会产生外部碎片,这两种技术对内存的利用率都比较低。我们希望内存的使用能尽量避免碎片的产生,这就引入了分页的思想:把主存空间划分为大小相等且固定的块,块相对较小,作为主存的基本单位。每个进程也以块为单位进行划分,进程在执行时,以块为单位逐个申请主存中的块空间。

分页的方法从形式上看,像分区相等的固定分区技术,分页管理不会产生外部碎片。但它又有本质的不同点:块的大小相对分区要小很多,而且进程也按照块进行划分,进程运行时按块申请主存可用空间并执行。这样,进程只会在为最后一个不完整的块申请一个主存块空间时,才产生主存碎片,所以尽管会产生内部碎片,但这种碎片相对于进程来说也是很小的,每个进程平均只产生半个块大小的内部碎片(也称页内碎片)。

将内存空间分为一个个大小相等的分区(比如:每个分区 4KB),每个分区就是一个“页框”(或称“页帧”、“内存块”、“物理块”、“物理页面”)。每个页框有一个编号,即“页框号”(或者“内存块号”、“页帧号”、“物理块号”、“物理页号”)页框号从 0 开始。

将用户进程的地址空间也分为与页框大小相等的一个个区域,称为“”或“页面”。每个页面也有一个编号,即“页号”页号也是从 0 开始。(注:进程的最后一个页面可能没有一个页框那么大。因此,页框不能太大,否则可能产生过大的内部碎片)

image

操作系统以页框为单位为各个进程分配内存空间。进程的每个页面分别放入一个页框中。也就是说,进程的页面与内存的页框有一一对应的关系。各个页面不必连续存放,也不必按先后顺序来,可以放到不相邻的各个页框中。

(1)分页存储的几个基本概念

  • 页面和页面大小。进程中的块称为页(Page),内存中的块称为页框(Page Frame,或页帧)。外存也以同样的单位进行划分,直接称为块(Block)。进程在执行时需要申请主存空间,即要为每个页面分配主存中的可用页框,这就产生了页和页框的一一对应。为方便地址转换,页面大小应是 2 的整数幂。同时页面大小应该适中,页面太小会使进程的页面数过多,这样页表就会过长,占用大量内存,而且也会增加硬件地址转换的开销,降低页面换入/换出的效率;页面过大又会使页内碎片增多,降低内存的利用率。所以页面的大小应该适中,要在空间效率和时间效率之间权衡。
  • 地址结构。地址结构包含两部分:前一部分为页号 P,后一部分为页内偏移量 W。地址长度为 32 位,其中 0 ~ 11 位为页内地址,即每页大小为 4KB;12 ~ 31 位为页号,地址空间最多允许 20 页。注意,地址结构决定了虚拟内存的寻址空间有多大。在实际问题中,页号、页内偏移、逻辑地址大多都是用十进制数给出的。
  • 页表。为了便于在内存中找到进程的每个页面所对应的物理块,系统为每个进程建立一张页表,它记录页面在内存中对应的物理块号,页表一般存放在内存中。页表是由页表项(进程的每个页面对应一个页表项)组成的,初学者容易混淆页表项与地址结构,页表项与地址都由两部分构成,而且第一部分都是页号,但页表项的第二部分是物理内存中的块号,而地址的第二部分是页内偏移;页表项的第二部分与地址的第二部分共同组成物理地址。在配置页表后,进程执行时,通过查找该表,即可找到每页在内存中的物理块号。可见,页表的作用是实现从页号到物理块号的地址映射

Eg. 假设某系统物理内存大小为 4GB,页面大小为 4KB,则每个页表项至少应该为多少字节?

内存块大小 = 页面大小 = 4KB = 212B,故 4GB 的内存被分成 220B

内存块号的范围应该是 0 ~ 220-1,故内存块号至少要用 20 bit 来表示,即 3B 来表示块号

页表项连续存放,因此页号可以是隐含的,不占存储空间(类比数组),因此页表项占 3B。

将进程地址空间分页之后,操作系统该如何实现逻辑地址到物理地址的转换?

虽然进程的各个页面是离散存放的,但是页面内部是连续存放的

  1. 要算出逻辑地址 A 对应的页号
  2. 要知道该页号对应页面在内存中的起始地址 P(需要查页表)
  3. 要算出逻辑地址在页面内的“偏移量”W
  4. 物理地址 = 页面始址 + 页内偏移量

Eg. 在某计算机系统中,页面大小是 50B。某进程逻辑地址空间大小为 200B,则逻辑地址 110 对应的页号、页内偏移量是多少?

页号 = 逻辑地址 / 页面长度(取除法的整数部分) = 110 / 50 = 2

页内偏移量 = 逻辑地址 % 页面长度(取除法的余数部分)= 110 % 50 = 10

逻辑地址可以拆分为(页号,页内偏移量)通过页号查询页表,可知页面在内存中的起始地址

页面在内存中的起始地址 + 页内偏移量 = 实际的物理地址

在计算机内部,地址是用二进制表示的,如果页面大小刚好是 2 的整数幂,则计算机硬件可以很快速的把逻辑地址拆分
成(页号,页内偏移量)

image

总结:页面大小 刚好是 2 的整数幂有什么好处?

  1. 逻辑地址的拆分更加迅速——如果每个页面大小为 2KB,用二进制数表示逻辑地址,则末尾 K 位 即为页内偏移量,其余部分就是页号。因此,如果让每个页面的大小为 2 的整数幂,计算机硬件就 可以很方便地得出一个逻辑地址对应的页号和页内偏移量,而无需进行除法运算,从而提升了运行速度。
  2. 物理地址的计算更加迅速——根据逻辑地址得到页号,根据页号查询页表从而找到页面存放的内 存块号,将二进制表示的内存块号和页内偏移量拼接起来,就可以得到最终的物理地址。

image

(2)基本地址变换机构

地址变换机构的任务是将逻辑地址转换为内存中的物理地址。地址变换是借助于页表实现的。

通常会在系统中设置一个页表寄存器(PTR),存放页表在内存中的起始地址 F 和页表长度 M。 进程未执行时,页表的始址和页表长度 放在进程控制块(PCB)中,当进程被调度时,操作系统内核会把它们放到页表寄存器中。

image

注意:页面大小是 2 的整数幂。设页面大小为 L,逻辑地址 A 到物理地址 E 的变换过程如下:

  1. 计算页号 P 和页内偏移量 W(如果用十进制数手算,则 P = A / L,W = A % L;但是在计算机实际运行时,逻辑地址结构是固定不变的,因此计算机硬件可以更快地得到二进制表示的页号、页内偏移量)
  2. 比较页号 P 和页表长度 M,若 P ≥ M,则产生越界中断,否则继续执行。(注意:页号是从 0 开始的,而页表长度至少是 1,因此 P = M 时也会越界)
  3. 页表中页号 P 对应的页表项地址 = 页表起始地址 F + 页号 P * 页表项长度,取出该页表项内容 b,即为内存块号。(注意区分页表项长度、 页表长度、页面大小的区别。页表长度指的是这个页表中总共有几个页表项,即总共有几个页;页表项长度指的是每个页表项占多大的存储空间;页面大小指的是一个页面占多大的存储空间)
  4. 计算 E = b * L + W,用得到的物理地址 E 去访存。(如果内存块号、页面偏移量是用二进制表示的,那么把二者拼接起来就是最终的物理地址了)

Eg. 若页面大小 L 为 1K 字节,页号 2 对应的内存块号 b = 8,将逻辑地址 A = 2500 转换为物理地址 E。

等价描述:某系统按字节寻址,逻辑地址结构中,页内偏移量占 10 位,页号 2 对应的内存块号 b = 8, 将逻辑地址 A = 2500 转换为物理地址 E。

  1. 计算页号、页内偏移量。页号 P = A / L = 2500 / 1024 = 2;页内偏移量 W = A % L = 2500 % 1024 = 452
  2. 根据题中条件可知,页号 2 没有越界,其存放的内存块号 b = 8
  3. 物理地址 E = b * L + W = 8 * 1024 + 425 = 8644

在分页存储管理(页式管理)的系统中,只要确定了每个页面的大小,逻辑地址结构就确定了。因此,页式管理中地址是一维的。即,只要给出一个逻辑地址,系统就可以自动地算出页号、页内偏移量两个部分,并不需要显式地告诉系统这个逻辑地址中,页内偏移量占多少位。

实际应用中,通常使一个页框恰好能放入整数个页表项。为了方便找到页表项,页表一般是放在连续的内存块中的

(3)具有快表的地址变换机构

由上面介绍的地址变换过程可知,若页表全部放在内存中,则存取一个数据或一条指令至少要访问两次内存:

  • 第一次是访问页表,确定所存取的数据或指令的物理地址;
  • 第二次是根据该地址存取数据或指令。显然,这种方法比通常执行指令的速度慢了一半。

为此,在地址变换机构中增设一个具有并行查找能力的高速缓冲存储器——快表,又称相联存储器(TLB),用来存放当前访问的若干页表项,以加速地址变换的过程。与此对应,主存中的页表常称为慢表。

image

  1. CPU 给出逻辑地址,由某个硬件算得页号、页内偏移量,将页号与快表中的所有页号进行比较。
  2. 如果找到匹配的页号,说明要访问的页表项在快表中有副本,则直接从中取出该页对应的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此, 若快表命中,则访问某个逻辑地址仅需一次访存即可。
  3. 如果没有找到匹配的页号,则需要访问内存中的页表,找到对应页表项,得到页面存放的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此, 若快表未命中,则访问某个逻辑地址需要两次访存(注意:在找到页表项后,应同时将其存入快表, 以便后面可能的再次访问。但若快表已满,则必须按照一定的算法对旧的页表项进行替换)

由于查询快表的速度比查询页表的速度快很多,因此只要快表命中,就可以节省很多时间。 因为局部性原理,一般来说快表的命中率可以达到 90% 以上。

(4)两级页表

单级页表的问题?

  • 页表必须连续存放,因此当页表很大时,需要占用很多个连续的页框。
  • 没有必要让整个页表常驻内存,因为进程在一段时间内可能只需要访问某几个特定的页面。

若分为两级页表后,页表依然很长,则可以采用更多级页表,一般来说各级页表的大小不能超过一个页面

两级页表的访存次数分析(假设没有快表机构) 第一次访存:访问内存中的页目录表,第二次访存:访问内存中的二级页表,第三次访存:访问目标内存单元。N 级页表访问一个逻辑地址需要 N + 1 次访存

基本分段储存管理方式

  1. 分段。段式管理方式按照用户进程中的自然段划分逻辑空间。例如,用户进程由主程序、两个子程序、栈和一段数据组成,于是可以把这个用户进程划分为 5 段,每段从 0 开始编址,并分配一段连续的地址空间(段内要求连续,段间不要求连续,因此整个作业的地址空间是二维的),其逻辑地址由段号 S 与段内偏移量 W 两部分组成。由于是按逻辑功能模块划分,用户编程更方便,程序的可读性更高。段号的位数决定了每个进程最多可以分几个段;段内地址位数决定了每个段的最大长度是多少
  2. 段表。每个进程都有–张逻辑空间与内存空间映射的段表,其中每个段表项对应进程的一段,段表项记录该段在内存中的始址和长度。配置段表后,执行中的进程可通过查找段表,找到每段所对应的内存区。可见,段表用于实现从逻辑段到物理内存区的映射。每个段对应一个段表项,其中记录了该段在内存中的起始位置(又称“基址”)和段的长度。各个段表项的长度是相同的。于段表项长度相同,因此段号可以是隐含的,不占存储空间。

image

分段、分页管理的对比

页是信息的物理单位。分页的主要目的是为了实现离散分配,提高内存利用率。分页仅仅是系统管理上的需要,完全是系统行为,对用户是不可见的。

段是信息的逻辑单位。分段的主要目的是更好地满足用户需求。一个段通常包含着一组属于一个逻辑模块的信息。分段对用户是可见的,用户编程时需要显式地给出段名。

页的大小固定且由系统决定。段的长度却不固定,决定于用户编写的程序。

分页的用户进程地址空间是一维的,程序员只需给出一个记忆符即可表示一个地址。

分段的用户进程地址空间是二维的,程序员在标识一个地址时,既要给出段名,也要给出段内地址。

分段比分页更容易实现信息的共享和保护。

不能被修改的代码称为纯代码或可重入代码(不属于临界资源),这样的代码是可以共享的。可修改的代码是不能共享的(比如,有一个代码段中有很多变量,各进程并发地同时访问可能造成数据不一致)

访问一个逻辑地址需要几次访存?

分页(单级页表):第一次访存——查内存中的页表,第二次访存——访问目标内存单元。总共两次访存 分段:第一次访存——查内存中的段表,第二次访存——访问目标内存单元。总共两次访存

与分页系统类似,分段系统中也可以引入快表机构,将近期访问过的段表项放到快表中,这样可以少一次访问,加快地址变换速度。

段页式管理方式

image

段号的位数决定了每个进程最多可以分几个段

页号位数决定了每个段最大有多少页

页内偏移量决定了页面大小、内存块大小是多少

“分段”对用户是可见的,程序员编程时需要显式地给出段号、段内地址。而将各段“分页”对用户是不可见的。系统会根据段内地址自动划分页号和页内偏移量。 因此段页式管理的地址结构是二维的。

虚拟内存管理

传统存储管理方式的特征、缺点

一次性:作业必须一次性全部装入内存后才能开始运行。这会造成两个问题:

  • 作业很大时,不能全部装入内存,导致大作业无法运行;
  • 当大量作业要求运行时,由于内存无法容纳所有作业,因此只有少量作业能运行,导致多道程序并发度下降。

驻留性:一旦作业被装入内存,就会一直驻留在内存中,直至作业运行结束。事实上,在一个时间段内,只需要访问作业的一小部分数据即可正常运行,这就导致了内存中会驻留大量的、暂时用不到的数据,浪费了宝贵的内存资源


局部性原理

时间局部性:如果执行了程序中的某条指令,那么不久后这条指令很有可能再次执行;如果某个数据被访问过,不久之后该数据很可能再次被访问。(因为程序中存在大量的循环)

空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问。(因为很多数据在内存中都是连续存放的,并且程序的指令也是顺序地在内存中存放的)

虚拟内存的基本概念

虚拟内存的定义和特征

基于局部性原理,在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行。

在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。

若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。

在操作系统的管理下,在用户看来似乎有一个比实际内存大得多的内存,这就是虚拟内存

虚拟内存有一下三个主要特征:

  • 多次性:无需在作业运行时-次性全部装入内存,而是允许被分成多次调入内存。
  • 对换性:在作业运行时无需一直常驻内存,而是允许在作业运行过程中,将作业换入、换出。
  • 虚拟性:从逻辑上扩充了内存的容量,使用户看到的内存容量,远大于实际的容量。

虚拟内存技术,允许一个作业分多次调入内存。如果采用连续分配方式,会不方便实现。因此, 虚拟内存的实现需要建立在离散分配的内存管理方式基础上。

主要区别: 在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。 若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。

操作系统要提供请求调页(或请求调段)功能,操作系统要提供页面置换(或段置换)的功能。

请求分页管理方式

页表机制

与基本分页管理相比,请求分页管理中,为了实现“请求调页”,操作系统需要知道每个页面是否已经调入内存;如果还没调入,那么也需要知道该页面在外存中存放的位置。

当内存空间不够时,要实现“页面置换”,操作系统需要通过某些指标来决定到底换出哪个页面;有的页面没有被修改过,就不用再浪费时间写回外存。有的页面修改过,就需要将外存中的旧数据覆盖,因此,操作系统也需要记录各个页面是否被修改的信息。

image

缺页中断机构

在请求分页系统中,每当要访问的页面不在内存时,便产生一个缺页中断,然 后由操作系统的缺页中断处理程序处理中断。

此时缺页的进程阻塞,放入阻塞队列,调页完成后再将其唤醒,放回就绪队列。

如果内存中有空闲块,则为进程分配一个空闲块,将所缺页面装入该块,并修改页表中相应的页表项。

页面置换算法

最佳(OPT)置换算法

先进先出(FIFO)页面置换算法

FIFO 算法还会产生所分配的物理块数增大而页故障数不减反增的异常现象,这由 Belady 于 1969 年发现,因此称为 Belady 异常 。

最近最久未使(LRU)置换算法

最佳置换算法性能最好,但无法实现;先进先出置换算法实现简单,但算法性能差;最近最久未使用置换算法性能好,是最接近 OPT 算法性能的,但是实现起来需要专门的硬件支持,算法开销大。

时钟(CLOCK)置换算法

时钟置换算法是一种性能和开销较均衡的算法,又称 CLOCK 算法,或最近未用算法(NRU,Not Recently Used)

简单的 CLOCK 算法实现方法:为每个页面设置一个访问位,再将内存中的页面都通过链接指针链接成 一个循环队列。当某页被访问时,其访问位置为 1。当需要淘汰一个页面时,只需检查页的访问位。 如果是 0,就选择该页换出;如果是 1,则将它置为 0,暂不换出,继续检查下一个页面,若第一轮扫描中所有页面都是 1,则将这些页面的访问位依次置为 0 后,再进行第二轮扫描(第二轮扫描中一定会有访问位为 0 的页面,因此简单的 CLOCK 算法选择一个淘汰页面最多会经过两轮扫描)

改进型的时钟置换算法

简单的时钟置换算法仅考虑到一个页面最近是否被访问过。事实上,如果被淘汰的页面没有被修改过, 就不需要执行 I/O 操作写回外存。只有被淘汰的页面被修改过时,才需要写回外存。 因此,除了考虑一个页面最近有没有被访问过之外,操作系统还应考虑页面有没有被修改过。在其他 条件都相同时,应优先淘汰没有修改过的页面,避免 I/O 操作。这就是改进型的时钟置换算法的思想。 修改位=0,表示页面没有被修改过;修改位=1,表示页面被修改过。 为方便讨论,用(访问位,修改位)的形式表示各页面状态。如(1,1)表示一个页面近期被访问过, 且被修改过。 算法规则:将所有可能被置换的页面排成一个循环队列

第一轮:从当前位置开始扫描到第一个(0, 0)的帧用于替换。本轮扫描 不修改任何标志位

第二轮:若第一轮扫描失败,则重新扫描,查找第一个(0, 1)的帧用于 替换。本轮将所有扫描过的帧访问位设为 0

第三轮:若第二轮扫描失败,则重新扫描,查找第一个(0, 0)的帧用于 替换。本轮扫描不修改任何标志位

第四轮:若第三轮扫描失败,则重新扫描,查找第一个(0, 1)的帧用于 替换。 由于第二轮已将所有帧的访问位设为 0,因此经过第三轮、第四轮扫描一 定会有一个帧被选中,因此改进型 CLOCK 置换算法选择一个淘汰页面最多 会进行四轮扫描

页面分配策略

抖动

工作集

地址翻译