对操作系统中的内存管理浅显理解

编者能力有限,有些专业名词不是那么的专业,请大家见谅。

启蒙篇

CPU执行程序的基本原理?

我们以“3+2”这条指令的执行过程为例:

先将这条指令从磁盘中读到内存中,“3”、“2”、“+”分别在内存中的不同地址上存储。

CPU先发出一个地址信号,通过地址总线找到“3”这条数据所在内存中某一行的地址,然后再根据这个地址中的八位数据分别对应的八条数据总线,将数据传入CPU的某个寄存器中(假设传入到“R0”寄存器中)

同理,“2”这条数据也是经过以上过程传入到“R1”寄存器中的。

但如果是指令(例如“+”),则不会被读入CPU的寄存器中,他会事先被CPU中的ALU(算数逻辑运行单元,是执行各种指令操作的一个元件)所识别读入进去,等待另一条数据读入寄存器后,再做加法运算。

运行结束后,会产生一个“5”的数据,它存储到“R2”的寄存器中。

“5”如果要进入到内存存储,则是数据进入CPU的逆过程,此处不再赘述。

通过以上的例子,我们需要注意:

  • 程序最终是一条一条的被读入寄存器内执行的。也就是说,一个程序执行的路线为:磁盘->内存->CPU->内存。(CPU中寄存器的个数是非常有限的,一般只有几十个,而且这几十个寄存器又是按照功能分类的,分为通用寄存器和特殊寄存器,特殊寄存器只能被一些特殊的指令去访问,普通寄存器什么指令都可以访问。既能够访问通用寄存器,也可以访问特殊寄存器那一部分程序就形成了一种状态——内核态,其他那一部分代码就形成了用户态,操作系统处于两种状态之间。)

  • 内存卡是一个临时保存程序的一个中介;磁盘是一个永久保存程序的介质。

  • 地址总线的选中原理:译码器原理

  • 了解四大类存储器的速度和所处位置:

    位置:

    ​ 寄存器:在CPU的内部;

    ​ cache(缓存):在CPU的内部;

    ​ 内存:CPU的外部;

    ​ 磁盘:CPU的外部;

    速度(速度越快,成本越高):

    ​ 寄存器 > cache (cache的数据仍然要装到寄存器中)> 内存 > 磁盘(越靠近CPU的速度越快

相似概念的区分和总结

CPU位数:实际上就是CPU内寄存器的位数,CPU位数越大,一次性能够处理的数据也就越多。

OS位数:OS位数会受到CPU位数的限制,也就是OS位数只能小于或等于CPU的位数。例:32位CPU只能安装32位以下的操作系统。(硬件限制软件)
数据总线数:常见的为8位的,主要影响数据向CPU寄存器中的传输次数。例如:CPU为32位,数据总线数为8位,则需要传4次才可以把这个CPU中的一个寄存器存满。

物理地址总线数:物理地址可以存放地址的总位数,相当于物理地址位数。他可以限制可访问内存大小。

可访问内存大小(三方面的最小值):

物理地址总线数可以限制它(硬件限制),例如:32位的物理地址位数->可访问内存大小就是232B。

内存卡大小也可以限制它(根据实际情况限制),内存卡买小了,物理地址总线数够了也没用!

OS位数也可以限制它(软件限制),即使物理总线数够了,但是还是OS说用多少就用多少!

逻辑地址位数:等价于操作系统的位数。逻辑地址位数是由操作系统中所能运行的最大程序决定的,而操作系统本身就是这个最大程序。

虚拟内存理论大小:等价于逻辑地址位数。

虚拟内存实际大小:可访问内存大小+磁盘大小 >= 虚拟内存实际大小 <= 虚拟内存理论大小

内存管理逻辑图

在这里插入图片描述

基础篇

程序的基础知识

编译

将源代码编译成汇编代码或机器代码,编译后产生的目标模块,都有自己的地址,这个地址为逻辑地址(相对地址)。在目标模块中指令中出现的地址都是逻辑地址。

链接

各个模块需要合并为一个模块,先将地址重新按照一定的顺序进行编码,编码时,还要给后面的模块进行地址上的偏移。

经过编译链接后就会形成可执行文件。

通过安装这个可执行文件,可以把这个程序存放在磁盘中。

装入

装入介绍

当程序从磁盘中装入到内存中时,会出现如下问题:CPU认为是从内存的起始地址开始的,而程序却认为是从程序的起始地址开始的,这样便造成了一种地址上的错位。

(因为程序有多个,所以程序上的地址为相对自己的地址,称为相对地址;而内存只有一个,所以地址是绝对的,称为绝对地址。)

(注:内存上的地址为物理地址(绝对地址),而程序上的地址为逻辑地址(相对地址),问题是由物理地址与逻辑地址之间的错位造成的。)

所以,如果随意的装入程序,CPU可能无法正常运行每一条程序。

下面,提供了三种解决此问题的方案:绝对装入、静态重定位装入、动态重定位装入。

绝对装入

装入前,就确定好程序的装入位置,修改好编译连接后的逻辑地址,使得逻辑地址与物理地址对齐(不错位)。

事先在编译链接后就确定好逻辑地址的开始位置,然后装入后只能装在指定的内存位置。(程序员事先肯定知道要装入到内存的哪个地址)

静态重定位装入

装入时,由装入程序(操作系统的一部分)对逻辑地址进行一次性的修改,从而避免错位。

在程序从磁盘装入到内存的过程中,一边装入,一边修改程序的逻辑地址。此时,装入程序起到了至关重要的作用。(程序员事先不知道要装入到哪里)

重定位:如果我今天装完了一个程序A,它在内存的起始位置为1000,等到今天程序A 运行结束后,明天我又想装入程序A,但此时他只能装在内存地址500的地方,这时,装入程序就会重新定位程序A的地址,将其装入500的位置。

静态:一旦装入后就不能随便在内存中移动。如果我今天装完了一个程序A,它在内存的起始位置为1000,这时,程序A还没有运行结束,我想让程序A向上移动100个地址,这时,就会发生错位,除非将程序A结束后,重新装入才可以。

动态重定位装入

程序运行时,利用重定位寄存器的弥补作用,让CPU以为逻辑地址与物理地址是对齐的(不错位)。

弥补作用(加法):程序从磁盘中原原本本的装入到内存中,然后重定位寄存器读取装入程序在内存中的入口地址(在程序的PCB中读取),将这个入口地址存入重定位寄存器中,然后把程序的地址和程序中的指定地址全部加上重定位寄存器中的数值,此时就起到了一个让CPU以为对齐的效果(实际上是没有对齐的,只是数值上的累加)。

如果此时装入后额的程序要在内存中移动,只需要让重定位寄存器读取程序PCB中的内存入口地址,将其存入到重定位寄存器中,再执行上述操作即可。

内存保护

操作系统必须要在内存中事先内存保护(越界保护)这一功能。在之前,进程间通信中我们知道,由于进程间是相互独立的,它们分配的内存也是相互独立的,因此需要进程间的通信。其实这就是一种进程保护,让各个进程之间的内存独立,不能直接的互相访问。

内存保护是由操作系统(实现更新任务)和相关硬件(实现具体实施任务)共同实现的。

有两种实现内存保护的方法,两者的实现原理是一样的,每次进程切换时,都要进行寄存器的更新(由进程档案袋PCB中的信息进行更新):

上、下限寄存器

​ 在程序读入内存时,事先在上下限寄存器中,给该程序固定一个可访问内存地址的上限地址及下限地 址(程序在进入内存时,都有一个PCB,PCB中存放着该程序的内存地址的范围,寄存器就是由PCB确定的),在该程序访问内存地址之前,首先要CPU读一下上下限寄存器,如果不在上下限范围内,则提示程序越界。

基址寄存器(重定位寄存器)、限长寄存器

​ 重定位寄存器记录了程序在内存中的起始地址,限长寄存器记录了程序在内存中有多长。

(起始位置+长度=终点位置)

扩充内存的方法

覆盖技术

将程序各个子程序设计成层次分明的模块,每设计每一个层次就将用户区分一个区,主程序为固定区,一直在这个区内运行,其他程序为覆盖区,只能在固定的区域运行,每当有一个同层次的程序运行时,会自动覆盖之前运行的程序。

特点:

  • 用在同一个程序(进程)中。
  • 覆盖技术实现了小内存运行大程序,但并不是万能的。
  • 对程序员要求极高。

对换技术

内存中同一时刻会有许多进程,有不同的运行状态,特别是阻塞状态,在内存中及占用位置但是迟迟不运行,在此刻,磁盘中有需要进入内存的进程,因为内存空间的不足进不来,此时就需要用到对换技术。

操作系统首先把进程中阻塞的进程装入磁盘中的“对换区”,再将磁盘中已经准备好的进程换入到内存中。

对换区:在具有对换功能的操作系统中,通常把磁盘分为文件区和兑换区两个部分。文件区是用来存储用户文件的区域,对它的主要目标是提高文件存储空间的利用率,它是采用离散分配的方式。而对换区只占用磁盘空间的小部分,用来存放从内存中换出的进程,对它的主要目标是提高进程换入和换出的速度,它采用连续分配的方式。

特点:

  • 对换通常在内存紧张时发生。
  • 交换技术主要用在不同进程(程序)间。
  • 覆盖技术已经过时了,对换技术仍然存在。
  • 对I/O速度要求非常高。

虚拟内存技术

连续存储分配

单一连续分配(单道程序)

内存中分为两个部分,一个部分为系统区,另一个部分为用户区。

单一:在用户区中,只能装一个程序。

连续:程序中有很多模块,这些模块挨着摆放到用户区的内存。

特点:

  • 单用户单任务
  • 内存的利用率极低(因为有内部碎片):例如:QQ分配到用户区,QQ占了20M,而用户区有30M,此时还剩10M,这10M就形成了内部碎片,没有程序进行使用。(内部碎片:剩余的空间属于这个程序,但是它没有去使用)
  • 通常采用绝对装入方式:事先修改逻辑地址为物理地址。

固定分区分配(多道程序)

第一阶段:用户区由操作系统分为大小相等的几个区域,此时内存的利用率会非常低。

第二阶段:用户区由操作系统根据使用情况分为大小不等的几个区域,提高了内存利用率。

特点:

  • 每个分区只能装入一道程序。(单一连续分配是整个用户区只能装入一道程序)
  • 分区大小很讲究,太小装不进程序,太大内存利用率低。
  • 有内部碎片。
  • 通常采用静态重定位装入方式。

分区说明表:只有设计到多道程序的内存分配才需要,单道程序不需要。操作系统运行时就已经被建立,是被固定的,它是一种操作系统的数据结构,通过查询分区说明表,操作系统就可以知道那一块是空的,以便于给程序分配内存。

  • 分区号:每个分区的编号
  • 区块大小:每个分区在内存中的大小
  • 起始位置:每个分区在内存中的起始位置
  • 状态:是否已经被使用

动态分区分配(多道程序)

解决固定分区分配分区大小很讲究的问题。

实现的方式为:随着程序进入内存进行分区,程序有多大,就分多大。当内存被分配完或内存被分配的最后一个空间不足以装入一个程序时,程序只能等待有适合大小的内存区被其他进程使用完后释放,如果释放的内存区比该程序大,则会产生外部碎片,但是当有其他与这个外部碎片相邻的内存区被释放时,可以进行内存的合并,但这种几率不是很多。

特点:

  • 动态分区——分区说明表:随着进程的进入和退出,是不断的在变化的,这一点与固定分区不同。
  • 内存的合并,减少外部碎片。
  • 紧凑技术:利用动态重定位,将不相邻的外部碎片或空间内存进行合并。
  • 动态分区算法:首次适应算法、最佳适应算法、最坏适应算法、临近适应算法
分配算法
  • 首次适应算法

    按照空闲分区的地址进行排序,地址靠前的排在前面,地址靠后的排在后面。分配时,从头开始依次扫描,直到找到第一个合适的为止,将程序分配给它。

  • 最佳适应算法

    按照空闲分区的大小进行排序,小的排在前面,大的排在后面。分配时,从头开始依次扫描,直到找到第一个合适的为止,将程序分配给它。这样程序总是能够找到满足要求、空闲分区较小的空闲分区分配给程序,避免了大材小用。

  • 最坏适应算法

    与最佳适应算法相反

  • 循环首次适应算法(临近适应算法)

    首次适应算法的概率,从上次分配空闲分区的位置开始查,这样就减少了查找已分配分区的时间,提高了查找的效率。

非连续存储分配

基本分页存储管理

引入分页的原因

在之前对多道程序进行内存分配时的两种方法都会产生碎片:固定分区分配会产生内部碎片,动态分区分配会产生外部碎片,虽然这些产生的碎片会很小,但是积少成多,总和也是一个不小的浪费。

处理这些碎片的方式可以使用之前的“紧凑”的方法,但是会付出很大的开销。

其实含有一种处理方法,就是将要装入的进程分成一页一页的页面,就可以将这些很小的页面转入到与之对应的碎片之中,这样就可以很好地把这些碎片利用起来。

之后,我们也可以想到,既然程序可以切块,那么内存也可以切块。所以我们可以这样做:将程序切成大小相等的若干个页面,并且将内存也切成与程序对应的大小相等的若干个块。

如何分页

通过之前讲解的为什么要分页我们已经知道,分页就是将内存和程序分为大小相等的块。一般是将内存分为每个4KB大小的块,也将程序切为每个4KB大小的块,这样就可以将程序中的某个4KB大小的块装入到内存中4KB大小的块中了。如果程序不能被4KB整除,程序的最后一块必然会小于4KB,这块程序装入到内存时,将会造成内存的浪费,但这个浪费相对于内存大小来说是微不足道的。同样,磁盘也想这样来分块。

这个被切成的块,在各个存储器中叫法不同:

  • 在内存中切的一块叫做一个页框(物理块)
  • 程序中被切的一块叫做页面,页面装在页框中。
  • 磁盘中被切的一块叫做磁盘块

分的块的大小最好为2n
现在我们已经知道,分页就是将程序中的每一个页面分散的装入到内存中的各个页框中,但是我们也产生了一个问题,分散装的时候,必定要有一个记录工具来记录装载的情况,寻找页面装在了哪个页框之中,这个记录工具就是页表。

页表

页面为了方便管理,每一个页面都需要一个编号,这个编号就叫做页号,总页数N=程序的大小/页面的大小,页号从0…N。

同样的,页框(物理块)为了方便管理,也需要为每一个页框进行编号,这个编号就叫做物理块号(页框号),总物理块号M=内存的大小/页框的大小,物理块号从0…M。

而页表中就存放着这两个元素——页号和物理块号,记录着哪个页面放在了哪个页框之中。页表的每一行都记录着自己的哪一个页面被放在哪一个页框中,每一行被称为一个页表项。

因为每个程序都会分成若干个页面,所以每个进程都有一张属于自己的页表。

页表是保存在内存之中的,那操作系统又是怎么知道这个页表在内存的哪个地方呢?这就要依赖于进程的PCB了。操作系统查询A进程的PCB,通过PCB找到A进程的页表存在于内存的什么地方,找到页表之后,就可以通过查询页表知道哪个页面被存到了内存中的哪个页框中。

这个时候又产生了一个问题,我们通过查询页表,只能知道页号和物理块号。我们并不能知道,逻辑地址在页面的哪一行,物理地址在页框中的哪一行。

下面我们就来分析一下,页号、物理块号与逻辑地址、物理地址之间的关系。

物理地址、逻辑地址、页表三者之间的关系

我们先讲一个这样的例子:把1到12分为三组,每一组有四个数字,这三组给三个编号,分别为0、1、2,我们要查找某个数字例如7,我们很容易观察出来,7是在第1组的第3行。

这是我们观察出来的,如果我们用数学计算的方式来说:

  • 十进制:7整除4取整=1 ->这是对应的组号 7求余4=3->这是计算出在哪一行
  • 二进制:7转二进制为0111,将低二位(11)分为一组,高二位(01)分为一组,11十进制为3,01十进制为1,这样就可以直接得出结论,高二位为哪一组,低二位为哪一行。

这样我们就可以类比出来:

  • 十进制的逻辑地址->7
  • 二进制的逻辑地址->0111
  • 页内偏移->低址
  • 页号->高址

现在我们就可以总结一下,在十进制(人类视角)和二进制(机器视角)如何求页号及页面偏移(在哪一行):

  • 十进制:逻辑地址整除页面大小取整为页号,求余为页内偏移
  • 二进制:将逻辑地址转为位数之后,高位为页号,低位(页面大小占的位数)为页内偏移

这里还有几个换算:

  • 已知页号的位数(N到M)和页内偏移(0到N-1)的位数:

    页面大小:2N-1

    逻辑地址:2M

    页号:第一种求法(机器求法):直接看一下N到M位对应二进制转化后的十进制

    ​ 第二种求法(人类求法):[逻辑地址/页面大小]

    页内偏移:第一种求法(机器求法):直接看一下0到N-1位对应二进制转化后的十进制

    ​ 第二种求法(人类求法):逻辑地址%页面大小

现在我们已经知道了如何通过页表查询页号,并且如何找到它的页内偏移,那么应该怎么找到与之对应的物理块号和物理地址呢?

其实,我们将页面装入到页框中时,是直接给原样装入的,所以页内偏移就相当于,页号对应的物理块号的偏移(哪一行),所以现在我们通过页表查到了物理块号,又知道了在哪一行,那么就可以准确的确定物理地址了。

  • 我们现在来总结一下操作系统是如何找到一个用户程序地址空间A在内存中的什么地方的:

  • 首先操作系统读取A对应进程的PCB,得知A的页表在内存的什么地方;

  • 通过分析A的逻辑地址,得知进程A的页号和页面偏移;

  • 通过查询页表得知A的物理块号M,并且通过页内偏移得知A在物理块号M中的偏移;

  • 通过M和物理块号M中的偏移就可以算出物理地址了;

现在我们所有分析的过程都是我们理论的推导,最终还是要在计算机中工程化实现的,这个实现过程的硬件就叫做地址变换机构。

基本地址变换机构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u0hf5yMK-1591763250954)(images/image-20200610111759450.png)]

页表寄存器:在CPU中。

  • 页表起始地址:该进程页表在内存中的起始地址。
  • 页表长度:该进程的页表一共有多少个页表项(有多少页)。

实现过程:

  • 将逻辑地址中的页内偏移量直接复制到物理地址中的物理块偏移量上;
  • 判断页号是否小于页表长度:若否,则产生越界中断;若是,继续执行。
  • 根据“页表起始地址+页号*页表项长度“查询”页号对应在页表中的地址“;
  • 通过该地址就可以找到相应的页表项,找到物理块号。
  • 物理块号与页内偏移量拼接到一起就可以找到物理地址了。

因为CPU中的页表寄存器只有一个,所以我们需要不断地更新页表寄存器,这时候要依赖于进程的PCB来更新它。

几个公式:

  • P = A / L
  • W = A%L
  • 查表位置 = F + P * 页表项长度

一个考点:执行一条指令要访问几次内存?

以一级页表为例:

​ 寻找物理地址 -> 读入CPU内部 -> 执行指令

​ (1次) (1次)

通过页号在页表中查询物理块号(1次)

具有快表的地址变换机构

在这里插入图片描述

快表相当于浏览记录,将之前访问过的逻辑地址的页号和物理块号按时间顺序依次记录在快表之中,但是记录的数量是有限的。

页表是存在于内存中的。快表虽然和页表的结构一样,但是快表是一组CPU内部的寄存器。CPU如果在寄存器中取东西显然会比在内存中取东西要快,所以才称之为快表。

设置快表的目的是减少CPU访问内存的次数,CPU会首先查询快表之中有没有记录,然后才会去查询页表之中的记录,快表里面查到记录就不会到页表里面去查了,这样就减少了访问内存的次数。

在快表中有可能查到记录,也有可能查不到记录。我们把这种在快表中就查到记录的概率称为命中率。

命中率是一个普遍统计后才得到的一个概率,所以一般会在题目中告诉。

如果告诉命中率为50%,求在有这样的快表的情况下,执行10条指令,CPU可能会访问几次内存呢?

  • 我们先计算执行1条指令:如果在快表中找到记录,需要访问1次内存(在CPU执行指令时);如果在快表中找不到,需要到页表中去找,这时候需要访问2次内存(页表1次,执行1次);就可以这样计算:

    (1 * 90%) + (2 * 10%) = 1.1
    
  • 那么执行10次指令就需要访问1.1*10=11次

如果没有快表,需要访问20次内存,有了快表只需要执行11次,这样就可以看出快表的优势了。

二级页表
引入二级页表的原因

我们之前学习一级页表主要是学习页表的工作原理(设计思想),但是一级页表几乎没有实际使用起来!!!

一级页表的一个页表项是由一个页号和一个物理块号组成,在某个页表中假设有N个页表项,我们需要先解决以下几个问题:

  • 一个页表项要有多大?

    页表项的大小我们人工计算肯定不能够精确的计算出来,这时候我们就要估计了,怎么来估计呢?我们使用页表必须要考虑这个页表所占的内存空间,所以我们估计的时候就要估计可以设计页表项的最小值 。一个程序确定了之后,有多少页号就已经确定了,但是物理块号可以不予考虑。所以可以直接考虑页号。

    以下是估计页表项的一个流程:

    一个32位的OS所对应的逻辑地址也是32位,所以程序最大的空间的大小为232B,我们假设页面大小为4KB,那么这时最多会分为220B页,也就意味着有220B页,此时转换为页号位数为20位,20位也就是20bit=2.5B,取整为3B。这时,3B是页号所占的字节,再加上物理块号,一个页表项所占的大小必定会大于3B,这里我们取4B。

在这里插入图片描述

  • 页表项确定后,一个页表要有多大?

    此时,页表的大小(4M) = 页表项的大小(4B)*页数(220B)

可想而知,一个进程的页表就需要4M,那么还会有其他许许多多的进程,它们也有页表,这样累积起来,页表占用内存的空间会非常大,这事就会造成内存浪费。所以我们这时候要想办法解决这个问题。

我们这时候可以回忆一下,之前程序很大的时候,我们为了减少碎片,我们采取了“切块”的方式,分散的装在内存的不同的块中,那么页表也可以这样处理,那么这时也需要一个表来记录,即是“页表的页表”,这样的处理方式的页表被称为“二级页表”。

如何设计二级页表?

我们现在规定一个设计的条件:

32位OS

4KB的页面大小

4B的页表项大小

我们分为四个步骤:

  • 按照最大程序进行切块

    32位的OS最大支持的程序为232B

  • 一级页表切块,块大小 = 页面大小

    程序被切的总块数 = 一级页表项个数 = 最大程序大小/页面大小 = 232B/4KB = 220

    一级页表项个数*一级页表项的大小 = 一级页表的大小 = 220×4B = 4MB

    下面我们也同样按照4KB的大小来切一级页表,这个时候还需要用一个表来记录一级页表的切块情况。

    一级页表被切的个数 = 二级页表项的个数 = 页表的大小/4kB = 210

    二级页表项的大小 = 4B

    二级页表的大小 = 4B*210 = 4KB

  • 当一个页表可以装在一个页面之内时,多级页表设计就结束。

    这时,4KB的页表就可以装在一个4KB的页面中了,设计就结束了。

  • 逻辑地址切块,块位数 = 该级页表每块容纳的页表项。

    在一级页表中,逻辑地址结构低位为页内偏移,高位为页号。

    在二级页表中,分三段,低位为页面偏移,中位为二级页号,高位为一级页号
    在这里插入图片描述

多级页表

同二级页表设计的思路。

虚拟内存管理

请求分页存储管理
引入请求分页的原因

我们通过之前的学习知道,一个程序要运行 = PCB +内存 + CPU ,在之前学习的内存分配方式中,都存在着一次性(程序的所有模块一次性全部复制到内存中)和驻留性(只有当该程序结束后才会退出内存)的特点,这两个特点会导致内存的浪费。

这时,我们需要优化一下这个机制,依据的原理是程序的局部性原理,因为程序中有太多的循环和模块,所以我们可以按照我们的需求,分批装入、分批调出,于是就发明出了请求分页存储管理,这种技术就是虚拟内存技术,让我们看起来就像程序一次性装入一样,但实际上不是这样的。

对虚拟内存技术的理解:

  • 在在请求分页存储管理中,OS只为一个程序分配一个固定大小的内存模块,这个模块的大小是远远小于程序块的大小的,比如用10块内存运行1000块程序,这种以小内存运行大程序的技术,就叫做虚拟内存技术。

对局部性原理的理解:

  • 假如QQ分为登陆界面和主界面,登录界面和主界面分为很多的模块,这些模块中也有很多的代码,这些代码中肯定会有很多的循环语句,所以在一个短时间内,程序就在一个局部的地方运行。
请求分页的工作原理

OS只为一个程序分配一个固定数量的页框,分配的页框数量远远小于程序的页面数量,所以这时程序只将一部分页面装入到页框中(基本分页是全部页面一次性装入),此时会产生一个该程序的请求分页的页表,之中记录着请求分页的信息,置换算法根据这个页表进行页面的置换。

工作集(驻留集)

概念:操作系统给某个进程分配的几个页框所装的页面的集合

我们可以把集合分为两种:

  • 长工:主程序页面->常驻内存的页面
  • 短工:子程序页面->实现某些功能的页面

驻留集大小:操作系统给某个进程分配的页框的大小,分配很讲究,不能过大,也不能过小。分配过大的话,程序没用几次就全部装入内存了,这样就和基本分页差不多了;分配过小的话,置换的过程就会非常频繁。

解决驻留集大小有三种策略:

  • 固定分配局部置换(计划经济策略)

    固定分配:操作系统事先计划好分配给该程序多少块页框,无论之后程序是否还申请,一直都是这么多块。

    局部置换:假如内存只给了程序A10块页框,程序A此时已经把10块页框装满了,这时候第11块页面要装入页框,只能等程序A之前装入的10块页面中的一个结束之后装入。

    非常死板、计划赶不上变化(程序是千变万化的)

  • 可变分配全局置换(低级的市场经济策略)

    随着程序的更新,分配的页框数量也不断增加(不会减少)。

    盲目扩产,产业泡沫。

  • 可变分配局部置换(高级的市场经济策略)

    抽肥补瘦、动态调整、十分灵活。

缺页率:工作集一定时,程序的模块分批次的换入换出内存的频率。缺页率与换入换出的频率成正比关系。

页表改进、缺页中断

在基本分页中,我们知道,页表起到了至关重要的作用。那么在请求分页存储管理中,一个程序都有为之对应的工作集,需要程序的每一块分批次的调入工作集,而基本分页中是程序的所有块一次性调入内存的,所以我们要改进页表。

请求分页页表(每一页调入和调出的时候都需要修改相应的页表信息):

  • 页号:我们假设为0……N。
  • 物理块号:每个页号都有所对应物理块号。但是呢,不是所有的页号都会装在与之对应的物理块号中的,所以这时候我们不得而知,哪一块是装在工作集中的,所以我们就需要用一个“状态位“来记录装载情况。
  • 状态位(有效位):来记录程序的某一页是否已经在内存中,我们假设”1“代表已装入,”0“代表未装入。假如工作集只有10个物理块,这个程序只能有10页装入内存。CPU在执行程序时,会检查状态位的情况,假如运行到程序的第5页,它的状态位为”1“,表示第5页已经装入到工作集,程序继续运行,运行到第6页,它的状态位为”0“,表示第6页没有装入到工作集,还在磁盘中待着,所以这时CPU就会产生缺页中断,就会将其调入到工作集,如果工作集装满了(”1“的个数为工作集中物理块的个数),就需要利用置换算法将其调入工作集。
  • 外存地址(磁盘块号):没有在内存的页面在相应的磁盘块中存放着,当CPU产生中断时,需要将磁盘中的页面调入到工作集中,这时就需要读取外存地址来确定相应页面的位置。
  • 访问字段:记录着程序的每一个页面被调入内存的次数(区别”长工“和”短工“),每当某个页面有效位为”1“时,该页面对应的访问字段就+1,所以访问字段越大,该页面被调入内存的次数就越多。根据局部性原理,被调用内存次数越多,这段程序之后被执行的概率也就越大,当工作集满后,需要调出页面,这就为之提供了一个参考,调出的页面总是访问字段比较小的那一个。
  • 修改位:表示该页调入内存执行后有没有被修改。如果修改位无效,表示没有被修改,则当下次调入内存时,直接调入该页面;如果修改位有效,表示被修改,则执行完后,先从内存调出到磁盘,覆盖该页,当下次调入内存时,调入该页面;
  • 使用位

缺页中断:CPU在执行到某程序的某个页面时,这个页面没有调入内存,此时会产生一个缺页中断,等待该页面调入内存。

页面调度时机
  • 程序刚开始运行

    程序刚开始运行时,要考虑首次要调入内存几页的页面,这时采用的是预调页策略,是采用的局部性原理,由程序员指定的(与main函数相关的几页)。

  • 程序运行中,发生缺页时

    程序运行中,发现某些功能性的子程序,并没有被装入到内存中,这时,CPU就会发生缺页中断,将工作集中已经不需要的页面调出到相应的磁盘块中,将这个功能性子程序的相关页面装入到工作集。这个功能性的子程序是被一页一页的调入到工作集中的,这样就使用到了请求调页策略(页面置换算法)。

    当CPU发生缺页中断时,就会执行中断处理程序(OS的一部分),中断处理程序就会调用页面置换算法。

页面置换算法
  • OPT算法(最佳置换算法)

    算法思想:淘汰之后永不访问或将来最长时间内不再访问的页面。

    算法特点:不能预知未来,因此该算法不能实现。

    作用:OPT算法是最优的(缺页中断次数和页面置换次数达到最小值),所以拿它做一个标准,用来评价其他算法。

    (其实就是把在该页之后,离该页最远的替换掉)

    例:工作集分配3个物理块

    ​ CPU执行页面的顺序:

    ​ 7 0 1 2 0 3 0 4 2 3 0 3 2 1 2 0 1 7 0 1

    ​ 物理块1: 7 7 7 2 2 2 2 2 2 2 2 2 2 2 2 2 2 7 7 7

    ​ 物理块2: 0 0 0 0 0 0 4 4 4 0 0 0 0 0 0 0 0 0 0

    ​ 物理块3: 1 1 1 3 3 3 3 3 3 3 3 1 1 1 1 1 1 1

    ​ 缺 页: √ √ √ √ √ √ √ √ √

    ​ 页面置换: √ √ √ √ √ √

    可以看出:缺页次数为9次,页面置换次数为6次,由于最佳置换算法是一个最优的算法,得到的缺页次数和页面置换次数相比于其他置换算法是最少的,所以其他置换算法肯定都大于9和6。

    也就是说,缺页次数 = 页面置换次数 + 分给该程序的物理块数(工作集容量)

  • FIFO算法(先进先出算法)

    算法思想:淘汰先调入内存的页面。(队列实现:先进先出)

    算法特点:简单、性能差(主程序有可能在此过程中被调出),且有Belady异常(随着给该程序物理块的增多可能会出现缺页率上升的情况<正常情况下是分的物理块也多,缺页率越少>)。

    (其实就是把该页之前,把出现最多的连续页数给替换掉)

    例:工作集分配3个物理块

    ​ CPU执行页面的顺序:

    ​ 7 0 1 2 0 3 0 4 2 3 0 3 2 1 2 0 1 7 0 1

    ​ 物理块1: 7 7 7 2 2 2 2 4 4 4 0 0 0 0 0 0 0 7 7 7

    ​ 物理块2: 0 0 0 0 3 3 3 2 2 2 2 2 1 1 1 1 1 0 0

    ​ 物理块3: 1 1 1 1 0 0 0 3 3 3 3 3 2 2 2 2 2 1

    ​ 缺 页: √ √ √ √ √ √ √ √ √ √ √ √ √ √ √

    ​ 页面置换: √ √ √ √ √ √ √ √ √ √ √ √

    可以看出:缺页次数为15次,页面置换次数为12次。

  • LRU算法(最近最久未使用算法)

    算法思想:淘汰最近最久未使用的页面。(用过去预测未来,用历史数据来说明)

    算法特点:性能优异,接近最佳置换算法,需要硬件栈支持,开销极大。

    (其实就是把在该页之前,离该页面最远的替换掉)

    例:工作集分配3个物理块

    ​ CPU执行页面的顺序:

    ​ 7 0 1 2 0 3 0 4 2 3 0 3 2 1 2 0 1 7 0 1

    ​ 物理块1: 7 7 7 2 2 2 2 4 4 4 0 0 0 1 1 1 1 1 1 1

    ​ 物理块2: 0 0 0 0 0 0 0 0 3 3 3 3 3 3 0 0 0 0 0

    ​ 物理块3: 1 1 1 3 3 3 2 2 2 2 2 2 2 2 2 7 7 7

    ​ 缺 页: √ √ √ √ √ √ √ √ √ √ √ √

    ​ 页面置换: √ √ √ √ √ √ √ √ √

    可以看出:缺页次数为12次,页面置换次数为9次。

  • CLOCK算法(NRU算法)

    算法思想:通过钟表扫描法,淘汰最近未使用的页面。(页表增加一个使用位<是否被曾经被CPU执行过>)(认为过去未被使用,将来也不会被使用,采用的是过去影响未来的策略)

    使用位

    • 首次装入(第一次从磁盘中被调入到内存中)置“1”
    • 再次访问置“1”
    • 扫描时将“1”变为“0”

    算法特点:由于最近未使用和最近最久未使用的思想接近,因此CLOCK和LRU算法性能很接近。但是CLOCK算法的性价比更高,因为LRU算法需要一个硬件栈的支持,而CLOCK算法不需要。

    例:工作集分配3个物理块

    ​ CPU执行页面的顺序:

    ​ 7 0 1 2 0 3 0 4 2 3 0 3 2 1 2 0 1 7 0 1

    ​ 物理块1: 7 7 7 2 2 2 2 4 2 2 2 2 2 1 1 0 0 7 7 7

    ​ 物理块2: 0 0 0 0 0 0 0 0 0 0 0 0 0 2 2 2 2 0 0

    ​ 物理块3: 1 1 1 3 3 3 3 3 3 3 3 3 3 3 1 1 1 1

    ​ 缺 页: √ √ √ √ √ √ √ √ √ √ √ √ √

    ​ 页面置换: √ √ √ √ √ √ √ √ √ √

    可以看出:缺页次数为13次,页面置换次数为10次。

在这里插入图片描述

  • 改进型CLOCK算法

    算法思想:通过钟表扫描法,优先淘汰最近未使用页面中的未修改的页面(u,m)

    算法特点:相比CLOCK算法,减少了页面回写磁盘的概率,从而节省了回写时间(I/O时间)

    使用位:

    • (0,0)未使用未修改
    • (0,1)未使用已修改
    • (1,0)已使用未修改
    • (1,1)已使用已修改

    算法步骤

    • 按照钟表扫描法,寻找(0,0)用于替换,找不到进行下一步。(只查不修改)
    • 重新执行钟表扫描法,寻找(0,1)用于替换。在扫描中,将(1,0)改为(0,0),(1,1)改为(0,1),若找不到则回到第一步。(边查边修改,只对使用位进行修改)
从何处调入页面,调出的页面放在哪里?

磁盘必须要有一块对换区(swap),临时存放程序的副本。

程序按照是否可以修改分为可修改程序(含有变量的那一块程序)和不可修改程序(无变量)。可修改程序被调入内存之后可以被修改,也可以不修改。

我们分为下面三种情况来运行程序:

  • 对换区足够大

    在程序进入内存之前,事先拷贝程序的所有模块到对换区中,保留一个程序的副本,当程序要装入内存时,从这个副本中调入程序进入内存,同样,如果程序在内存中被修改了,则将修改后的程序重写到对换区中,而不是程序原本的位置,这样就保证了原程序不会被修改的程序所覆盖了。

  • 对换区不够大

    只将程序中可修改的部分拷贝到对换区中,不可修改部分仍在原来的位置保留着。当程序进入内存时,可修改程序从对换区中进入,不可修改程序从原来的磁盘位置进入;那些不可修改的程序由于一直没有被修改,所以就不必被重写到磁盘,可修改程序被重写后调入到对换区中。

  • unix方式

    首次调入内存时,从文件区中调入,调出时,调出到对换区中,之后调入时,先检查对换区中有没有程序段,有的话直接从对换区调入,没有再从文件区调入。

未完待续。。。。。。。。。。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Jackson Xi

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

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

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

打赏作者

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

抵扣说明:

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

余额充值