操作系统——内存管理

0.关注博主有更多知识

操作系统入门知识合集

目录

7.1内存管理功能

思考题:

7.2物理内存管理

7.2.1分区内存管理

思考题:

7.2.2分区放置策略

7.2.3内存覆盖技术

7.2.4内存交换技术

7.2.5内存碎片

7.3虚拟内存管理

7.3.1页式虚拟存储管理

思考题:

7.3.2快表技术和页面共享技术

7.3.3缺页中断

思考题:

7.3.4页面淘汰

思考题:

7.3.5缺页因素和页式内存管理缺点

7.3.6段式内存管理和段页式内存管理

7.1内存管理功能

操作系统与普通软件的最大区别就在于操作系统具有进程管理和内存管理的功能。那么在用户的角度看来,我们希望存储器容量足够大、速度足够快、信息能够长期保存、多道程序并发运行。抛开存储器的物理性质不谈,就单单支持多道程序并发就会带来许多问题:我们希望能够实现代码和数据的共享从而节省内存,同时又不希望程序之间的相互非法访问。

实际存储体系:

  1.采用三级存储体系,在容量、速度、价格方面取到一个均衡点

  2.基本原理:当内存太小不够用时,用辅存来支援内存;暂时不运行的模块换出到辅存上,必要时再换入内存

存储管理的功能:

  1.地址映射

  2.虚拟存储

  3.内存分配

  4.存储保护

地址映射:

  1.定义:把程序中的地址(虚拟地址,也叫逻辑地址)转换成内存的真实地址(物理地址,也叫实地址)的过程

  2.方式:固定地址映射、静态地址映射、动态地址映射

  3.固定地址映射:

    1)定义:程序在编译或编译时就确定逻辑地址和物理地址的映射关系

    2)特点:因为程序在运行之前就已经确定了物理地址,所以程序加载时必须放在指定的内存区域。因为物理地址只有一份,所以当多个程序运行时,很容易产生地址冲突,导致运行失败

    3)缺点:程序被载入内存运行之前就确定了物理地址,那么程序装入内存之后就不能移动(如果移动了,再运行时就必须放回原来的位置);程序占用了连续的内存空间

  4.静态地址映射:

    1)定义:程序装入时由操作系统完成逻辑地址到物理地址的映射

    2)过程:每个程序都有自己的虚地址空间,这块空间的每个单位都有一个逻辑地址(VA,Virtual Addr.)。程序装入内存之后,操作系统会记录一个装入基址(BA,Base Addr.),这个装入基址用来描述程序在内存当中的起始位置。然后由操作系统根据公式计算出程序虚拟地址对应的物理地址,这个公式为[物理地址=装入基址+逻辑地址]

    3)特点:程序在被加载到内存时才确定物理地址,这就已经与固定地址映射法拉开差距了。固定地址映射在运行之前就确定了物理地址,而这个物理地址只有一份,注定了多个程序之间不能存在相同的地址;而静态地址映射在没有被加载内存之前使用的是虚拟地址,这个虚拟地址可以重复使用,也就是说,操作系统使用静态地址映射法能够确保程序之间即使使用相同的地址,也不会影响这些程序的装入,只需要分配不同的装入基址即可。

    4)缺点:程序被装入内存之后不能移动(如果移动了,再运行时就必须放回原来的位置);程序占用了连续的内存空间

  5.动态地址映射:

    1)定义:在程序执行过程中把逻辑地址转换为物理地址。也就是说CPU只有执行了访存指令,操作系统才会计算真正的物理地址。

    2)过程:如同静态地址映射一样,程序在被加载到内存之前也只是拥有虚拟地址,加载到内存后操作系统分配一个装入基址。但是此时操作系统不会立刻计算物理地址,而是CPU执行了访存指令才计算物理地址。

    3)特点:程序占用的内存可动态变化并且可以不占用连续的内存空间,因为程序被加载到内存时并没有确定物理地址,因此这个程序还处于"灵活"状态,不过操作系统必须及时更新程序的基址地址以及知道程序不占用连续的内存空间时每一段的基址。因为程序可以不占用连续的内存,那么这个程序的某一段可以作为多个进程共享的代码,也就是说被共享的代码可以作为独立的一段存放

    4)缺点:实现动态地址映射需要有复杂的硬件支持,例如内存管理单元MMU;同时操作系统的设计也会变得复杂

虚拟存储:

  1.为什么要有虚拟内存:当程序过大或过多时,也就是内存不足以装入这个程序时导致该程序无法运行;多个程序并发时地址发生冲突导致的不能运行......这些问题必须要通过虚拟内存来解决

  2.虚拟内存的概念:虚拟内存是面向用户的、虚拟的封闭的、连续的线性存储空间,这个空间的大小由操作系统确定,典型32位操作系统提供虚拟内存的大小为4G。这个封闭的虚拟内存只能被进程所占用(进程是分配资源的基本单位),虚拟内存与物理内存相互分离,他们的地址相互不冲突。程序员在编程时使用的地址就是虚拟内存的地址。需要注意的是,这个虚拟内存并不是真正意义上的内存空间(或许本身就不存在),虚拟内存更加强调虚拟地址,每个进程能够拿到多少地址就决定了他们能看到多少空间,也就是说在32位操作系统下,每个进程都占用许多虚拟地址,这些虚拟地址组合起来的空间有4G这么大(一个地址对应一个字节),这个空间我们就称为虚拟内存,它是一种假想的空间。至于进程如何拥有这么多虚拟地址的,我们把这个问题留到Linux环境编程部分中讲解。

  3.虚拟内存的目标:

    1)使得较大的程序能够在较小的内存中运行,因为这些程序看到的内存(虚拟内存)可能比物理内存还要大

    2)使得多个程序在较小的内存中运行,因为每个程序都能看到一个很大的内存(内存),操作系统通过一些特殊的管理手段就能使得这些程序同时被加载到内存

    3)使得多个程序并发运行时地址不冲突,虚拟地址并不是唯一的,也就是说及时多个程序之间使用的虚拟地址一样,操作系统可以通过特殊的手段将这些程序装入内存的不同地方

    4)虚拟内存使得物理内存的利用率变高,具体表现在没有内存碎片、共享内存方便

内存分配功能:

  1.目的:为程序运行时(进程)分配足够的内存空间

  2.过程:操作系统通过一些策略来达到上述目的:

    1)放置策略:操作系统需要考虑程序装入内存时需要放在内存的哪个位置

    2)调入策略:操作系统需要考虑何时把要运行的代码和数据调入内存

    3)淘汰策略:操作系统需要考虑当内存空间不足时,要换出某些"无用"代码和数据以腾出内存空间

存储保护功能:

  1.目的:保证在内存中的多道程序只能在给定的存储区域内活动并互不干扰,这样做的目的是防止越界(程序与程序之间的非法访问)、防止越权(防止用户程序访问内核程序)

  2.方法:在CPU中设置一对上限寄存器和下限寄存器存放程序在内存中的下限地址和上限地址,程序访问内存时硬件自动将目的地址与下限寄存器、上限寄存器中存放的地址做比较,如果超出这两个寄存器中的地址范围,则是越界访问。另外还可以通过基址寄存器和限长寄存器来实现存储保护

思考题:

1.何为地址映射功能?在开发环境当中定义的变量与地址是什么关系?

地址映射指的是将程序的逻辑地址转化为物理地址。开发环境当中定义的变量确实占有空间,但是这个空间是编译器分配的,每个变量占用的空间的地址都是编译器分配的虚拟地址。

2.具有虚拟内存管理功能操作系统,是不是不需要物理内存了?

肯定需要物理内存。因为虚拟内存是一个假想的内存,目的是让每个进程感觉自己"独占"物理内存,进程能"独占"内存是由于虚拟地址的存在,这些虚拟地址表示起来的内存空间或许比物理内存还要大,这种思想有利于程序的并发执行。而CPU能够访问的地址仅仅是物理地址。

7.2物理内存管理

在了解虚拟内存之前,先了解操作系统如何直接对物理内存做管理以及管理过程中出现的问题,然后再介绍操作系统虚拟内存的管理手段。通过比较操作系统直接管理物理内存和管理虚拟内存之间的差异,我们可以更加清晰的认识到为什么现代操作系统都采用虚拟内存管理。操作系统管理物理内存时使用的手段为:

  1.分区内存管理

  2.分区放置策略

  3.内存覆盖技术

  4.内存交换技术

  5.内存碎片问题

7.2.1分区内存管理

分区存储管理可以分为两类:

  1.单一区存储管理:用户区的内存作为一整个分区

  2.分区存储管理:固定分区和动态分区

单一区存储管理:

  1.定义:不分区存储管理(用户区的内存作为一整个分区),这块内存只能被一个程序完全占用。例如DOS操作系统,DOS操作系统被加载到内存后只能再加载一个程序

  2.优点:简单,不需要复杂的硬件支持,适用于单用户单任务的操作系统

  3.缺点:程序运行占用整个内存,即使该程序很小。没有并发和内存浪费、利用率低

分区存储管理:

  1.定义:把用户区内存划分为若干大小不等的分区,以供不同的程序使用。使用单用户但任务的操作系统

  2.分类:固定分区和动态分区

  3.固定分区:

    1)定义:把内存固定地划分为若干大小不等的分区供各个程序使用。每个分区的大小和位置都固定,操作系统运行期间不再重新划分

    2)分区表:操作系统为了维护各个分区,会使用名为"分区表"的一种数据结构记录各个分区的位置、大小和使用情况。如下图(1)所示,该图表示内存的各个分区都为空闲状态;图(2)表示某些分区已经装入了程序

    3)使用特点:在装入程序之前,内存已被分区且大小固定,不可再改变;每个分区的大小不相同,目的是为了适应不同大小的程序;操作系统要维护分区表。例如IBM的360操作系统就采用了固定分区的方法,它是一款具有固定任务数的多道程序操作系统

    4)缺点:浪费内存,如果装入的程序比所在分区小,那么该分区多余的部分不能被其他程序使用;当某一程序的大小超过最大分区的大小时,该程序永远不可能被运行。操作系统当然意识到了这些缺点,所以它尽可能会根据分区表来安排程序装入内存,使得每个程序都能找到自己合适的分区

  4.动态分区:

    1)定义:再程序装入时创建分区,使分区的大小刚好与程序的大小相等

    2)过程:操作系统管理动态分区时,会采用下图的策略。图(1)表示当内存没有程序时(除了操作系统);图(2)表示某些程序装入内存时;图(3)表示某些程序撤出内存时

    3)特点:分区的个数和大小都可变,由程序的个数和大小决定

    4)缺点:存在内存碎片问题如上图(3)程序1和程序3撤出内存。现在有程序5(18K)和程序6(23K)又被加载进内存,就会导致某些空闲区只有1KB、2KB或3KB的大小,无法装入任何程序(程序再小也没这么小)。所以动态分区最核心的目标是解决内存碎片问题

思考题:

1.分区存储管理用在专用的、具有特定的任务的嵌入式系统(嵌入式系统不是嵌入式操作系统)中,可不可行?

可行,嵌入式系统需要轻量级的操作系统,并且嵌入式系统的任务种类固定,因此嵌入式操作系统可以采用固定分区或动态分区的管理策略来管理内存。

7.2.2分区放置策略

这里讲述的分区放置策略基于动态分区。

空闲区表:操作系统维护一个数据结构,该数据结构描述内存空闲区的位置和大小

分配过程:假设程序的大小为s

  1.从空闲区表的第一个区开始,寻找>=s的空闲区

  2.找到后从该空闲区中分割出大小为s的空间以便转入程序

  3.分割后的剩余部分作为空闲区,仍然等级在空闲区表中

  4.分割空闲区时一般从空闲区的结束位置向前分割。例如有一程序(15K)要装入内存,操作系统遍历空闲区表发现第一块合适的空闲区的起始位置为30K,结束位置为50K(起始位置+大小=结束位置),从50K开始向上划分15K的空间给该程序,这样操作系统只需要修改空闲区表中被分割的空闲区的大小即可

放置策略——空闲区表如何排序:空闲区表的排序影响了操作系统的内存放置策略

  1.按空闲区的位置由低到高排序:首次适应算法

    如上图的空闲区表就是按空空闲区的起始位置由低到高排序的。它的特点就是操作系统要求用户尽可能地先使用低地址空间。

  2.按空闲区的位置由高到低排序:没有这个算法

  3.按空闲区的大小由低到高排序:最佳适应算法

    如下图所示就是按空闲区表的大小递增排序。它的特点就是以最少的遍历次数,最快的找到最合适的空闲区。

  4.按空闲区的大小由高到低排序:最坏适应算法

    如下图所示就是按空闲区表的大小递减排序。它的特点就是尽可能使用大空间,使用后的空闲区剩余的大小还能被其他较小的程序使用。

事实上"最佳适应算法"并不是最佳,"最坏适应算法"并不是最坏。最佳适应算法有可能会遍历空闲区表很久,并且在后期会产生非常多的内存碎片;最坏适应算法很有可能使得一个大的空闲区被多个较小的程序使用。

分区的回收:

  1.功能:当程序撤出内存时,操作系统需要回收该程序撤出之前占用的空间(释放区),然后将该空间视为空闲区,并将该空闲区登记在空闲区表中

  2.回收算法:操作系统要考虑释放区是否与现有的空闲区相邻。如果释放区与现有的空闲区不相邻,那么就空闲区表就要多一项;如果相邻,就需要将两个区合并,并更新空闲区表的登记信息

  3.回收算法的具体过程:

7.2.3内存覆盖技术

内存覆盖技术也是从分区思想进化而来的。分区管理内存会存在一个问题,那便是无法在较小的分区中装入较大的程序。而内存覆盖技术便是解决这一问题。

覆盖(Overlay):

  1.目的:在较小的内存空间中运行较大的程序

  2.内存分区:内存在整体上被划分成两个区——常驻区和覆盖区。常驻区指的是该分区长时间被一个程序的某一程序段单独且固定地占用,常驻区可以划分多个;覆盖区指的是能被多个程序段共享的一块内存空间,可以划分多个

  3.程序的划分:程序员主动划分程序模块和确定覆盖关系,其中最核心的、最常用的应当被分配到常驻区;其他较为独立的模块可以不同时装入内存,它们可以共用覆盖区。以一个简单的例子来了解内存覆盖技术:假设当前有用户可用内存110K,而有一程序,其大小为190K,程序员已经将该程序划分好了层次、模块关系。

在这个例子中,程序员将程序划分为了三个层次、六个模块。其中D、E、F作为应用层,程序员认为这三个模块是用户直接使用的模块,D、E两个模块会自己去调用B,B自己去调用A从而完成任务,F模块自己去调用C,C自己去调用A从而完成任务。这些模块的大小是合理的,无论是哪个模块都能装入特定的覆盖区。

  4.缺点:缺点非常明显,程序员需要做的工作太多了,为程序划分模块、指定模块装入特定的内存分区......这个过程一旦出错程序便不能运行,例如将D、E模块合并,那么它的大小就为60K,而内存当中没有60K大小的空间;如果将A装入覆盖区,那么很有可能B、C在调用A时,A已经被其他模块覆盖了......除了程序员需要工作之外,程序的执行时间也非常长,因为程序需要频繁地从外存装入内存

7.2.4内存交换技术

内存交换技术(Swapping):

  1.原理:内存不够时把程序写到磁盘(换出,Swap Out);当程序需要从新写回内存时,从磁盘再次装入内存(换入,Swap In)

  2.优点:能够增加进程的并发数,程序员不用再考虑程序的结构

  3.缺点:如果以整个程序为单位换入换出,那么交换的单位就太大了;并且换入换出的过程会增加CPU、操作系统的开销

  4.需要考虑的问题:应该减少换入换出的单位,即换入换出整个程序减小到换入换出某个程序的某个模块或者段;需要提供换出之后的程序模块在外存当中的管理方法;程序模块换入内存时,需要考虑该模块装入内存时应该放在哪个位置,即地址重定位

  5.地址重定位:地址重定位发生在程序模块换入内存时,操作系统考虑是将该程序模块放入原来的位置还是另找一块空闲区存放:

如果我们把换入的程序模块放到其第一次装入的位置(装入原来的位置),这种方案实现起来确实简单,但如果这个位置已经有其他程序了,就会造成地址冲突;而如果装入其他人任意一块空位置,就会发生地址重定位问题,地址重定位需要操作系统去解决,地址重定位之后地址冲突的概率就会减小许多

7.2.5内存碎片

我们在介绍动态分区的时候,提到过内存碎片。内存碎片指的是空闲区过小,实际上难以被利用,内存碎片会降低内存的利用率。

动态分区的缺点:

  1.容易产生内存碎片:内存被反复的释放和分割

  2.最佳适应算法能够确保找到最合适的空闲区,但其最容易产生内存碎片;最坏适应算法是最不容易产生内存碎片的算法。但这并不意味只采用最坏适应算法就能解决问题

解决内存碎片的方法1:规定门限值,即计算当前空闲区被分割后还剩多少空闲大小,如果小于门限值,那么该空闲区不分割,而是直接全部分配给程序

解决内存碎片的方法2:内存拼接技术。将所有空闲区集中一起构成一个大的空闲区。那么何时集中空闲区在一起便又是个问题:

  1.释放区回收的时候集中空闲区:程序本来就会频繁地装入和撤出,这就意味着选择在释放区回收地时候集中空闲区会导致拼接频率过大,增加操作系统的开销

  2.操作系统找不到足够大的空闲区时:如果要装入一个较大的程序,但是此时内存当中没有匹配的空闲区,操作系统在此时就会集中所有空闲区。但是选择在这种时机集合空闲区就意味着操作系统管理空闲区的算法会变得复杂

  3.定期集中空闲区:这样做很合理,但管理算法也较复杂

内存拼接技术看似美好,实际上问题是最严重的:它会过量地消耗操作系统的资源,这是很正常的;内存拼接的主要问题是离线拼接,离线拼接就意味着需要集中所有空闲区的时候,CPU、操作系统当前所做的工作会全部停下来转向去执行集合所有空闲区;空闲区被集中之后,内存当中已存在的程序的位置就会发生改变,这时候就要重新定义作业

解决内存碎片的方法3:解除程序占用连续内存空间才能运行的限制。把程序分拆为多个部分,装入不同的空闲区,充分利用碎片。这种解决方案是最佳,但是其实现过程是蛮复杂的,所以这里就不阐述了。事实上,解除程序必须占用连续的内存空间的限制是虚拟内存的基本思想

7.3虚拟内存管理

我们可以看到,操作系统直接管理物理内存会带来许多问题,为了解决这些问题又要采用复杂的管理手段,这些管理手段又会增加操作系统的消耗。在这样的背景下,诞生出了虚拟内存。虚拟内存的核心是让每个进程以为自己拥有整个物理内存甚至比物理内存还大的空间,操作系统就要想办法不去揭穿这个"谎言",从而让计算机系统正常工作。那么操作系统不去揭穿"谎言"的办法,就是我们要探讨的主题——操作系统如何管理虚拟内存。

先来回顾一下物理内存管理的特点以及缺点:

那么改善物理内存管理的相关技术有:内存拼接、内存交换、覆盖技术......

虚拟内存管理的目标:

  1.使得较大的程序能够在较小的内存中运行

  2.使得多个程序能在较小的内存中运行(较小的内存也能支持并发)

  3.使得多个程序并发运行时地址不冲突

  4.使得内存利用率高,无内存碎片、共享方便......

我们在7.1节当中提到过操作系统管理内存时需要管理地址映射,那么地址映射就属于虚拟内存管理的范畴。其中介绍了三种映射方法:固定地址映射、静态地址映射、动态地址映射。使用固定地址映射的程序在未被加载到内存之前就已经确定物理地址了;使用静态地址映射的程序装入内存后就直接拥有了物理地址;使用动态地址映射的程序只有在CPU执行访存指令的时候才需要物理地址。所以,使用动态地址映射更加符合虚拟内存管理的理念。当然,虚拟内存管理不是这么简单粗暴的,现在操作系统对虚拟内存的管理是复杂且高效的。

虚拟内存的实现思路:在程序运行时,只把当前必要的、很小的一部分代码和数据装入内存,其余代码和数据需要时再装入内存,不再运行的代码和数据及时从内存中删除。

程序运行的局部性:CPU一个有限的时间段内,其访问的代码和数据往往集中在有限的地址范围内;把程序的一小部分装入内存在较大概率上能让该程序运行一小段时间(如没有发生较大范围的跳转)。

典型的虚拟内存管理方式:

  1.页式虚拟存储管理

  2.段式虚拟存储管理

  3.段页式虚拟存储管理

7.3.1页式虚拟存储管理

内存分页与进程分页:程序要装入内存运行时,操作系统使用进程描述该程序的一次运行活动,那么进程是资源分配的基本单位,所以进程具有进程地址空间(进程地址空间是一个实在的空间,但是这块空间的地址都是虚拟地址,所以更习惯称它为虚拟地址空间),该空间内存放有代码和数据。那么页式虚拟存储管理中,进程地址空间和物理内存空间被划分成等大小的小片,这个小片通常为1K、2K、4K......其中4K是最常见的。那么进程的小片称为页帧(虚拟页或页面);物理内存的小片称为页框(物理页)

进程装入和使用内存的原则:内存以页框为单位分配使用;进程以页帧为单位装入内存。只要把进程的部分页帧装入内存便可运行(可以运行一小段时间),页帧在内存当中占用的页框不必相邻;需要新的页帧时,按需从外存中调入内存,不再运行的页帧及时删除,腾出空间。

以上是页式虚拟存储管理的一个大概逻辑,现在需要关注操作系统是如何具体进行管理的:

  1.虚拟地址(VA)可以分解成页号P和页内偏移W:页号P=VA / 页的大小,页号代表了进程的某个虚拟地址在哪个页帧中;页内偏移W = VA % 页的大小,页内偏移代表了该虚拟地址在某个页帧当中的具体一个字节。

 2.页面映射表:由于进程使用的依然是虚拟地址,所以CPU访问的也是虚拟地址,所以操作系统必须提供一种虚拟地址到物理地址的转化机制——查询页面映射表。页面映射表是记录页帧与页框之间的对应关系,简称页表,是由操作系统维护的一种数据结构。页表当中有非常多的属性,但现在我们只关心两种:页帧号和页框号。页帧号登记了虚拟地址所在的页帧号,页框号登记了该页帧所在的物理页面:

  3.页式地址映射:

    1)功能:将虚拟地址转化为物理地址

    2)过程:从虚拟地址(VA)分离出页帧号P和页内偏移W;查找页表,以P为索引找到对应的页框号P`;计算物理地址MA,MA=P` * 页的大小 + W

思考题:

1.在虚拟内存管理方案的支持下,Windows或Linux会不会因为内存过小导致程序无法运行?

不会。在虚拟内存的管理方案中,操作系统只会选择一小部分代码装入内存,必要时再从外存中加载新的代码。所以支持虚拟内存管理的操作系统中的程序不会因为内存过小而无法运行,最多是运行、加载速度变慢。  

2.页表在页式内存管理中起什么作用?

作用一便是记录页帧与页框的对应关系(虚拟地址与物理地址的对应关系);作用二便是提醒操作系统CPU的访存指令是否非法(越权访问物理地址或访问不存在的物理地址);作用三则是方便计算物理地址。

7.3.2快表技术和页面共享技术

快表机制:

  1.快表的概念:慢表指的是存放在内存中页表;快表指的是存放在CPU高速缓存中的页表

  2.快表的特点:容量小、访问快,但是成本高;快表是慢表的部分内容的拷贝;操作系统需要地址映射时优先访问快表,如果在快表中完成了地址映射,则称为命中,没有命中时则需要访问慢表,并更新快表(将快表中没有的部分从慢表中拷贝过来)。合理的页面调度而策略能够使快表具有较高的命中率,现代操作系统的命中都在90%~95%之间

  3.快表机制下的地址映射过程:CPU当中存在一个页表地址寄存器和一个页表长度寄存器,分别记录了慢表在内存当中的起始位置和页表的长度;当CPU执行访存指令时,访问的都是虚拟地址,此时需要计算出页号P和页内偏移W,计算出页号P后优先在高速缓存当中查询快表;如果快表查询未果,页号P与页表长度寄存器记录的页表长度比对,如果P>=页表长度,说明访问越界;如果没有越界,则通过页表地址寄存器中存储的慢表地址去内存当中查询慢表,从而计算出物理地址

页面共享技术:

  1.页面共享的例子:假设有10个文本编辑器进程,每个进程有150KB的代码段和50KB的数据段,这10个进程如果完整的装入内存,那么就会占用10*(150+50)KB=2MB的空间;而使用页面共享技术可以减少内存开支,因为每个进程的程序都是文本编辑器,不同的地方仅仅在于数据不同,所以这10个进程完全可以共享代码段,只需要保持数据相互独立即可,那么使用页面共享技术后的内存占用大小为150+10*50=650KB。

  2.页面共享原理:在不同进程的页表中(每个进程都有一个页表,该页表的地址被记录在PCB中)填上相同的页框号,多个进程能访问相同的物理内存,从而实现页面共享;共享的页面在内存只有一份真实存储,进而节省内存;虽然共享的页面当中只有一份真实存储,但是每个进程互不知道彼此的存在,也就是说每个进程都认为自己单独占用该页面,这就是虚拟内存带来的好处

7.3.3缺页中断

在文章开头提到过三级存储体系:

CPU先从高速缓存中读取数据,如果在高速缓存中没有命中,那么就要去内存中读取数据;在页式虚拟内存管理中,因为进程的代码和数据并不是全部都被加载进内存,所以CPU访问内存时有可能访问不到想要的数据对应的页,此时触发缺页中断,操作系统就会从外存当中加载对应的页到内存。

页表扩充——带中断位的页表:

  1.现在我们介绍一个新的页表项:中断位I和辅存地址。若I=1,说明CPU访问的页不在内存当中,此时触发缺页中断,操作系统通过页表当中对应的辅存地址读取CPU要访问的页到内存当中;若I=0,说明CPU访问的页在内存当中。

  2.缺页中断:

    1)定义:在地址映射的过程中,当要访问的目的页不在内存时,操作系统产生一种异常中断——缺页中断

    2)缺页中断处理程序:缺页中断处理程序把访问内存所缺的页从页表指出的辅存地址对应的页调入内存的某个页框中,并更新页表中该页对应的页框号以及修改中断位I为0

上图是理想情况下的缺页中断,实际上操作系统需要考虑的事情很多。当缺页中断发生时,操作系统需要判断内存的页框是否已经被全部占用,如果全部都被占用则需要一个优秀的淘汰策略来清空一个页框。

页表扩充——带有访问位和修改位的页表:

  1.对页表再次进行扩充:访问位和修改位。若访问位的值位0,说明该页最近没有被访问,反之最近则被访问过;若修改位的值位0,说明该页未被修改过,反之则被修改过

  2.CPU执行访存指令的过程:

其中"是否需要重写"指的是,如果选择了一个修改过的页面(即页表中的修改位为1)作业淘汰页面,那么该页就需要被保存下来,保存的方式就是写入外存。中断处理程序与其他中断处理程序不一样的是,中断处理程序执行完之后并不会返回触发中断的断点处,而是让CPU重新执行访存指令。

缺页率:缺页率描述了触发缺页中断的概率,如果缺页率过高,说明该操作系统在虚拟内存管理方面做的不够好。缺页率=缺页次数 / 访问页的总次数。

我们可以在Windows的任务管理器当中查看CPU执行进程的访存指令时关于缺页中断的信息:

思考题:

1.页式虚拟内存管理是否适合实时操作系统?

不适合。实时操作系统讲究的是及时且快速地处理突发任务,如果采用页式虚拟内存管理的话,该任务就不会全部装入内存,也就是说CPU在处理这个任务时很可能会触发缺页中断,操作系统访问外存是一个I/O操作,所以很可能会导致该任务不能及时被处理。

7.3.4页面淘汰

淘汰策略:

  1.当触发缺页中断时并且内存当中没有可用的空闲页框时,操作系统需要淘汰掉一个页面以腾出空间,从外存装入CPU所需的页帧

  2.淘汰策略:选择淘汰哪一页便是淘汰策略

  3.页面抖动:页面在内存和辅存频繁交互的现象称为页面抖动。页面抖动会导致操作系统的管理效率下降

  4.好的淘汰策略具有较低的缺页率和较少的页面抖动

常用淘汰算法:

  1.最佳算法(OPT算法)

  2.先进先出淘汰算法(FIFO算法)

  3.最久未使用淘汰算法(LRU算法)

  4.最不经常使用算法(LFU算法)

最佳算法(OPT算法,Optimal):

  1.思想:淘汰以后不再需要或最远的将来才会用到的页面

  2.例子:假定一个内存只有三个页框,现有一个程序,CPU访问该程序的页面序列为A、B、C、D、A、B、E、A、B、C、D、E,按照OPT算法来分析淘汰的页面和缺页中断:

在该例中,缺页次数为7次,访问次数为12次,所以缺页率为58%。

  3.特点:最佳淘汰算法在理论上是最佳算法,但实际上这种技术不可能实现。因为操作系统根本无法预知程序的下一步将要执行什么

先进先出淘汰算法(FIFO算法):

  1.思想:淘汰在内存中停留时间最长的页面

  2.例子:假定一个内存只有三个页框,现有一个程序,CPU访问该程序的页面序列为A、B、C、D、A、B、E、A、B、C、D、E,按照FIFO算法来分析淘汰的页面和缺页中断:

在该例中,缺页次数为9次,访问次数为12次,所以缺页率为75%。

  3.特点:实现非常简单,页面按进入内存的时间排序,淘汰队头页面

  4.缺点:效率并不理想,如果需要淘汰一个页面时选择的是最先进入的那个内面,但是很有可能下一次访问的页面就是刚刚被淘汰掉的页面。FIFO淘汰策略会出现异常现象,对于一些特定的访问序列,随着内存的初始空闲页框增多,缺页率不降反增(读者可以上面的例子修改一下,将内存的初始页框数修改为4,最终得到的缺页率为83%)

最久未使用淘汰策略(LRU算法,Least Recently Used):

  1.思想:淘汰最长时间未被使用的页面。这是利用了局部性原理,当一个页面很久没有被访问时,下一次访问的页面很大概率也不是这个页面

  2.例子:假定一个内存只有三个页框,现有一个程序,CPU访问该程序的页面序列为A、B、C、D、A、B、E、A、B、C、D、E,按照LRU算法来分析淘汰的页面和缺页中断(具体过程省略)。在该例中,缺页次数为10次,访问次数为12次,所以缺页率为83%。

  3.LRU的实现1——硬件方法:每一个页面可以配备一个移位寄存器,每当该一面被访问则将其寄存器重置为1。寄存器当中值会随时间周期性的向左移位,当需要淘汰页面时选择一个最大的移位寄存器的值对应的页面:

这种方案的缺点非常明显:只有当寄存器的位数足够多且周期足够小的时候才能精确比较出页面之间谁在内存当中停留的最久,但是要实现,硬件成本是非常高的;一般的,移位寄存器的位数都非常少,所以随着时间推移会造成溢出,即所有页面的移位寄存器都为0,导致无法比较页面之间谁停留在内存当中的时间最久。

  4.LRU的实现——软件方法:使用LRU近似算法也能实现LRU淘汰策略。利用页表的访问位属性,页被访问时操作系统将其对应的页表项的访问位的值置为1,并且操作系统周期性地将页表中的所有页的访问位置0,需要淘汰页面时根据访问位来淘汰页面:访问位为1时,说明在周期时间内该页面被访问过,保留,不淘汰;访问位为0时,说明在周期时间内该页面没有被访问过,淘汰该页!其缺点在于周期难以确定:如果周期太小,就会造成页表中访问位为0的页面过多,找不到合适的淘汰页面;如果周期太大,就会造成页表中访问位为1的页面过多,找不到合适的淘汰页面

最不经常使用算法(LFU算法,Least Frequently Used):

  算法原则:选择到当前时间为止被访问次数最少的页面,页表中多加一个属性——访问计数器,当页面被访问时,对应的访问计数器加1,发生缺页中断时,淘汰计数值最小的页面,并将所有计数清零。

思考题:

1.如果最佳算法(OPT算法)无法被实现,那它存在的意义是什么?

即使OPT算法无法实现,但是可以被当成一种指标,众多淘汰算法中谁的效率最接近OPT算法,谁就是好算法。

7.3.5缺页因素和页式内存管理缺点

缺页的因素:

  1.淘汰算法:如果操作系统使用的淘汰算法不够优秀、效率不高,就会导致许多的页面发生被不合理的淘汰,进而导致缺页中断

  2.分配个进程的页框数:给进程分配的页框太少,就会导致进程大部分的代码和数据不在内存当中,进而导致缺页中断

  3.页本身的大小:如果页面太大,就会浪费内存,一个极端就是单一区存储管理;如果页面太小,就会导致页表的长度增加,而页表也是需要在内存当中存储的,所以也会浪费内存,并且因为页面太小,能够容纳的代码和数据也相对较少,所以更有可能导致缺页中断。页面的常见大小为2的整数次幂,例如1KB、2KB、4KB......现代硬件体系结构和操作系统都是以4KB为单位划分页面“

  4.程序的编制方法:局部性不强的代码更有可能导致缺页中断。我们开辟一个存储int类型的10000*20000的二维数组,使用不同的方法遍历该数组,其遍历时间是不一样的:

#include <ctime>
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<vector<int>> arr(10000, vector<int>(20000));

	size_t begin1 = clock();
	for (int i = 0; i < arr.size(); i++)
	{
		for (int j = 0; j < arr[0].size(); j++)
		{
			arr[i][j] = 0;
		}
	}
	size_t end1 = clock();

	size_t begin2 = clock();
	for (int i = 0; i < arr[0].size(); i++)
	{ 
		for (int j = 0; j < arr.size(); j++)
		{
			arr[j][i] = 0;
		}
	}
	size_t end2 = clock();

	cout << "begin1 - end1 = " << end1 - begin1 << endl;
	cout << "begin2 - end2 = " << end2 - begin2 << endl;

	return 0;
}

二维数组在内存当中是线性存储的,遍历方法一是逐行遍历的,就是说遍历的每个元素都是相邻的;而遍历方法二则会发生较大范围的跳转,就有可能导致访问的页面不在内存当中而触发缺页中断。即使最后计算的结构相差不是很大(大约300ms),但足以证明,局部性越强编码其效率越高

页式内存管理的不足:

  1.页面划分毫无逻辑含义:事实上我们介绍了很多关于页的描述,但我们或许并不知道分页到底是为了什么,所以页面的划分是没有实际的逻辑意义的

  2.页面的共享不灵活:需要共享的页面只需要占用一个物理页框,但如我们指向共享该页面的某一部分,页式内存管理是做不到的

  3.内存碎片:当我们的代码和数据不足以满足页面的单位大小时,就会产生内存碎片

7.3.6段式内存管理和段页式内存管理

段式内存管理:

  1.进程分段(进程地址空间分段):把进程按逻辑意义划分成多个段,每段有段名,长度不定,进程由多段组成。例如可以将进程划分为代码段、数据段、堆栈段......

  2.段式内存管理的内存分配:进程以段为单位装入内存(实际上就是将程序分段)装入内存,每段分配连续的内存空间,但是段和段之间不要求相邻

  3.段式内存管理的虚拟地址:与页式内存管理一样,段式的虚拟地址可以分理出段号S和段内偏移W

段式内存管理的地址映射:

  1.段表(SMT,Segment Memory Table):记录每段在内存中映射的位置。具体记录的属性有段号S,表示段的唯一编号;段长L,表示对应段的长度;基地址B,表示该段在内存中的首地址。那么为什么页表不需要页框的首地址?其原因在与页面的大小的是固定的,可以很难容易计算出基址。

  2.地址映射过程:

    1)由逻辑地址分离出段号和段内偏移

    2)查询段表,检索段号S,查询该段的基地B址和长度L

    3)计算物理地址,MA = B + W

    4)地址映射过程中需要保证段内偏移不越界,即 0<=W<L

    5)段表不止记录有段号、段长、基地址,与页表一样,段表也记录有中断位、访问位、修改位、RWX权限等属性

段的共享:

  1.共享段在内存当中只有一份。如同共享页一样,共享的页框在内存当中也只有一份

  2.共享段被多个进程映射到各自段表(每个进程一个段表)

  3.需要共享的模块都可以作为单独的段

段式内存管理的缺点:

  1.段需要连续的内存空间

  2.段的最大尺寸收到内存大小的限制

  3.在外存中管理大小可变的段比较困难

段式内存管理与页式内存管理:

  1.段长可变;页面是固定的大小

  2.段的划分有具体的逻辑意义;页面无意义

  3.段的共享较为方便;页面共享不方便(共享的页框不止一个或不足一个时)

  4.对用户来说段是可见的;页面对用户来说是不可见的

  5.段内偏移有溢出;页面偏移无溢出(主要是取模运算)

段页式内存管理:

  1.段页式内存管理是结合了段式、页式的优点集合而来的内存管理技术。现代操作系统都是段页式内存管理

  2.具体的结合方法为:以段为主,在段中划分页(包括物理内存也划分等大小的页)

  3.段式内存管理的虚拟地址:该地址宏观上由两个部分构成,即段号S和段内偏移W;但在段内偏移W`中有可以分离出页号P和页内偏移W

  4.操作系统在加载程序时,依然以页为单位划分,按页装入内存

段页式内存管理的地址映射:

  同时采用段表和页表实现地址映射:

    1)操作系统为每个进程建立一个段表

    2)操作系统为每个段建立一个页表

    3)段表给出每个段的页表基址(页表基址就是段的基地址)和页表长度(页表长度就是段长)

    4)页表给出每页的对应页框

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小龙向钱进

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值