第 3 章 内存管理

第 3 章 内存管理

1、内存的基础知识

1.1、思维导图

image-20210105203112644

1.2、什么是内存?有何作用?

内存的作用

内存可存放数据。 程序执行前需要先放到内存中才能被CPU处理——缓和CPU与硬盘之间的速度矛盾

内存的编址

思考: 在多道程序环境下, 系统中会有多个程序并发执行, 也就是说会有多个程序的数据需要同时放到内存中。 那么, 如何区分各个程序的数据是放在什么地方的呢?

方案: 给内存的存储单元编地址,一般都是按照字节进行编址

image-20210105203328288

1.3、几个常用的数量单位

常用的存储计量单位

210 = 1K (千)
220 = 1M (兆, 百万)
230 = 1G (十亿, 千兆)

举个栗子

一台手机/电脑 有 4GB 内存, 是什么意思?

答:是指该内存中可以存放 4*230个字节。 如果是按字节编址的话, 也就是有 4*230 = 232个“小房间” 。这么多“小房间” , 需要 232 个地址才能一一标识, 所以地址需要用 32 个二进制位来表示(0~ 232 -1)

1.4、知识滚雪球: 指令的工作原理

1、程序的编译

假设使用 C 语言编写的 x = x + 1 语句编译后生成如下的机器语言

image-20210105203620995

2、程序的执行

  1. 运行中的进程在内存中有三个区域:PCB(记录进程状态)、 程序段(存放指令)、数据段(存放数据)

    image-20210105203749397

  2. 执行第一句数据传送指令:将变量 x 的值(内存地址为 79)赋值给寄存器(地址为 3 )

    image-20210105203754674

  3. 执行第二句加法指令:在寄存器中执行累加操作

    image-20210105203800462

  4. 执行第二句数据传送指令:将寄存器(地址为 3 )的值赋值给地址为 79 的内存空间

    image-20210105203804917

3、关于内存地址的思考

指令的工作基于“地址” 。每个地址对应一个数据的存储单元。可见, 我们写的代码要翻译成CPU能识别的指令。 这些指令会告诉CPU应该去内存的哪个地址读/写数据,这个数据应该做什么样的处理。

在这个例子中, 我们默认让这个进程的相关内容从地址#0开始连续存放, 指令中的地址参数直接给出了变量 x 的实际存放地址(物理地址) 。

思考: 如果这个进程不是从地址#0 开始存放的, 会影响指令的正常执行吗?

4、逻辑地址与物理地址

程序经过编译、 链接后生成的指令中指明的是逻辑地址(相对地址) , 即: 相对于进程的起始地址而言的地址。而在实际执行指令的时候,我们需要给 CPU 明确的物理地址(绝对地址),这样 CPU 才知道应该去哪个内存单元操作数据

Tip: 为了简化理解, 本节中我们默认操作系统给进程分配的是一片连续的内存空间

在下面这个例子中,进程相关内容的起始地址为 #0,因此逻辑地址与物理地址相同

image-20210105210451583

5、物理地址与逻辑地址不对应咋办?

在下面的例子中进程相关内容的起始地址为 #100,因此逻辑地址与物理地址相差 100,那么如果按照逻辑地址进行操作,则可能会误操作其他进程的内存空间

image-20210105205147723

问题: 如何将指令中的逻辑地址转换为物理地址? 答:三种装入方式

  1. 绝对装入
  2. 可重定位装入(静态重定位)
  3. 动态运行时装入(动态重定位)

1.5、装入的三种方式

1.5.1、绝对装入

1、绝对装入的做法

绝对装入: 在编译时, 如果知道程序将放到内存中的哪个位置, 编译程序将产生绝对地址的目标代码。装入程序按照装入模块中的地址, 将程序和数据装入内存。

Eg: 如果知道装入模块要从地址为 100 的地方开始存放,那么就将逻辑地址都加 100 即可得到物理地址

image-20210105210426783

2、绝对装入的特点

绝对装入只适用于单道程序环境。

程序中使用的绝对地址, 可在编译或汇编时给出, 也可由程序员直接赋予。 通常情况下都是编译或汇编时再转换为绝对地址。

1.5.2、可重定位装入

1、可重定位装入的做法

静态重定位: 又称可重定位装入。 编译、 链接后的装入模块的地址都是从0开始的, 指令中使用的地址、 数据存放的地址都是相对于起始地址而言的逻辑地址。 可根据内存的当前情况, 将装入模块装入到内存的适当位置。 装入时对地址进行“重定位” , 将逻辑地址变换为物理地址(地址变换是在装入时一次完成的) 。

Eg:进程装入的起始物理地址为100, 则所有地址相关的参数都+100

image-20210105210808068

2、可重定位装入的特点

静态重定位的特点是在一个作业装入内存时, 必须分配其要求的全部内存空间, 如果没有足够的内存, 就不能装入该作业。作业一旦进入内存后, 在运行期间就不能再移动, 也不能再申请内存空间。

1.5.3、动态运行时装入

1、动态运行时装入的做法

动态重定位: 又称动态运行时装入。 编译、 链接后的装入模块的地址都是从0开始的。 装入程序把装入模块装入内存后, 并不会立即把逻辑地址转换为物理地址, 而是把地址转换推迟到程序真正要执行时才进行。 因此装入内存后所有的地址依然是逻辑地址。 这种方式需要一个重定位寄存器的支持。

image-20210105211000911

在程序运行的时候,将逻辑地址 + 重定位寄存器,得到的结果即为变量对应的实际物理地址

image-20210105211222643

2、动态运行时装入的特点

采用动态重定位时允许程序在内存中发生移动(可将程序分配到不连续的存储区中; 在程序运行前只需装入它的部分代码即可投入运行, 然后在程序运行期间, 根据需要动态申请分配内存; 便于程序段的共享, 可以向用户提供一个比存储空间大得多的地址空间。 )

1.6、从写程序到程序运行

经历的几个步骤

  1. 源文件:程序员编写源代码文件,比如 *.c 文件
  2. 编译:每个源文件经编译生成目标模块,比如 *.o 文件,每个目标文件的逻辑地址相互独立,起始地址均为 0
  3. 链接:将各个目标模块链接成装入模块,链接后便形成了完成的逻辑地址,比如 *.exe 文件
  4. 装入:由装入程序将装入模块装入内存运行

image-20210105211752454


编译: 由编译程序将用户源代码编译成若干个目标模块(编译就是把高级语言翻译为机器语言)

链接: 由链接程序将编译后形成的一组目标模块, 以及所需库函数链接在一起, 形成一个完整的装入模块装入(装载) : 由装入程序将装入模块装入内存运行

1.7、链接的三种方式

1、静态链接

静态链接: 在程序运行之前,先将各目标模块及它们所需的库函数连接成一个完整的可执行文件(装入模块) ,之后不再拆开。

image-20210105212739091

2、装入时动态链接

装入时动态链接: 将各目标模块装入内存时, 边装入边链接的链接方式。

image-20210105212809423

3、运行时动态链接

运行时动态链接: 在程序执行中需要该目标模块时, 才对它进行链接。 其优点是便于修改和更新, 便于实现对目标模块的共享。

image-20210105212949208

1.8、本节课小结

image-20210105211649919

2、内存管理的概念

2.1、思维导图

image-20210105213030677

2.2、内存管理四件事

操作系统作为系统资源的管理者, 当然也需要对内存进行管理, 要管些什么呢?

1、内存空间的分配与回收

操作系统要怎么记录哪些内存区域已经被分配出去了,哪些又还空闲?

当进程运行结束之后, 如何将进程占用的内存空间回收?

当一个进程需要运行时,给它分配哪一片内存空间呢?

综上:操作系统负责内存空间的分配与回收

2、内存空间的扩展

游戏 GTA 的大小超过 60GB, 按理来说这个游戏程序运行之前需要把 60GB数据全部放入内存。然而, 实际我的电脑内存才 4GB, 但为什么这个游戏可以顺利运行呢?

答:虚拟技术(操作系统的虚拟性)

综上:操作系统需要提供某种技术从逻辑上对内存空间进行扩充

3、地址转换

为了使编程更方便, 程序员写程序时应该只需要关注指令、 数据的逻辑地址。 而逻辑地址到物理地址的转换(这个过程称为地址重定位) 应该由操作系统负责, 这样就保证了程序员写程序时不需要关注物理内存的实际情况。

三种装入方式

image-20210105215612361

综上:操作系统需要提供地址转换功能, 负责程序的逻辑地址与物理地址的转换

4、内存保护

一个进程不能访问操作系统的内存空间,也不能访问其他进程的内存空间

image-20210105215826251

综上:操作系统需要提供内存保护功能。 保证各进程在各自存储空间内运行, 互不干扰


内存保护的两种实现方式

  1. 方法一: 在CPU中设置一对上、 下限寄存器, 存放进程的上、 下限地址。 进程的指令要访问某个地址时, CPU检查是否越界。

    image-20210105220016454

  2. 方法二: 采用重定位寄存器(又称基址寄存器) 和界地址寄存器(又称限长寄存器) 进行越界检查。 重定位寄存器中存放的是进程的起始物理地址。 界地址寄存器中存放的是进程的最大逻辑地址。

    image-20210105220040584

2.3、本节课小结

image-20210105215321392

3、覆盖与交换

3.1、思维导图

image-20210105220515321

3.2、覆盖技术

1、覆盖技术出现的背景

早期的计算机内存很小, 比如 IBM 推出的第一台PC机最大只支持 1MB 大小的内存。 因此经常会出现内存大小不够的情况。 后来人们引入了覆盖技术, 用来解决“程序大小超过物理内存总和” 的问题

image-20210105222504054

2、覆盖技术的思想

覆盖技术的思想: 将程序分为多个段(多个模块) 。常用的段常驻内存, 不常用的段在需要时调入内存。

内存中分为一个“固定区” 和若干个“覆盖区” 。需要常驻内存的段放在“固定区” 中, 调入后就不再调出(除非运行结束)不常用的段放在“覆盖区” , 需要用到时调入内存,用不到时调出内存

3、覆盖技术的举例

举个栗子:main() 函数中依次调用 B 和 C(不会同时调用),B 中会调用 D,C 中会依次调用 E 和 F(不会同时调用)

按照自身逻辑结构, 让那些不可能同时被访问的程序段共享同一个覆盖区

image-20210105222554617

4、覆盖技术的缺点

必须由程序员声明覆盖结构, 操作系统完成自动覆盖。 缺点: 对用户不透明, 增加了用户编程负担。覆盖技术只用于早期的操作系统中, 现在已成为历史。

3.3、交换技术

交换技术的思想

交换(对换) 技术的设计思想: 内存空间紧张时, 系统将内存中某些进程暂时换出外存, 把外存中某些已具备运行条件的进程换入内存(进程在内存与磁盘间动态调度)


  1. 当内存空间足够时,进程可常驻内存中

    image-20210105223420897

  2. 当内存空间不够时,会将某些进程暂时换出外存,并将这些进程的 PCB 排在挂起队列中(PCB 中会记录这些进程调出外存时,存在外存的什么位置)

    image-20210105223425393

交换技术与进程七状态模型

交换(对换) 技术的设计思想: 内存空间紧张时, 系统将内存中某些进程暂时换出外存, 把外存中某些已具备运行条件的进程换入内存(进程在内存与磁盘间动态调度)

暂时换出外存等待的进程状态为挂起状态(挂起态, suspend),挂起态又可以进一步细分为就绪挂起、 阻塞挂起两种状态

image-20210105223220029

交换技术需要解决的问题

交换技术面临的问题:

  1. 应该在外存(磁盘) 的什么位置保存被换出的进程?
  2. 什么时候应该交换?
  3. 应该换出哪些进程?

以上问题的解决办法:

  1. 具有对换功能的操作系统中, 通常把磁盘空间分为文件区和对换区两部分。 文件区主要用于存放文件, 主要追求存储空间的利用率, 因此对文件区空间的管理采用离散分配方式; 对换区空间只占磁盘空间的小部分, 被换出的进程数据就存放在对换区。 由于对换的速度直接影响到系统的整体速度, 因此对换区空间的管理主要追求换入换出速度, 因此通常对换区采用连续分配方式(学过文件管理章节后即可理解) 。 总之, 对换区的I/O速度比文件区的更快。
  2. 交换通常在许多进程运行且内存吃紧时进行, 而系统负荷降低就暂停。 例如: 在发现许多进程运行时经常发生缺页, 就说明内存紧张, 此时可以换出一些进程;如果缺页率明显下降, 就可以暂停换出。
  3. 可优先换出阻塞进程; 可换出优先级低的进程; 为了防止优先级低的进程在被调入内存后很快又被换出, 有的系统还会考虑进程在内存的驻留时间…

image-20210105223826102


注意:

PCB 会常驻内存, 不会被换出外存

3.4、本节课小结

image-20210105223843912

4、连续分配管理方式

4.1、思维导图

连续分配: 指为用户进程分配的必须是一个连续的内存空间。

image-20210107192510109

4.2、单一连续分配

单一连续分配的做法

在单一连续分配方式中,内存被分为系统区和用户区。系统区通常位于内存的低地址部分,用于存放操作系统相关数据;用户区用于存放用户进程相关数据。

内存中只能有一道用户程序,用户程序独占整个用户区空间。

image-20210107200619179

单一连续分配的优缺点

优点:实现简单;无外部碎片;可以采用覆盖技术扩充内存;不一定需要采取内存保护(eg:早期的 PC 操作系统 MS-DOS)。

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


内部碎片:分配给某进程的内存区域中,如果有些部分没有用上,就是“内部碎片”

4.3、固定分区分配

固定分区分配的做法

20世纪60年代出现了支持多道程序的系统,为了能在内存中装入多道程序,且这些程序之间又不会相互干扰,于是将整个用户空间划分为若干个固定大小的分区,在每个分区中只装入一道作业,这样就形成了最早的、最简单的一种可运行多道程序的内存管理方式。

固定分区分配的分类

按照分区大小是否相同进行分类

image-20210107202600936

分区大小相等:缺乏灵活性,但是很适合用于用一台计算机控制多个相同对象的场合(比如:钢铁厂有n个相同的炼钢炉,就可把内存分为n个大小相等的区域存放n个炼钢炉控制程序)

分区大小不等:增加了灵活性,可以满足不同大小的进程需求。根据常在系统中运行的作业大小情况进行划分(比如:划分多个小分区、适量中等分区、少量大分区)

image-20210107202628281

分区表的维护

操作系统需要建立一个数据结构——分区说明表,来实现各个分区的分配与回收。每个表项对应一个分区,通常按分区大小排列。每个表项包括对应分区的大小、起始地址、状态(是否已分配)。

用数据结构的数组(或链表)即可表示下面这个表。

image-20210107202728486

如何寻找空闲分区?

当某用户程序要装入内存时,由操作系统内核程序根据用户程序大小检索该表,从中找到一个能满足大小的、未分配的分区,将之分配给该程序,然后修改状态为“已分配”。

固定分区分配的优缺点

优点:实现简单,无外部碎片。

缺点:a. 当用户程序太大时,可能所有的分区都不能满足需求,此时不得不采用覆盖技术来解决,但这又会降低性能;b. 会产生内部碎片,内存利用率低。

4.4、动态分区分配

动态分区分配的做法

动态分区分配又称为可变分区分配。这种分配方式不会预先划分内存分区,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。因此系统分区的大小和数目是可变的。(eg:假设某计算机内存大小为64MB,系统区8MB,用户区共56 MB…)


动态分配需要解决的问题

  1. 系统要用什么样的数据结构记录内存的使用情况?
  2. 当很多个空闲分区都能满足需求时,应该选择哪个分区进行分配?
  3. 如何进行分区的分配与回收操作?

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

空闲分区表的表示方法

image-20210107203735592


举个栗子:内存的使用情况

image-20210107204151632

空闲分区表:每个空闲分区对应一个表项。表项中包含分区号、分区大小、分区起始地址等信息

image-20210107204044552

空闲分区链:每个分区的起始部分和末尾部分分别设置前向指针和后向指针。起始部分处还可记录分区大小等信息

image-20210107204121040

2、当很多个空闲分区都能满足需求时,应该选择哪个分区进行分配?

需要解决的问题

当进程5(4MB)到来时,应该用最大的分区进行分配?还是用最小的分区进行分配?又或是用地址最低的部分进行分配?

image-20210107204310497


动态分区分配算法

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

下个小节会介绍四种动态分区分配算法…

3、如何进行分区的分配与回收操作?(以空闲分区表为例)

1)、内存的分配

情况一:空闲区域比进程需占用的内存大

内存初始情况

image-20210107205130723

进程 5(4MB)分配在 20MB 空闲内存区域内,还剩 16MB

image-20210107205211337

空闲分区表的更新

image-20210107205320476

情况二:空闲区域刚好等于进程需占用的内存

内存初始情况

image-20210107205444463

这次将进程 5(4MB)分配在最下面的 4MB 空闲区域内

image-20210107205539255

导致空闲分区表的个数发生改变

image-20210107205601701


2)、内存的回收

情况一:回收区的后面有一个相邻的空闲分区

进程 4(4MB)待回收

image-20210107205802945

将进程 4(4MB)回收后,加上后面的 10MB 形成 14MB 的空闲空间

image-20210107205844846

空闲分区表的变换:两个相邻的空闲分区合并为一个

image-20210107210035031

情况发二:回收区的前面有一个相邻的空闲分区

进程 3(18MB)待回收

image-20210107210246837

将进程 3(18MB)回收后,加上前面的 10MB 形成 28MB 的空闲空间

image-20210107210251558

空闲分区表的变换:两个相邻的空闲分区合并为一个

image-20210107210927766

情况三:回收区的前、后各有一个相邻的空闲分区

进程 4(4MB)待回收

image-20210107210259239

将进程 4(4MB)回收后,加上前面的 20MB 和后面的 10MB 形成 34MB 的空闲空间

image-20210107210302327

空闲分区表的变换:三个相邻的空闲分区合并为一个

image-20210107210912501

情况四:回收区的前、后都没有相邻的空闲分区

进程 2(14MB)待回收

image-20210107210306557

将进程 2(14MB)回收后,形成 14MB 的空闲空间

image-20210107210310016

空闲分区表的变换:新增一个表项

image-20210107210951504


:各表项的顺序不一定按照地址递增顺序排列,具体的排列方式需要依据动态分区分配算法来确定。

动态分区分配的优劣势

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

内部碎片,分配给某进程的内存区域中,如果有些部分没有用上,则称为内部碎片。

外部碎片,是指内存中的某些空闲分区由于太小而难以利用,则称为外部碎片。


外部碎片的解决办法

如果内存中空闲空间的总和本来可以满足某进程的要求,但由于进程需要的是一整块连续的内存空间,因此这些“碎片”不能满足进程的需求。可以通过紧凑(拼凑,Compaction)技术来解决外部碎片。


思考:动态分区分配应使用哪种装入方式?“紧凑”之后需要做什么处理?

4.5、本节课小结

注意事项

连续分配:为用户进程分配的必须是一个连续的内存空间

另外,需要对用于管理空闲分区的数据结构有印象——空闲分区表、空闲分区链

回收内存时遇到相邻的空闲分区要合并

image-20210107211455535

5、动态分区分配算法

5.1、思维导图

动态分区分配算法要做爪子事?

动态分区分配算法:在动态分区分配方式中, 当很多个空闲分区都能满足需求时,应该选择哪个分区进行分配?

image-20210107211150323

5.2、首次适应算法

首次适应算法的思想和实现

算法思想:每次都从低地址开始查找,找到第一个能满足大小的空闲分区。

如何实现:空闲分区以地址递增的次序排列。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。

举个栗子

内存初始情况

image-20210107215749774

空闲分区表和空闲分区链的初始情况

image-20210107215815716

进程 5(15MB)被分配到 20MB 空闲空间内,分配后还剩余 5MB 空闲空间

image-20210107215833138

更新空闲分区链

image-20210107220018593

5.3、最佳适应算法

最佳适应算法的思想和实现

算法思想:由于动态分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片区域。因此为了保证当“大进程”到来时能有连续的大片空间,可以尽可能多地留下大片的空闲区,即,优先使用更小的空闲区。

如何实现:空闲分区按容量递增次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。

举个栗子

内存初始情况

image-20210107220213537

空闲分区表和空闲分区链的初始情况

image-20210107220217027

进程 6(9MB)被分配到 10MB 空闲空间内,分配后还剩余 1MB 空闲空间

image-20210107220224674

更新空闲分区链:需要进行重新排序

image-20210107220301292

最佳适应算法的缺点

缺点:每次都选最小的分区进行分配,会留下越来越多的、很小的、难以利用的内存块。因此这种方法会产生很多的外部碎片。

5.4、最坏适应算法

又称最大适应算法(Largest Fit)

最坏适应算法的思想和实现

算法思想:为了解决最佳适应算法的问题——即留下太多难以利用的小碎片,可以在每次分配时优先使用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用。

如何实现:空闲分区按容量递减次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。

举个栗子

内存初始情况

image-20210107220650076

空闲分区表和空闲分区链的初始情况

image-20210107220654203

进程 5(3MB)被分配到 20MB 空闲空间内,分配后还剩余 17MB 空闲空间

image-20210107220703275

更新空闲分区链

image-20210107220729953

进程 6(9MB)被分配到 17MB 空闲空间内,分配后还剩余 8MB 空闲空间

image-20210107220735314

更新空闲分区链:需要进行重新排序

image-20210107220743066

最坏适应算法的缺点

缺点:每次都选最大的分区进行分配,虽然可以让分配后留下的空闲区更大,更可用,但是这种方式会导致较大的连续空闲区被迅速用完。如果之后有“大进程”到达,就没有内存分区可用了。

5.5、邻近适应算法

邻近适应算法的思想和实现

算法思想:首次适应算法每次都从链头开始查找的。这可能会导致低地址部分出现很多小的空闲分区,而每次分配查找时,都要经过这些分区,因此也增加了查找的开销。如果每次都从上次查找结束的位置开始检索,就能解决上述问题。

如何实现:空闲分区以地址递增的顺序排列(可排成一个循环链表)。每次分配内存时从上次查找结束的位置开始查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。

举个栗子

内存初始情况

image-20210107220905287

空闲分区链的初始情况

image-20210107220909587

进程 5(5MB)被分配到 6MB 空闲空间内,分配后还剩余 1MB 空闲空间

image-20210107220937202

更新空闲分区链

image-20210107220940698

进程 6(5MB)被分配到 10MB 空闲空间内,分配后还剩余 5MB 空闲空间

image-20210107220945411

更新空闲分区链

image-20210107220949522

邻近适应算法的缺点

首次适应算法每次都要从头查找,每次都需要检索低地址的小分区。但是这种规则也决定了当低地址部分有更小的分区可以满足需求时,会更有可能用到低地址部分的小分区,也会更有可能把高地址部分的大分区保留下来(最佳适应算法的优点)

邻近适应算法的规则可能会导致无论低地址、高地址部分的空闲分区都有相同的概率被使用,也就导致了高地址部分的大分区更可能被使用,划分为小分区,最后导致无大分区可用(最大适应算法的缺点)综合来看,四种算法中,首次适应算法的效果反而更好

5.6、本节课小结

四种动态分区分配算法的对比

image-20210107221056304

6、基本分页存储管理的基本概念

6.1、思维导图

连续分配 & 非连续分配

连续分配:为用户进程分配的必须是一个连续的内存空间。

非连续分配:为用户进程分配的可以是一些分散的内存空间。

image-20210107221248826

6.2、什么是“地址空间”?

概念回顾:逻辑地址(相对地址) & 物理地址(绝对地址)

进程内部的逻辑地址映射到物理地址需要加上一个起始偏移量

image-20210119225951848

6.3、什么是分页存储?

分页存储的概念

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

将进程的逻辑地址空间也分为与页框大小相等的一个个部分,每个部分称为一个“页”或“页面” 。每个页面也有一个编号,即“页号”,页号也是从0开始。

image-20210119230143779

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

image-20210119230237987

注意事项

初学易混的专业名词:页、页面 vs 页框、页帧、物理页页号、页面号 vs 页框号、页帧号、物理页号

进程的最后一个页面可能没有一个页框那么大。也就是说,分页存储有可能产生内部碎片,因此页框不能太大,否则可能产生过大的内部碎片造成浪费

6.4、重要的数据结构——页表

页表的作用

为了能知道进程的每个页面在内存中存放的位置,操作系统要为每个进程建立一张页表。

  1. 一个进程对应一张页表
  2. 进程的每个页面对应一个页表项
  3. 每个页表项由“页号”和“块号”组成
  4. 页表记录进程页面和实际存放的内存块之间的映射关系
  5. 每个页表项的长度是相同的

image-20210119230552700


:页表通常存在PCB(进程控制块)中

使用页表需要解决的两个问题

  1. 每个页表项多大?占几个字节?
  2. 如何通过页表实现逻辑地址到物理地址的转换?

6.5、问题一:每个页表项占多少字节?

计算块号的大小

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

计算块号大小:

  1. 内存块大小=页面大小=4KB= 212 B
  2. 4GB 的内存总共会被分为232 / 212 = 220 个内存块
  3. 内存块号的范围应该是0 ~ 220 -1
  4. 内存块号至少要用20 bit 来表示
  5. 至少要用3B来表示块号(3*8=24bit)

image-20210119231308862


重要重要重要考点:计算机中内存块的数量 → 页表项中块号至少占多少字节

计算页号的大小

:那页号又要占多少字节呢?

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


假设页表中的各页表项从内存地址为X 的地方开始连续存放,如何找到页号为 i 的页表项?

  1. i 号页表项的存放地址= X + 3*i
  2. 因此,页表中的页号可以是隐含的,即页号不占用存储空间

image-20210119231712958

计算页表项的大小

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

计算页表项大小:

  1. 内存块大小=页面大小=4KB= 212 B
  2. 4GB 的内存总共会被分为232 / 212 = 220 个内存块
  3. 内存块号的范围应该是0 ~ 220 -1
  4. 内存块号至少要用20 bit 来表示
  5. 至少要用3B来表示块号(3*8=24bit)
  6. 由于页号是隐含的,因此每个页表项占3B,存储整个页表至少需要3*(n+1)B

image-20210119232054372


注意:页表记录的只是内存块号,而不是内存块的起始地址!J 号内存块的起始地址= J * 内存块大小

6.6、问题二:如何实现地址的转换?

重定位寄存器的作用

:进程在内存中连续存放时,操作系统是如何实现逻辑地址到物理地址的转换的?

:重定位寄存器:指明了进程在内存中的起始位置

image-20210119232312677

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

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


如果要访问逻辑地址A,则

①确定逻辑地址A 对应的“页号”P

②找到P号页面在内存中的起始地址(需要查页表)

③确定逻辑地址A 的“页内偏移量”W

则:逻辑地址A 对应的物理地址= P号页面在内存中的起始地址+页内偏移量W

image-20210119232531732

子问题:如何确定一个逻辑地址对应的页号、页内偏移量?

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

如何计算:

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

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

带入题目条件:

页号= 110 / 50 = 2

页内偏移量= 110 % 50 = 10


页号 & 页内偏移量

逻辑地址可以拆分为(页号,页内偏移量)

通过页号查询页表,可知页面在内存中的起始地址

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

image-20210119232623527

子问题:利用二进制的特性拆分页号和页偏移量

公式列在这里:

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

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


举个栗子

image-20210119232916324


结论

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

如果每个页面大小为 2KB,用二进制数表示逻辑地址,则末尾 K 位即为页内偏移量,其余部分就是页号

子问题:如何利用二进制的特性快速计算实际物理地址?

公式列在这里:

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

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


举个栗子

image-20210119233144018


结论

根据页号可以查询页表,而页表中记录的只是内存块号,而不是内存块的起始地址!J 号内存块的起始地址= J * 内存块大小

如果页面大小刚好是2的整数幂,则只需把页表中记录的物理块号拼接上页内偏移量就能得到对应的物理地址

子问题:为何页面大小要取2的整数幂?

公式列在这里:

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

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


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

①逻辑地址的拆分更加迅速——如果每个页面大小为2KB,用二进制数表示逻辑地址,则末尾K 位即为页内偏移量,其余部分就是页号。因此,如果让每个页面的大小为2 的整数幂,计算机硬件就可以很方便地得出一个逻辑地址对应的页号和页内偏移量,而无需进行除法运算,从而提升了运行速度。

②物理地址的计算更加迅速——根据逻辑地址得到页号,根据页号查询页表从而找到页面存放的内存块号,将二进制表示的内存块号和页内偏移量拼接起来,就可以得到最终的物理地址。


Tips:学有余力的同学建议看看二进制数的运算(计组内容),才更能理解本质原因

6.7、分页存储管理的逻辑地址结构

分页存储管理的逻辑地址结构如下所示

地址结构包含两个部分:前一部分为页号,后一部分为页内偏移量W。在上图所示的例子中,地址长度为32 位,其中011位为“页内偏移量”,或称“页内地址”;1231 位为“页号”。

image-20210119233753067

页偏移量与页号的计算

如果有K 位表示“页内偏移量”,则说明该系统中一个页面的大小是2K个内存单元

如果有M 位表示“页号”,则说明在该系统中,一个进程最多允许有2M个页面

注:页面大小和页内偏移量位数知道其中一个的大小,就可以推得另外一个的大小,并且在整个内存大小已知的情况下,可以得到分页存储管理的逻辑地址结构


Tips:有些奇葩题目中页面大小有可能不是2的整数次幂,这种情况还是得用最原始的方法计算:

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

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

6.8、本节课小结

image-20210119234417605

7、基本地址变换机构

7.1、思维导图

学习思路

结合上一小节理解基本地址变换机构(用于实现逻辑地址到物理地址转换的一组硬件机构)的原理和流程

image-20210119234457779

7.2、基本地址变换机构

基本地址变换机构的作用

基本地址变换机构可以借助进程的页表将逻辑地址转换为物理地址。

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


注意

页面大小是2的整数幂

进程控制块(PCB)存放在系统区

设页面大小为L,逻辑地址A到物理地址E的变换过程如下:

①计算页号P 和页内偏移量W( 如果用十进制数手算,则P=A/L,W=A%L;但是在计算机实际运行时,逻辑地址结构是固定不变的,因此计算机硬件可以更快地得到二进制表示的页号、页内偏移量)

②比较页号P 和页表长度M,若P≥M,则产生越界中断,否则继续执行。(注意:页号是从0开始的,而页表长度至少是1,因此P=M 时也会越界)

③页表中页号P对应的页表项地址= 页表起始地址F + 页号P * 页表项长度,取出该页表项内容b,即为内存块号。(注意区分页表项长度、页表长度、页面大小的区别。页表长度指的是这个页表中总共有几个页表项,即总共有几个页;页表项长度指的是每个页表项占多大的存储空间;页面大小指的是一个页面占多大的存储空间)

④计算E = b * L + W,用得到的物理地址E 去访存。(如果内存块号、页面偏移量是用二进制表示的,那么把二者拼接起来就是最终的物理地址了)

image-20210120095910538

动手验证:使用公式计算和使用二进制计算的结果是否相同?

动手验证:假设页面大小L = 1KB,最终要访问的内存块号b = 2,页内偏移量W= 1023。

①尝试用E = b * L + W 计算目标物理地址。

②尝试把内存块号、页内偏移量用二进制表示,并把它们拼接起来得到物理地址。

对比①②的结果是否一致

再举个栗子

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

计算物理地址

①计算页号、页内偏移量:页号P = A/L = 2500/1024 = 2; 页内偏移量W = A%L = 2500%1024 = 452

②根据题中条件可知,页号2没有越界,其存放的内存块号b = 8

③物理地址E = b * L + W = 8 * 1024 + 452 = 8644

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

7.3、对页表项大小的进一步探讨

页表项长度对编程寻址的影响

每个页表项的长度是相同的,页号是“隐含”的

Eg:假设某系统物理内存大小为4GB,页面大小为4KB, 的内存总共会被分为232 / 212 = 220 个内存块,内存块号的范围应该是0 ~ 220 -1。因此至少要20 个二进制位才能表示这么多的内存块号,因此至少要3个字节才够(每个字节8 个二进制位,3个字节共24 个二进制位)

image-20210120201226270


页表项长度为 3 字节

各页表项会按顺序连续地存放在内存中,如果该页表在内存中存放的起始地址为X ,则 M 号页对应的页表项是存放在内存地址为X + 3*M

假设一个页面为4KB,则每个页框可以存放 4096/3 = 1365 个页表项,但是这个页框会剩余4096 % 3 = 1 B 页内碎片。因此,1365 号页表项存放的地址为X + 3*1365 + 1

image-20210120200634562


页表项长度为 4 字节

但如果如果每个页表项占4字节,则每个页框刚好可存放 1024 个页表项,1024 号页表项虽然是存放在下一个页框中的,但是它的地址依然可以用X + 4*1024 得出

image-20210120201020033


结论:理论上,页表项长度为3B 即可表示内存块号的范围,但是,为了方便页表的查询,常常会让一个页表项占更多的字节,使得每个页面恰好可以装得下整数个页表项

7.4、本节课小结

image-20210120201238747

8、具有快表的地址变换机构

8.1、思维导图

具有快表的地址变换机构是基本地址变换机构的改进版本

image-20210120201327335

8.2、什么是快表?

块表的定义

快表,又称联想寄存器(TLB, translation lookaside buffer ),是一种访问速度比内存快很多的
高速缓存(TLB不是内存!),用来存放最近访问的页表项的副本,可以加速地址变换的速度。
与此对应,内存中的页表常称为慢表。

为什么需要高速缓存?

CPU 和内存访问性能的差距非常大。如今,一次内存的访问,大约需要 120 个 CPU Cycle。这也意味着,在今天,CPU 和内存的访问速度已经有了 120 倍的差距。

为了弥补两者之间的性能差异,充分利用 CPU,现代 CPU 中引入了高速缓存(CPU Cache)。高速缓存分为 L1/L2/L3 Cache,不是一个单纯的、概念上的缓存(比如使用内存作为硬盘的缓存),而是指特定的由 SRAM 组成的物理芯片。

为什么不能全用高速缓存?

虽然高速缓存速度非常快,但是非常昂贵,并且高速缓存和内存掉电都会丢失数据,不安全!

image-20210130233559070

8.3、地址变换过程

引入快表后,地址的变换过程

① CPU给出逻辑地址,由某个硬件算得页号、页内偏移量,将页号与快表中的所有页号进行比较。

② 如果找到匹配的页号,说明要访问的页表项在快表中有副本,则直接从中取出该页对应的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表命中,则访问某个逻辑地址仅需一次访存即可。

③ 如果没有找到匹配的页号,则需要访问内存中的页表,找到对应页表项,得到页面存放的内存块号,再将内存块号与页内偏移量拼接形成物理地址,最后,访问该物理地址对应的内存单元。因此,若快表未命中,则访问某个逻辑地址需要两次访存(注意:在找到页表项后,应同时将其存入快表,以便后面可能的再次访问。但若快表已满,则必须按照一定的算法对旧的页表项进行替换)

image-20210130233900249

引入块表后的访存耗时

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

:某系统使用基本分页存储管理,并采用了具有快表的地址变换机构。访问一次快表耗时1us,访问一次内存耗时100us。若快表的命中率为90%,那么访问一个逻辑地址的平均耗时是多少?

  1. 若系统不支持快表和慢表同时查找:(1+100) * 0.9 + (1+100+100) * 0.1 = 111 us
  2. 有的系统支持快表和慢表同时查找,如果是这样,平均耗时应该是:(1+100) * 0.9 + (100+100) * 0.1 = 110.9 us
  3. 若未采用快表机制,则访问一个逻辑地址需要100+100 = 200us 显然,引入快表机制后,访问一个逻辑地址的速度快多了。

image-20210130234348983

思考:能否把整个页表都放在TLB中?

快表为高速缓存,造价昂贵,想要把整个页表都放进入,那显然是不可能的事情~

既然存不下整个页表,那万一TLB装满了怎么办?那么就需要选择淘汰一些页表项——置换算法

8.4、局部性原理

时间局部性原理 & 空间局部性原理

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

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

上小节介绍的基本地址变换机构中,每次要访问一个逻辑地址,都需要查询内存中的页表。由于局部性原理,可能连续很多次查到的都是同一个页表项

举个栗子

如下图所示,由于该段代码的指令存放在 10 号页面,变量存放在 23 号页面,执行该段代码会很频繁地访问10号页面、23号页面,因此我们只需要将 10 号页面和 23 号页面的内存块号放在快表中,就能极大提高程序的执行效率

image-20210130234930602

8.5、本节课小结

TLB 和普通Cache 的区别

TLB 中只有页表项的副本,而普通Cache 中可能会有其他各种数据的副本

image-20210130234650355

9、两级页表

9.1、思维导图

image-20210131162847638

9.2、单级页表存在的问题

举个实际的例子:单级页表存在的问题

:某计算机系统按字节寻址,支持32 位的逻辑地址,采用分页存储管理,页面大小为 4KB(12 bit),页表项长度为 4B(内存块号至少要用 3B 来表示块号,但为了寻址方便,这里定义为 4B)

计算过程:4KB = 212B, 因此页内地址要用12位表示, 剩余 20 位表示页号。因此, 该系统中用户进程最多有 220 页。 相应的, 一个进程的页表中, 最多会有 220 = 1M = 1,048,576 个页表项, 所以一个页表最大需要 220 * 4B = 222 B, 共需要 222 /212 = 210 个页框存储该页表,即需要专门给进程分配 210 = 1024 个连续的页框来存放它的页表。

image-20210131164426232

根据页号查询页表的方法: K 号页对应的页表项存放位置 = 页表始址 + K * 4。注意:该寻址方式的前提条件是要在所有的页表项都连续存放的基础上才能用这种方法找到页表项

根据局部性原理可知,很多时候,进程在一段时间内只需要访问某几个页面就可以正常运行了。因此没有必要让整个页表都常驻内存。

9.3、如何解决单级页表的问题?

单级页表存在的两个问题

问题一:页表必须连续存放,因此当页表很大时,需要占用很多个连续的页框,这和离散分配的思想相违背

问题二:根据局部性原理:进程在一段时间内可能只需要访问某几个特定的页面,因此 没有必要让整个页表常驻内存,

如何解决单级页表存在的问题?

思考:我们是如何解决进程在内存中必须连续存储的问题的?


解决办法:将进程地址空间分页,并为其建立一张页表,记录各页面的存放位置。同样的思路也可用于解决“页表必须连续存放”的问题,把必须连续存放的页表再分页

可将长长的页表进行分组,使每个内存块刚好可以放入一个分组(比如上个例子中,页面大小 4KB,每个页表项 4B,每个页面可存放 1K 个页表项,因此每 1K 个连续的页表项为一组,每组刚好占一个内存块,再讲各组离散地放到各个内存块中)另外,要为离散分配的页表再建立一张页表,称为页目录表,或称外层页表,或称顶层页表


总结:把页表再分页并离散存储,然后再建立一张页表记录页表各个部分的存放位置,称为页目录表,或称外层页表,或称顶层页表

9.4、地址结构

如何划分单击页表的逻辑地址结构

32 位逻辑地址空间,页表项大小为 4B,页面大小为 4KB,则页内地址占 12 位,如果采用单级页表结构,那么一个进程最多可能会有 220 个页面,对应着 0~220-1 个页号。

image-20210131172630906

问题就在于 0~220-1 个页号太长了,为了方便进行页表的进行离散管理,于是我们可以将这 220 个页表再划分一下,将每个页表离散化划分为 1024 个页表项,由于内存块号占用 4B,因此离散化划分之后的页表大小也为 4KB(1024 * 4B = 4KB ),这样离散化后的页表刚好也能放入页框中

image-20210131172758904

两级页表结构的逻辑地址结构

上面已经讲到可以将每个页表离散化划分为 1024 个页表项,因此在一级页号中需要存储 210 个页表项,用于记录二级离散页表的内存块号,在二级页表中存储的内存块号才是目标访存的内存地址

image-20210131180327588

两级页表结构访存的实际操作步骤:根据一级页号可以拿到查看一级页表,可以拿到二级页表的内存块号,拿到二级页表的内存块号之后,再结合二级页号可以得知目标内存的实际物理地址,根据此物理地址执行访存操作即可

9.5、二级页表的地址变换

两级页表结构的逻辑地址结构

image-20210131181411193

举个栗子

:将逻辑地址(0000000000,0000000001,111111111111) 转换为物理地址(用十进制表示)


寻址步骤

①按照地址结构将逻辑地址拆分成三部分:(0000000000,0000000001,111111111111)

②从PCB 中读出页目录表始址,再根据一级页号查页目录表,一级页号为 0000000000,可得二级页表存储的内存块号为 3,因此我们就知道了下一级页表(二级页表)在内存中的存放位置

③根据二级页表的内存块号(3)和二级页号(0000000001)查二级页表,可以得到二级页表存储的内存块号为 4(页表项存放位置:3*4096+1*4 = 12,292)

④结合页内偏移量(二进制为 111111111111,十进制为 4095)和最终要访问的内存块号(4),该内存块的起始地址为 4*4096 = 16384,页内偏移量为 4095,最终的物理地址为 16384 + 4095= 20479

image-20210131182035264

9.6、二级页表的注意细节

二级页表解决的总结

由于单级页表存在长度很长的问题,以及进程运行时没有必要让整个页表常驻内存,我们使用了二级页表来解决这两个问题,将之前的单级页表离散化,并为离散化之后的页表(二级页表)再建立一个页表(一级页表)

采用了离散化页表技术之后,我们可以在需要访问页面时才把页面调入内存(虚拟存储技术)。可以在页表项中增加一个标志位,用于表示该页面是否已经调入内存。

若想访问的页面不在内存中,则产生缺页中断(内中断/异常),然后将目标页面从外存调入内存

多级页表

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


:某系统按字节编址,采用 40 位逻辑地址,页面大小为 4KB,页表项大小为 4B,假设采用纯页式存储,则要采用()级页表,页内偏移量为()位?

计算过程

  1. 页面大小 = 4KB =212B,按字节编址,因此页内偏移量为 12 位
  2. 页号 = 40 - 12 = 28 位
  3. 页面大小 = 212B,页表项大小= 4B ,则每个页面可存放212 / 4 = 210 个页表项
  4. 因此各级页表最多包含210 个页表项,需要 10 位二进制位才能映射到 210 个页表项,因此每一级的页表对应页号应为 10 位。总共 28 位的页号至少要分为三级

image-20210131184259834

为什么需要三级页表?如果只分为两级页表,则一级页号占 18 位,也就是说页目录表中最多可能有 218 个页表项,显然,一个页面是放不下这么多页表项的。

二级页表访存分析

两级页表的访存次数分析(假设没有快表机构)

  1. 第一次访存:访问内存中的页目录表
  2. 第二次访存:访问内存中的二级页表
  3. 第三次访存:访问目标内存单元

9.7、本节课小结

需要掌握的技能

能根据逻辑地址位数、页面大小、页表项大小确定多级页表的逻辑地址结构

image-20210131183907871

10、基本分段存储管理方式

10.1、思维导图

分配地址空间的基本单位

基本分段存储管理方式与“分页”最大的区别就是——离散分配时所分配地址空间的基本单位不同

image-20210131184915465

10.2、进程的分段存储管理

进程的分段

进程的地址空间:按照程序自身的逻辑关系划分为若干个段,每个段都有一个段名(在低级语言中,程序员使用段名来编程),每段从0开始编址

内存分配规则:以段为单位进行分配,每个段在内存中占据连续空间,但各段之间可以不相邻。

image-20210131185328862

由于是按逻辑功能模块划分,用户编程更方便,程序的可读性更高

LOAD 1, [D] | <A>;  //将分段D中A单元内的值读入寄存器1
STORE 1, [X] | <B>; //将寄存器1的内容存入X分段的B单元中

如何进行分段?

分段系统的逻辑地址结构由段号(段名)和段内地址(段内偏移量)所组成。如:

  1. 段号的位数决定了每个进程最多可以分几个段
  2. 段内地址位数决定了每个段的最大长度是多少

image-20210131185723216


举个栗子

在上述例子中,若系统是按字节寻址的,则段号占 16 位,因此在该系统中,每个进程最多有 216 = 64K 个段,段内地址占 16 位,因此每个段的最大长度是 216 = 64KB。


汇编代码举例

写程序时使用的段名[D][X] 会被编译程序翻译成对应段号

<A>单元、<B>单元会被编译程序翻译成段内地址

LOAD 1, [D] | <A>;  //将分段D中A单元内的值读入寄存器1
STORE 1, [X] | <B>; //将寄存器1的内容存入X分段的B单元中

image-20210131185856661

10.3、重要数据结构之段表

分段存储管理需要解决的问题

问题:程序分多个段,各段离散地装入内存,为了保证程序能正常运行,就必须能从物理内存中找到各个逻辑段的存放位置。为此,需为每个进程建立一张段映射表,简称“段表”。

解决办法:每个段对应一个段表项,其中记录了该段在内存中的起始位置(又称“基址”)和段的长度。各个段表项的长度是相同的。

image-20210131190613903


举个栗子

例如:某系统按字节寻址,采用分段存储管理,逻辑地址结构为(段号 16 位,段内地址 16位),因此用 16 位即可表示最大段长。物理内存大小为 4GB(可用 32 位表示整个物理内存地址空间)。因此,可以让每个段表项占 16+32 = 48 位,即 6B。由于段表项长度相同,因此段号可以是隐含的,不占存储空间。若段表存放的起始地址为 M,则 K 号段对应的段表项存放的地址为 M + K*6

10.4、分段式管理的地址变换

举个栗子

假设如下指令经过编译程序编译后,形成等价的机器指令:“取出段号为2,段内地址为1024 的内存单元中的内容,放到寄存器1中”

LOAD 1, [D] | <A>; //将分段D中A单元内的值读入寄存器1

机器指令中的逻辑地址用二进制表示: 00000000000000100000000100000000,CPU 执行指令时需要查询段表,将逻辑地址变换为物理地址

image-20210131225039491

分段式存储管理的地址变换

① 根据段号的位数和段内地址的位数,将进程的逻辑地址拆分为段号和段内地址

② 对段号进行安全性检查,如果越界则产生中断,否则继续执行下一步

③ 在 PCB 中存储了段表的起始地址 F 和段表长度 M,当进程上 CPU 运行时,操作系统就会将这两个变量放到段表寄存器中。根据段表起始地址和段号计算段表项的存放地址为:F+S*段表项长度

④ 知道段表项存放地址就可以取出段长 c 和基址 b,并对段内地址进行安全性检查,如果越界则产生中断,否则继续执行下一步

⑤ 实际物理地址 = 段基址b + 段内地址W

⑥ 执行访存操作

image-20210131224611519

10.5、分页管理的对比

分段与分页的本质区别

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

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

内存大小方面

页的大小固定且由系统决定。

段的长度却不固定,决定于用户编写的程序。

空间维度方面

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

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


举个栗子

使用分页管理方式只需要指定变量 A 的地址(实际的物理地址)就能访问该变量,但使用分段管理方式需要给出变量 A 所在的段名和段内地址才能访问该变量

image-20210131225927503

信息共享方面

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

不能被修改的代码称为纯代码或可重入代码(不属于临界资源),这样的代码是可以共享的

可修改的代码是不能共享的(比如,有一个代码段中有很多变量,各进程并发地同时访问可能造成数据不一致)


使用分段式存储管理方式

在生产者消费者问题中,生产者进程的 1 号段的功能为判断缓冲区此时是否可访问,因此该段需要被消费者进程共享访问,在各个消费者进程中只需让其段表都指向 1 号段表所在的内存区域即可

image-20210131230311808


使用分页式存储管理方式

假设使用分页式存储管理方式,进程各个完整的段可能会被拆分开来,比如 1 好短就被拆分为 1KB 和 2KB,分别存放在两个页框(1 号页框和 2 号页框)中。如果让消费者进程的页面分别指向这两个页面,显然是很瓜皮的行为,因为这两个页面中包含不可共享的区域

image-20210131230743016

访存步骤

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

分段:第一次访存——查内存中的段表,第二次访存——访问目标内存单元。总共两次访存与分页系统类似,分段系统中也可以引入快表机构,将近期访问过的段表项放到快表中,这样可以少一次访问,加快地址变换速度。

10.6、本节课小结

image-20210131225810319

11、段页式管理方式

11.1、思维导图

image-20210131233003402

11.2、分段的对比

分页、分段的优缺点分析

注:分段管理中产生的外部碎片也可以用“紧凑”来解决,只是需要付出较大的时间代价

image-20210131233052401

11.3、进程的段页式管理

分段+分页=段页式管理

将进程按逻辑模块分段,再将各段分页(如每个页面4KB)再将内存空间分为大小相同的内存块/页框/页帧/物理块进程前将各页面分别装入各内存块中

image-20210131233313234

11.4、段页式管理的地址结构

分段式管理的逻辑地址结构

分段系统的逻辑地址结构由段号和段内地址(段内偏移量)组成。如:

image-20210131233557490

段页式管理的逻辑地址结构

段页式系统的逻辑地址结构由段号、页号、页内地址(页内偏移量)组成。如:

  1. 段号的位数决定了每个进程最多可以分几个段
  2. 页号位数决定了每个段最大有多少页
  3. 页内偏移量决定了页面大小、内存块大小是多少

image-20210131233624933


在上述例子中,若系统是按字节寻址的,则

段号占 16 位,因此在该系统中,每个进程最多有 216 = 64K 个段

页号占 4 位,因此每个段最多有 24 = 16 页

页内偏移量占 12 位,因此每个页面/每个内存块大小为 212 = 4096 = 4KB


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

11.5、段页式管理的地址变换

举个栗子

每个段对应一个段表项,每个段表项由段号、页表长度、页表存放块号(页表起始地址)组成。每个段表项长度相等,段号是隐含的。注意与分段式管理的段表进行区分,分段式管理的段表由段号、段长、段起始地址组成

每个页面对应一个页表项,每个页表项由页号、页面存放的内存块号组成。每个页表项长度相等,页号是隐含的。

采用段页式管理方式之后,一个进程会对应一个段表,但可能会对应着多个页表

image-20210131234410920

段页式存储管理的地址变换

① 根据段号的位数、页号的位数、位数,将进程的逻辑地址拆分为段号、页号、页内地址

② 对段号进行安全性检查,如果越界则产生中断,否则继续执行下一步

③ 在 PCB 中存储了段表的起始地址 F 和段表长度 M,当进程上 CPU 运行时,操作系统就会将这两个变量放到段表寄存器中。根据段表起始地址和段号计算段表项的存放地址为:F+S*段表项长度

④ 知道段表项存放地址就可以取出页表长度和页表存放块号,并对页号进行安全性检查,如果越界则产生中断,否则继续执行下一步

⑤ 根据页表存放块号即可算出页表存放的内存地址,再根据页号即可找到页表中对应的页表项,并获取到待访问内存单元的内存块号

⑥ 根据内存块号和页内偏移量即可得到最终的物理地址

⑦ 执行访存操作

image-20210201000043871

:段页式管理也可引入快表机构,用段号和页号作为查询快表的关键字。若快表命中则仅需一次访存

11.6、本节课小结

image-20210131233444132

12、虚拟内存的基本概念

12.1、思维导图

内存管理的概念

image-20210201000132552

虚拟内存的基本概念

image-20210201000253673

12.2、传统存储管理方式

传统存储管理方式的特征和缺点:一次性和驻留性

一次性:作业必须一次性全部装入内存后才能开始运行。这会造成两个问题:①作业很大时,不能全部装入内存,导致大作业无法运行;②当大量作业要求运行时,由于内存无法容纳所有作业,因此只有少量作业能运行,导致多道程序并发度下降。

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

image-20210201000407651

总结:在传统存储管理方式中,很多暂时用不到的数据也会长期占用内存,导致内存利用率不高,存在着一次性和驻留性的问题,可以使用虚拟存储技术解决此问题

12.3、局部性原理

局部性原理:时间局部性和空间局部性

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

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

高速缓存

高速缓冲技术的思想:将近期会频繁访问到的数据放到更高速的存储器中,暂时用不到的数据放在更低速存储器中。快表机构就是将近期常访问的页表项副本放到更高速的联想寄存器中

image-20210201000717100

12.4、虚拟内存技术

虚拟内存的定义和特征

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

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

若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。在操作系统的管理下,在用户看来似乎有一个比实际内存大得多的内存,这就是虚拟内存

虚拟内存是操作系统虚拟性的一个体现,实际的物理内存大小没有变,只是在逻辑上进行了扩充

image-20210201103509520

虚拟内存的大小

易混知识点

虚拟内存的最大容量是由计算机的地址结构(CPU寻址范围)确定的

虚拟内存的实际容量 = min(内存和外存容量之和,CPU寻址范围)


举个栗子

如:某计算机地址结构为 32 位,按字节编址,内存大小为 512 MB,外存大小为 2GB。

则虚拟内存的最大容量为 232B = 4GB

虚拟内存的实际容量 = min (232B, 512MB+2GB) = 2GB+512MB

虚拟内存的三大特征

多次性:无需在作业运行时一次性全部装入内存,而是允许被分成多次调入内存。

对换性:在作业运行时无需一直常驻内存,而是允许在作业运行过程中,将作业换入、换出。

虚拟性:从逻辑上扩充了内存的容量,使用户看到的内存容量,远大于实际的容量。

12.5、如何实现虚拟内存技术?

虚拟内存技术需要建立在离散分配的基础上

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

image-20210201104732090


在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。操作系统要提供请求调页(或请求调段)功能

若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。操作系统要提供页面置换(或段置换)的功能

12.6、本节课小结

image-20210201104832005

13、请求分页管理方式

13.1、思维导图

请求分页存储管理与基本分页存储管理的主要区别

在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存(操作系统要提供请求调页功能,将缺失页面从外存调入内存),然后继续执行程序

若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存(操作系统要提供页面置换的功能,将暂时用不到的页面换出外存)

注意与基本分页存储管理的页表机制、地址变换流程对比学习

image-20210201170831364

13.2、页表机制

页表机制的实现原理

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

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

基本页表与请求页表

基本页表

image-20210201172408909


请求页表

请求页表相较于基本也表增加了四个字段,

  1. 状态位:记录该内存块号是否已经调入内存中
  2. 访问字段:可用于记录页面最近被访问过几次或者上次访问的时间,供置换算法选择换出页面时参考
  3. 修改位:记录页面调入内存后是否被修改过,如果没有被修改过则不用写回外存,可省去耗时的 I/O 操作
  4. 外存地址:记录页面在外存中存放的位置

image-20210201172607322

13.3、缺页中断机构

缺页中断机构的作用

在请求分页系统中,每当要访问的页面不在内存时,便产生一个缺页中断,然后由操作系统的缺页中断处理程序处理中断。此时缺页的进程阻塞,放入阻塞队列,调页完成后再将其唤醒,放回就绪队列。

1、内存中有空闲块

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


举个栗子

假设此时要访问逻辑地址=(页号,页内偏移量)=(0, 1024),此时页表的状态如下

image-20210201175421878

查找页表发现,0 号页表项的状态位为 0,即表示该页面不在物理内存中,而是存储在外存的 x 块号中。假设操作系统恰好能找到空闲的 a 号块,那么就将外存 x 号块中的内容复制到物理内存 a 号块中

image-20210201175615716

当然还需要更新请求页表的页表项,将 0 号页表项的状态位设置为 1,表示正在占用物理内存;将 0 号页表项的内存块号设置为 a,表示正在占用 a 号块物理内存

image-20210201180012833

2、内存中没有空闲块

如果内存中没有空闲块,则由页面置换算法选择一个页面淘汰,若该页面在内存期间被修改过,则要将其写回外存。未修改过的页面不用写回外存。


举个栗子

假设此时要访问逻辑地址=(页号,页内偏移量)=(0, 1024),此时页表的状态如下

image-20210201175421878

查找页表发现,0 号页表项的状态位为 0,即表示该页面并不在物理内存中,而是存储在外存的 x 块号中。假设操作系统此时无法找到空间的内存块,那么就需要通过置换算法回收一个内存块。假设操作系统通过置换算法将 c 号块回收,将其数据写回外存 z 号块中

image-20210201181251365

当 c 号块被回收,变成空间状态之后,再将外存 x 号块中的内容复制到 c 号块中

image-20210201181608434

整个过程结束后,页表中有两个页表项发生了改变:2 号页表项的状态位被置零,内存块号被清空,修改位被置零;0 号页表项的状态位被置 1,并且记录了占用的 c 号块

image-20210201181717527

缺页中断属于内中断

缺页中断是因为当前执行的指令想要访问的目标页面未调入内存而产生的,因此属于内中断一条指令在执行期间,可能产生多次缺页中断。

如:copy A to B,即将逻辑地址A中的数据复制到逻辑地址B,而A、B属于不同的页面,则有可能产生两次中断)


复习中断的分类

  1. 内中断:信号的来源为CPU内部
    1. 陷阱、陷入(trap):有意而为之的异常,如系统调用
    2. 故障(fault):由错误条件引起的,可能被故障处理程序修复,如缺页中断
    3. 终止(abort):不可恢复的致命错误造成的结果,终止处理程序不再将控制返回给引发终止的应用程序,如整数除0
  2. 外中断:信号的来源为CPU外部

image-20210201182256406

13.4、地址变换机构

请求分页存储管理在地址变换上新增的步骤

新增步骤 1:请求调页(查到页表项时发现页面并不在内存中)

新增步骤 2:页面置换(当需要调入页面,但没有空闲内存块时进行)

新增步骤 3:需要修改请求页表中新增的表项(新增的四个字段)

请求分页存储管理的地址变换机构

  1. 根据页号查找快表,若快表命中,则直接执行第 3 步,否则执行第二步(注:快表中有的页面一定是在内存中的。若某个页面被换出外存,则快表中的相应表项也要删除,否则可能访问错误的页面)
  2. 根据页表寄存器中的页表始址和页号可以查询到对应的页表项
  3. 找到对应页表项后,若对应页面未调入内存,则产生缺页中断,之后由操作系统的缺页中断处理程序进行处理
  4. 根据查询到的内存块号和页内偏移量得到待访问内存的物理地址,执行访存操作即可

image-20210201183034998

地址变换的补充细节

① 只有“写指令”才需要修改“修改位”,并且一般来说只需修改快表中的数据。只有要将快表项删除时才需要写回内存中的慢表。这样可以减少访存次数。

② 和普通的中断处理一样,缺页中断处理依然需要保留CPU现场。

③ 需要用某种“页面置换算法”来决定一个换出页面(下节内容)

④ 换入/换出页面都需要启动慢速的I/O操作,可见,如果换入/换出太频繁,会有很大的开销。

⑤ 页面调入内存后,需要修改慢表,同时也需要将表项复制到快表中。

image-20210201184044541


在具有快表机构的请求分页系统中,访问一个逻辑地址时,若发生缺页,则地址变换步骤是:
查快表(未命中)——查慢表(发现未调入内存)——调页(调入的页面对应的表项会直接加入快表)——查快表(命中)——访问目标内存单元

13.5、本节课小结

image-20210201184155267

14、页面置换算法

14.1、思维导图

请求分页存储管理与基本分页存储管理的主要区别

在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存(操作系统要提供请求调页功能,将缺失页面从外存调入内存),然后继续执行程序

若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存(操作系统要提供页面置换的功能,将暂时用不到的页面换出外存)

页面的换入、 换出需要磁盘I/O, 会有较大的开销, 因此好的页面置换算法应该追求更少的缺页率

image-20210201185019392

14.2、最佳置换算法(OPT)

最佳置换算法(OPT, Optimal)的思想

最佳置换算法(OPT, Optimal) : 每次选择淘汰的页面将是以后永不使用, 或者在最长时间内不再被访问的页面, 这样可以保证最低的缺页率。

举个栗子说明最佳置换算法的执行过程

: 假设系统为某进程分配了三个内存块, 并考虑到有一下页面号引用串(会依次访问这些页面) :7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1

算法执行过程

  1. 访问页面 7,此时三个内存块都还是空闲状态,将页面 7 放在内存块 1 中

    image-20210201202352058

  2. 访问页面 0,此时剩下两个内存块都还是空闲状态,将页面 0 放在内存块 2 中

    image-20210201202503186

  3. 访问页面 1,此时只剩下一个空闲状态的内存块,将页面 1 放在内存块 3 中

    image-20210201202507624

  4. 访问页面 2,此时没有空闲的内存块,需要使用最佳置换算法置换出一个空闲内存块,其具体做法是:向后寻找,发现页面 0 和页面 1 均会被使用到,页面 7 为当下最长时间内不再被访问的页面,于是将页面 7 置换出内存,将页面 2 装入内存块 1 中

    image-20210201202614248

  5. 访问页面 0 时,发现页面 0 在内存中,直接访问即可

    image-20210201203001824

  6. 访问页面 3,此时没有空闲的内存块,需要使用最佳置换算法置换出一个空闲内存块,其具体做法是:向后寻找,发现页面 0 和页面 2 均会被使用到,页面 1 为当下最长时间内不再被访问的页面,于是将页面 1 置换出内存,将页面 3 装入内存块 3 中

    image-20210201203050284


以上打 ✔ 的部分表示发生了缺页中断,整个过程缺页中断发生了9次, 缺页率 = 9/20 = 45%,页面置换发生了6次。

注意: 缺页时未必发生页面置换。 若还有可用的空闲内存块,就不用进行页面置换。

最佳置换算法的优缺点

最佳置换算法(OPT, Optimal) : 每次选择淘汰的页面将是以后永不使用, 或者在最长时间内不再被访问的页面, 这样可以保证最低的缺页率。

最佳置换算法可以保证最低的缺页率, 但实际上, 只有在进程执行的过程中才能知道接下来会访问到的是哪个页面。 操作系统无法提前预判页面访问序列。 因此, 最佳置换算法是无法实现的。

14.3、先进先出置换算法(FIFO)

先进先出置换算法(FIFO) 的思想

先进先出置换算法(FIFO) : 每次选择淘汰的页面是最早进入内存的页面

实现方法: 把调入内存的页面根据调入的先后顺序排成一个队列, 需要换出页面时选择队头页面即可。队列的最大长度取决于系统为进程分配了多少个内存块。

举个栗子说明先进先出置换算法的执行过程

: 假设系统为某进程分配了三个内存块, 并考虑到有以下页面号引用串:3, 2, 1, 0, 3, 2, 4, 3, 2, 1, 0, 4

算法执行过程

  1. 访问页面 3,此时三个内存块都还是空闲状态,将页面 3 放在内存块 1 中,此时队列也为空,将页面 3 作为队列的队头

    image-20210201204509727

  2. 访问页面 2,此时剩下两个内存块都还是空闲状态,将页面 0 放在内存块 2 中,将页面 2 插入到队列队尾的位置

    image-20210201204515278

  3. 访问页面 1,此时只剩下一个空闲状态的内存块,将页面 1 放在内存块 3 中,将页面 1 插入到队列队尾的位置

    image-20210201204519590

  4. 访问页面 0,此时没有空闲的内存块,需要使用先进先出算法置换出一个空闲内存块,其具体做法是:从页面队列的队头弹出页面 3,将页面 3 置换出内存,并将页面 0 装入内存块 1 中

    image-20210201204527063

  5. 访问页面 3,此时没有空闲的内存块,需要使用先进先出算法置换出一个空闲内存块,其具体做法是:从页面队列的队头弹出页面 1,将页面 1 置换出内存,并将页面 3 装入内存块 3 中

    image-20210201204533168


以上打 ✔ 的部分表示发生了缺页中断,分配三个内存块时, 缺页次数为 9 次

再举个栗子说明先进先出算法存在 Belady 异常

: 假设系统为某进程分配了四个内存块, 并考虑到有以下页面号引用串:3, 2, 1, 0, 3, 2, 4, 3, 2, 1, 0, 4

image-20210201205125276

缺页结果:分配四个内存块时,缺页次数: 10次分配三个内存块时,缺页次数: 9次,这种现象称之为 Belady 异常

Belady 异常——当为进程分配的物理块数增大时, 缺页次数不减反增的异常现象。

先进先出算法的优缺点

只有 FIFO 算法会产生 Belady 异常。 另外, FIFO算法虽然实现简单, 但是该算法与进程实际运行时的规律不适应, 因为先进入的页面也有可能最经常被访问。 因此,该 算法性能差

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

最近最久未使用置换算法(LRU)额思想

最近最久未使用置换算法(LRU, least recently used) : 每次淘汰的页面是最近最久未使用的页面

实现方法: 赋予每个页面对应的页表项中, 用访问字段记录该页面自上次被访问以来所经历的时间 t(通过访问字段记录上次访问的时间)。当需要淘汰一个页面时, 选择现有页面中 t 值最大的, 即最近最久未使用的页面。

image-20210201205918894

举个栗子说明最近最久未使用置换算法的执行过程

: 假设系统为某进程分配了四个内存块, 并考虑到有以下页面号引用串:1, 8, 1, 7, 8, 2, 7, 2, 1, 8, 3, 8, 2, 1, 3, 1, 7, 1, 3, 7

算法执行过程

在手动做题时, 若需要淘汰页面, 可以逆向检查此时在内存中的几个页面号。 在逆向扫描过程中最后一个出现的页号就是要淘汰的页面。

  1. 访问页面 1,此时三个内存块都还是空闲状态,将页面 1 放在内存块 1 中

    image-20210201205713089

  2. 访问页面 8,此时剩下两个内存块都还是空闲状态,将页面 8 放在内存块 2 中

    image-20210201205716807

  3. 以此类推,访问页面 2,此时只剩下一个空闲状态的内存块,将页面 2 放在内存块 4 中

    image-20210201205721118

  4. 访问页面 3,此时没有空闲的内存块,需要使用 LRU 算法置换出一个空闲内存块,其具体做法是:计算得知最近最久未使用的页面为页面 7,将页面 7 置换出内存,并将页面 3 装入内存块 3 中

    image-20210201205729024

  5. 访问页面 7,此时没有空闲的内存块,需要使用 LRU 算法置换出一个空闲内存块,其具体做法是:计算得知最近最久未使用的页面为页面 8,将页面 8 置换出内存,并将页面 7 装入内存块 2 中

    image-20210201210933161

LRU 算法的优缺点

该算法的实现需要专门的硬件支持, 虽然算法性能好,但是实现困难, 开销大

14.5、时钟置换算法(CLOCK)

时钟置换算法(CLOCK)的思想

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

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

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

image-20210201211221793

举个栗子说明时钟置换算法的执行过程

: 假设系统为某进程分配了五个内存块, 并考虑到有以下页面号引用串:1, 3, 4, 2, 5, 6, 3, 4, 7

算法执行过程

  1. 访问页面 1, 3, 4, 2, 5 时会将页面 1, 3, 4, 2, 5 装入内存,循环队列的状态如下(访问位均为 1)

    image-20210201211436550

  2. 访问页面 6,此时没有空闲的内存块,需要使用时钟置换算法置换出一个空闲内存块,其具体做法是:遍历循环队列,目标是寻找访问位为 0 的页面,在寻找的过程当中如果遇到访问位为 1 的页面,将其访问位改为 0

    image-20210201211553133

  3. 很遗憾,第一圈下来,并没有找到访问位为 0 的页面,于是就将所有的页面访问位都修改为 0,再次进行第二圈遍历时,将页面 1 置换出内存,并将页面 6 装入内存

    image-20210201212435015

  4. 继续访问页面 3, 4 时会将页面的访问位置为 1(页面 3, 4 在内存中,无需执行请求调页)

    image-20210201212834934

  5. 访问页面 6,此时没有空闲的内存块,需要使用时钟置换算法置换出一个空闲内存块,其具体做法是:遍历循环队列,目标是寻找访问位为 0 的页面,在寻找的过程当中如果遇到访问位为 1 的页面,将其访问位改为 0。上图中的页面 2 的访问位为 0,将其置换出内存,并将页面 7 装入内存

    image-20210201212928541

时钟置换算法思想的核心

构建循环队列:将访问过的页面依次放入循环队列中,并将其访问位设置为 0

执行时钟置换算法:遍历队列找到最近的访问位等于 0 的页面,即表示最近没有使用过的页面

14.6、改进型的时钟置换算法

改进型的时钟置换算法的思想

简单的时钟置换算法仅考虑到一个页面最近是否被访问过。 事实上, 如果被淘汰的页面没有被修改过,就不需要执行I/O操作写回外存。 只有被淘汰的页面被修改过时, 才需要写回外存。

因此, 除了考虑一个页面最近有没有被访问过之外, 操作系统还应考虑页面有没有被修改过。 在其他条件都相同时, 应优先淘汰没有修改过的页面, 避免I/O操作。 这就是改进型的时钟置换算法的思想。修改位=0, 表示页面没有被修改过; 修改位=1, 表示页面被修改过。为方便讨论, 用(访问位, 修改位) 的形式表示各页面状态。 如(1, 1) 表示一个页面近期被访问过,且被修改过。

算法规则: 将所有可能被置换的页面排成一个循环队列

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

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

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

第四轮: 若第三轮扫描失败, 则重新扫描, 查找第一个(0, 1) 的帧用于替换。

由于第二轮已将所有帧的访问位设为0, 因此经过第三轮、 第四轮扫描一定会有一个帧被选中, 因此改进型CLOCK置换算法选择一个淘汰页面最多会进行四轮扫描

改进型的时钟置换算法:遍历第一轮就能找到淘汰页面的情况

  1. 循环队列的初始状态

    image-20210201215847342

  2. 遍历找到第一个(0, 0)的页面,将其置换出内存

    image-20210201220835972

改进型的时钟置换算法:遍历第二轮就能找到淘汰页面的情况

  1. 循环队列的初始状态

    image-20210201220929701

  2. 第一轮扫描并没有找到(0, 0)的页面,于是进行第二轮扫描,在扫描的过程中将页面的访问位置零,并找到第一个(0, 1)的页面,将其置换出内存

    image-20210201220957820

改进型的时钟置换算法:遍历第三轮才能找到淘汰页面的情况

  1. 循环队列的初始状态

    image-20210201221135157

  2. 第一轮扫描并没有找到(0, 0)的页面,于是进行第二轮扫描,在扫描的过程中将页面的访问位置零,第二轮扫描没有找到(0, 1)的页面

    image-20210201221151107

  3. 于是执行第三轮扫描,找到第一个(0, 0)的页面,将其置换出内存

    image-20210201221205153

改进型的时钟置换算法:遍历第四轮才能找到淘汰页面的情况

  1. 循环队列的初始状态

    image-20210201221715022

  2. 第一轮扫描并没有找到(0, 0)的页面,于是进行第二轮扫描,在扫描的过程中将页面的访问位置零,第二轮扫描没有找到(0, 1)的页面

    image-20210201221719981

  3. 于是执行第三轮扫描,第三轮扫描没有找到(0, 0)的页面

    image-20210201221731349

  4. 于是执行第四轮扫描,找到第一个(0, 1)的页面,将其置换出内存

    image-20210201221739697

改进型的时钟置换算法思想的核心

第一轮遍历(第一优先级): 最近没访问,且没修改的页面

第二轮遍历(第二优先级):最近没访问,但修改过的页面

第三轮遍历(第三优先级):最近访问过,但没修改的页面

第四轮遍历(第四优先级):最近访问过,且修改过的页面

14.7、本节课小结

image-20210201222044873

15、页面分配策略

15.1、思维导图

image-20210201222135698

15.2、置换策略

驻留集的概念

驻留集:指请求分页存储管理中给进程分配的物理块的集合。在采用了虚拟存储技术的系统中,驻留集大小一般小于进程的总大小。若驻留集太小,会导致缺页频繁,系统要花大量的时间来处理缺页,实际用于进程推进的时间很少;驻留集太大,又会导致多道程序并发度下降,资源利用率降低。所以应该选择一个合适的主流级大小

举个栗子:考虑一个极端情况,若某进程共有100个页面,则该进程的驻留集大小为100时进程可以全部放入内存,运行期间不可能再发生缺页。若驻留集大小为1,则进程运行期间必定会极频繁地缺页

固定分配 & 可变分配

固定分配:操作系统为每个进程分配一组固定数目的物理块,在进程运行期间不再改变。即,驻留集大小不变

可变分配:先为每个进程分配一定数目的物理块,在进程运行期间,可根据情况做适当的增加或减少。即,驻留集大小可变

局部置换 & 全局置换

局部置换:发生缺页时只能选进程自己的物理块进行置换

全局置换:可以将操作系统保留的空闲物理块分配给缺页进程,也可以将别的进程持有的物理块置换到外存,再分配给缺页进程

分配策略与置换策略的搭配方式

全局置换意味着一个进程拥有的物理块数量必然会改变,因此不可能是固定分配

image-20210215235156174


1、固定分配局部置换

固定分配局部置换:系统为每个进程分配一定数量的物理块,在整个运行期间都不改变。若进程在运行中发生缺页,则只能从该进程在内存中的页面中选出一页换出,然后再调入需要的页面。

固定分配局部置换策略的缺点是:很难在刚开始就确定应为每个进程分配多少个物理块才算合理。(采用这种策略的系统可以根据进程大小、优先级、或是根据程序员给出的参数来确定为一个进程分配的内存块数)


2、可变分配全局置换

可变分配全局置换:刚开始会为每个进程分配一定数量的物理块。操作系统会保持一个空闲物理块队列。当某进程发生缺页时,从空闲物理块中取出一块分配给该进程;若已无空闲物理块,则可选择一个未锁定的页面(系统会锁定一些页面,这些页面中的内容不能置换出外存,如:重要
的内核数据可以设为“锁定”状态)换出外存,再将该物理块分配给缺页的进程。

采用可变分配全局置换策略时,只要某进程发生缺页,都将获得新的物理块,仅当空闲物理块用完时,系统才选择一个未锁定的页面调出。被选择调出的页可能是系统中任何一个进程中的页,因此这个被选中的进程拥有的物理块会减少,缺页率会增加


3、可变分配局部

可变分配局部:刚开始会为每个进程分配一定数量的物理块。当某进程发生缺页时,只允许从该
进程自己的物理块中选出一个进行换出外存。如果进程在运行中频繁地缺页,系统会为该进程多分配几个物理块,直至该进程缺页率趋势适当程度;反之,如果进程在运行中缺页率特别低,则可适当减少分配给该进程的物理块


总结

可变分配全局置换:只要缺页就给分配新物理块

可变分配局部置换:要根据发生缺页的频率来动态地增加或减少进程的物理块

15.3、何时调入页面

1、预调页策略

预调页策略:根据局部性原理(主要指空间局部性,即:如果当前访问了某个内存单元,在之后很有可能会接着访问与其相邻的那些内存单元),一次调入若干个相邻的页面可能比一次调入一个页面更高效。但如果提前调入的页面中大多数都没被访问过,则又是低效的。因此可以预测不久之后可能访问到的页面,将它们预先调入内存,但目前预测成功率只有50%左右。故这种策略主要用于进程的首次调入(即运行前调入),由程序员指出应该先调入哪些部分。

2、请求调页策略

请求调页策略:进程在运行期间发现缺页时才将所缺页面调入内存(即运行时调入)。由这种策略调入的页面一定会被访问到,但由于每次只能调入一页,而每次调页都要磁盘I/O操作,因此I/O开销较大。

15.4、从何处调入页面

0、对换区(Swap 分区)

swap 分区通常被称为交换分区,这是一块特殊的硬盘空间,即当实际内存不够用的时候,操作系统会从内存中取出一部分暂时不用的数据,放在交换分区中,从而为当前运行的程序腾出足够的内存空间。

使用 swap 交换分区,显著的优点是,通过操作系统的调度,应用程序实际可以使用的内存空间将远远超过系统的物理内存。由于硬盘空间的价格远比 RAM 要低,因此这种方式无疑是经济实惠的。当然,频繁地读写硬盘,会显著降低操作系统的运行速率,这也是使用 swap 交换分区最大的限制,但相较于离散的分配方式,swap 分区采用连续分配方式,读写速度更快。

相比较而言,Windows 不会为 swap 单独划分一个分区,而是使用分页文件实现相同的功能,在概念上,Windows 称其为虚拟内存,从某种意义上将,这个叫法更容易理解。因此,初学者将 swap 交换分区理解为虚拟内存是没有任何问题的。

1、系统拥有足够的对换区空间

系统拥有足够的对换区空间:页面的调入、调出都是在内存与对换区之间进行,这样可以保证页面的调入、调出速度很快。在进程运行前,需将进程相关的数据从文件区复制到对换区。

image-20210216105901403

2、系统缺少足够的对换区空间

系统缺少足够的对换区空间:凡是不会被修改的数据都直接从文件区调入,由于这些页面不会被修改,因此换出时不必写回磁盘,下次需要时再从文件区调入即可。对于可能被修改的部分,换出时需写回磁盘对换区,下次需要时再从对换区调入。

image-20210216111344136

3、UNIX 调入页面的方式

UNIX 调入页面方式:运行之前进程有关的数据全部放在文件区,故未使用过的页面,都可从文件区调入。若被使用过的页面需要换出,则写回对换区,下次需要时从对换区调入。

image-20210216202714420

15.5、抖动(颠簸)现象

抖动(颠簸)现象及产生的原因

刚刚换出的页面马上又要换入内存,刚刚换入的页面马上又要换出外存,这种频繁的页面调度行为称为抖动,或颠簸。产生抖动的主要原因是进程频繁访问的页面数目高于可用的物理块数(分配给进程的物理块不够)

因此我们为进程分配的物理块太少时,会使进程发生抖动现象。为进程分配的物理块太多,又会降低系统整体的并发度,降低某些资源的利用率

为了研究为应该为每个进程分配多少个物理块,Denning 提出了进程“工作集”的概念

15.6、工作集

工作集的概念

驻留集:指请求分页存储管理中给进程分配的内存块的集合。

工作集:指在某段时间间隔里,进程实际访问页面的集合。

工作集与窗口尺寸

操作系统会根据“窗口尺寸”来算出工作集。例:某进程的页面访问序列如下,窗口尺寸为4,各时刻的工作集为?

image-20210216203211970

工作集大小可能小于窗口尺寸,实际应用中,操作系统可以统计进程的工作集大小,根据工作集大小给进程分配若干内存块。如:窗口尺寸为 5,经过一段时间的监测发现某进程的工作集最大为3 ,那么说明该进程有很好的局部性,可以给这个进程分配 3 个以上的内存块即可满足进程的运行需要。一般来说,驻留集大小不能小于工作集大小,否则进程运行过程中将频繁缺页。


拓展:基于局部性原理可知,进程在一段时间内访问的页面与不久之后会访问的页面是有相关性的。因此,可以根据进程近期访问的页面集合(工作集)来设计一种页面置换算法——选择一个不在工作集中的页面进行淘汰。

15.7、本节课小结

image-20210216203326332

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值