操作系统-1w字关于内存的总结

内存的基本概念

什么是内存,有何作用

内存是用于存放数据的硬件。程序执行前需要先放入内存中才能被CPU处理

存储单元

内存中也有一个一个的小房间,每个小房间就是一个存储单元。
如果计算机按照 字节编址,则每个存储单元的大小为1个字节,即1B,即8个二进制位。
如果字长为16位的计算机按字编址,则每个存储单元的大小为一个字;每个字的大小是一个16位的二进制,也就是俩字节。

内存地址

内存地址就是对存储单元编号,每个内存地址对应一个存储单元。

进程运行的基本原理

其实我们使用高级语言写的代码需要翻译成CPU能识别的指令才能执行。这些指令会告诉CPU应该去内存的那个位置存取数据。但是实际上生成指令的时候并不知道该进程的数据会被放在哪个位置。所以编译生成的指令一般是使用逻辑地址(也就是相对地址)

逻辑地址(相对地址);物理地址(绝对地址)

逻辑地址并不是数据存储的真实地址,它通常是我们写代码中操作的地址,真实的地址就是物理地址,物理地址才是将变量或其他数据放在存储单元的具体位置。逻辑地址在编译或装入、运行阶段编译成物理地址

从写程序到程序运行
  • 编辑源代码文件
  • 编译:由源代码文件生成目标文件(高级语言翻译成机器语言)
  • 链接:由目标模块生成装入模块,链接后形成完整的逻辑地址
  • 装入:由装入模块装入内存,装入形成物理地址
三种链接方式:
  • 静态链接:装入前链接成一个完整的物理地址
  • 装入时动态链接:运行前一边装入一边链接
  • 运行时动态链接:运行时需要目标模块才装入并链接
三种装入方式:
  • 绝对装入:编译时产生绝对地址(适用于单道批处理阶段,无操作系统)
  • 可重定位装入:装入时将逻辑地址转换为物理地址(多道批处理阶段)
  • 动态运行时装入:运行时将逻辑地址转换为物理地址,需设置重定位寄存器(现代操作系统)

内存管理的概念

操作系统是系统资源的管理者当然也要对内存进行相对应的管理 ,但是具体的话,需要管理哪些方面呢?
首先关于内存,肯定是有些内存已经分配的有资源还有一些是空闲的,操作系统需要管理内存空间的分配与回收;

当我们再电脑上玩游戏的时候,明明电脑运行时的内存很大,有些是远大于我们电脑的内存的,但是仍然可以运行,这个就是操作系统的虚拟技术,它从逻辑上对内存空间进行扩充;
操作系统需要提供地址转换功能负责将程序的逻辑地址与物理地址进行转换。

内存空间的分配与回收

连续分配管理方式

连续分配是指为用户进程分配的内存空间一定是连续的
单一连续分配:
内存被分为系统区和用户区。系统通常位于内存的低地址部分,用于存放和操作系统相关的数据;用户区一般在高地址部分存放用户进程相关的数据。内存中只能有一道用户程序,用户程序独占整个用户区空间。

优点:实现简单;没有外部碎片;可以采用覆盖技术扩充内存;不一定需要内存保护。
缺点:只能用于单用户、单任务的操作系统中;有内部碎片(其实就是给用户进程分配的内存有些没用上就是内部碎片);存储器利用率低。

固定分区分配:
为了能在内存中装入多个程序,并且程序之间不会互相影响,于是将整个用户分空间划分为若干个固定大小的分区,每个分区中放一个作业,这样就形成了早期的运行多道程序的内存管理方式。
固定内存分配又分为两种:一个是分区大小相等,另一个就是分区大小不等。

分区大小相等
取法灵活性,但是很适用于相同作业同时运行

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

操作系统需要加上一个分区说明表,用来分配分区和回收分区,其实这个分区说明表就是一个数据结构,里面一般含有以下属性:分区大小,起始地址,分区状态,分区编号。

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

优点:实现简单,无外部碎片
缺点当用户程序太大时,可能所有的分区都不能满足需求,此时不得不才用覆盖技来解决,但这个会降低性能;同样这种内存分配方式也会产生内存碎片,内存利用率降低。(好比一个程序需要10M内存但是扫面所有分区后没有10M内存的分区只有15M的分区,所以就分配到15M分区里面,但是这里面有5M的空间是空余的就造成了内部碎片)

动态分区分配

动态分区分配又称为可变分区分配。这种分配方式不会预先划分内存分区,而是在进程装入内存时,根据进程的进入顺序动态地建立分区,并使分区大小刚好合适。俗称来一个分配一个;因此动态分区的分区数量和大小都是可变的。

动态分区是没有内部碎片的但是有外部碎片。
外部碎片就是内存的一些分区大小太小难以利用;
如果出现空闲分区的大小总和满足程序所需要的内存空间, 但是他们空闲的内存分区每一个都不能独立满足程序所需的大小。这时可以通过拼凑技术解决外部碎片。修改空闲分区的起始地址让他们联合在一起满足程序所需内存大小。

回收内存碎片的时候可能遇到的四种情况

  • 回收之后有相邻的空闲分区
  • 回收之前有相邻的空闲分区
  • 回收前后都有相邻空闲分区
  • 回收前后都没有相邻空闲分区
非连续分配管理方式

也即是给用户进程分配内存空间的时候分配的内存是不连续的

内存空间的扩充

覆盖技术

思想:一个程序运行起来太大内存不够,那就将程序分为多个段(模块)常用的段调入内存,不常用的段需要的时候才调入。

一个固定区,存放最活跃(常用的)的程序段。固定区中的程序段在运行过程中不会调入与调出。
若干覆盖区,覆盖区不能同时访问程序段可共享的一个覆盖区,覆盖区中的程序段在运行过程中会根据需要调入调出。

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

交换技术

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

应该在磁盘的哪个位置保存被换出的进程?
磁盘分为文件去和对换区,换出的进程放在对换区

什么时候进行交换?
交换通常在许多进程运行且内存吃紧时才进行,而系统负荷降低就暂停。

应该换出哪些进程?
可以优先交换出阻塞进程;也可换出优先极低的进程;为了防止优先级低的进程饿死,有的系统会考虑进程在内存的驻留时间。

覆盖于交换的区别:

覆盖实在同一个进程中的;交换是在不同的进程之间的。

地址的转换

操作系统需要负责程序的逻辑地址与物理地址的转换
绝对装入,可重定位装入、动态运行时装入

存储的保护

保证各个级才能拿在自己的内存区域不会越界访问。其实也就是说,一个普通的进程总不能直接对操作系统的那块内存区域进行更改吧,那样太过于危险。
实现存储保护的两种方式:

  1. 设置上下限寄存器
    设置两个寄存器,一个里面装的是这个进程的物理地址的上限,两一个是物理地址下限;
    进程指令要访问某个地址时,CPU检查是否会越界。
  2. 利用重定位寄存器 届地址寄存器进行判断

重定位寄存器中存放的是进程的起始物理地址。界地址寄存器中存放的是进程的最大逻辑地址。
举一个例子就比较好理解了:比如说有一个进程1他的逻辑地址空间是0~179;物理地址空间是100-279
那么重定位寄存器中存放的就是起始物理地址100,界地址寄存器中存放的是最大逻辑地址179,如果这时申请的逻辑地址编号是80 ,那么首先要与界地址寄存器中的地址进行比较,如果比他大,那就是越界访问内存,抛出越界异常;如果比他小就证明可以访问,然后重定向寄存器中的地址会与80相加算出绝对地址(物理地址);然后去访问。

动态分区分配

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

首次适应算法

思想:每次都从低地址开始查找,直到第一个能够满足大小的空闲分区
实现:空闲分区以地址递增的顺序排序。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能够满足要求的第一个空闲分区。

两种常用的数据结构:分区表和分区链

分区表类似于以下这种

分区链类似于这种

最佳适应算法

思想:由于动态分区是连续分配内存的方式, 所以为了让各进程分配的空间必须是连续的一整片区域,因此要保证大进程到来时有大片空间供他使用,所以优先使用小的内存空间。
实现:空闲分区按照容量递增的次序链接。

这时拿分区链举例,按照内存空间容量大小排序后,如果这时来了一个进程,这个进程需要的内存大小是8M,就会从链表头依次往后进行匹配,如果找到一个分区容量大小大于等于8M,就放进这个分区,并要修改目前分区的大小,还要将整个链表重新排序。

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

最坏适应算法

思想:与最佳适应算法相反。
实现:空闲分区按照容量大小递减的顺序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能够满足要求的第一个空闲分区。

**缺点:**每次都选最大的分区进行分配,大的分区就会不断地分配成小的分区,种方式会导致较大的连续空闲分区迅速用完。如果用完后有“大进程”到来就会无处安放。

邻近适应算法

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

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

缺点:邻近适应算法的规则可能导致无论低地址、高地址部分的空闲分区都有相同的概率被使用,也就导致了高地址部分的大分区更有可能被划分为小分区,最后导致没有大分区可用(最坏适应算法的缺点)

综合来看。四种算法中,首次适应算法的效果更好。

总结

首次适应算法:从头到尾找适合的分区;分区排序顺序是按照地址从低到高;优点:综合性能最好,算法开销小,回收分区一般不需要对空闲分区对列重新排序。

最佳适应算法:优先使用更小的分区,已保留更多大分区。空闲分区的排列顺序是按照分区容量的大小递增排序的;优点:会有更多的大分区被保留下来,更能满足大进程需求;缺点:会产生喝多外部碎片,难以利用;算法开销大回收分区后可能需要对空闲分区队列重新排序。

最坏适应算法:优先使用更大的分区,以防止产生太小的不可用的碎片;空闲分区按照容量递减排序;优点:可以减少难以利用的碎片空间;缺点:大分区容易被使用完,大进程无处安放;算法开销大。

邻近适应算法:由首次适应演变而来,每次从上次查找结束为止开始查找。空闲分区以地址递增次序排序;不用每次都从低地址的小分区开始检索,算法开销小;缺点:会使高地址的大分区被用完。

分页存储的基本概念

连续分配方式的缺点

  1. 如果采用固定分区分配的方式,会导致出现内部碎片也就是相对于进程来说是和进程挨着的在分区里面,内存利用率很低。
  2. 动态内存分配会出现外部碎片,在分区的外面,虽然可以采用紧凑技术来处理这些碎片但是紧凑技术的时间代价很高。

非连续分配方式

为用户进程分配的空间不是连续的,是分散的。

分页存储管理的基本概念

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

将用户进程的地址空间也分为与页框大小相等的一个个区域,称为页或页面。每个页面也有一个编号,就是页号,页号是从0开始的。

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

分页存储逻辑地址到物理地址之间的转换

  1. 要算出逻辑地址对应的页号
  2. 要知道该页号对应页面在内存中的起始地址(其实就是该页面在物理内存中的起始地址)
  3. 要算出逻辑地址在页面内的偏移量
  4. 物理地址=页面地址+页内偏移量

页面地址和页内偏移量的计算

页号 = 逻辑地址/页面长度
页内偏移量 = 逻辑地址%页面长度

如果换算成二进制计算页面号和偏移量

如果用32个二进制位表示逻辑地址,页面大小为2^10 = 1024B = 1KB

0号页的逻辑地址空间应该是0~1023,用二进制表示应该是:
0000000000000000000000**0000000000** ~ 0000000000000000000000**1111111111**

1号页的逻辑地址空间应该是1024~2047,用二进制表示应该是:
0000000000000000000001**0000000000** ~ 0000000000000000000001**1111111111**

结论:如果一个页面大小为2^k B,用二进制数表示逻辑地址,则末尾K位即为页内偏移量(用*号括起来的),其余部分就是页号。

因此如果让每个页面的大小为2的整数幂,计算机就可以很方便的得出一个逻辑地址对应的页号和页内偏移量。

页表

页表记录进程页面和实际存放的内存之间的对应关系;

一个进程对应一张页表,进程的每一页对应一个页表项,每个页表项由“页号”“块号”组成,每个页表项的长度是相同的。因此页号是隐含的(只需要知道页表存放的起始地址和页表项长度就能算出各个页号对应的页表项存放表的位置)

基地址变换机制

基本地址变化机构:用于实现逻辑地址到物理地址转换的一组硬件机构。可以借助进程的页表将逻辑地址转换为物理地址。
通常会在系统中设置一个页表寄存器,存放页表在内存中的起始地址F和页表长度M.
进程未执行时,页表的起始地址和页表长度放在进程控制块中。当进程被调用时,操作系统内核会把他们放到页表寄存器中。

注意:页面大小是2的整数幂

  1. 页表寄存器存放的有也表的起始地址F,页表长度L,我们只需知道逻辑地址A,就能算出页号和页内偏移量(根据页面大小)
  2. 需要进行判断页号是否合法(越界中断)如果页号>=页表长度L 就会发出越界中断信号,否则继续执行(页号是从0开始的,而页表长度至少是1,因此P=L也会越界)
  3. 页表中页号P对应的页表项地址 = 页表起始地址F + 页号P * 页表项长度,取出该页表项内容b,即为内存块号。 (页表长度指的是这个页表中总共有几个页表项;页表项长度指的是每个页表项占多大的存储空间;页面大小指的是一个页面占多大的存储空间)
  4. 物理地址E = 内存块号(页框号)* L(页表长度) + 偏移量
页式管理地址中地址时一维的。只要给出一个逻辑地址系统就可以自动的算出页号页内偏移量两个部分。

其他细节:

  • 页内偏移量位数与页面大小之间的关系(用用其中一个已知条件推出另一个条件)
  • 页式管理是一维的
  • 时机应用中通常是一个页框(内存块)恰好能放入整数个页表项
  • 为了方便找到页表项,页表一般是房子啊连续的内存块中的

快表的地址变换

局部性原理

时间局部性:如果执行程序中的指令,那么不久后这条指令很有可能被再次访问;如果某个数据被访问过,不久后改数据很可能再次被访问。(因为程序中存在大量循环)
空间局部性:一旦程序访问了某个存储单元,在不久后,其附近的存储单元也有可能被再次访问。(因为很多数据在内存中都是连续存放的)

可以使用这些原理来减少访问页表的次数(引入快表)

什么是快表?

快表,又称TLB(联想寄存器),是一种访问速度比内存快很多的高速缓冲存储器,用来存放当前访问的若干页表项,以加速地址变换的过程。内存中的页表常被称为慢表。

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

  1. CPU给出逻辑地址,由某个硬件算出页号、页内偏移量,将页号与快表中的所有页号进行比较。
  2. 如果命中页号,说明要访问的页表项在快表中有副本,则直接从中取出该页对应的内存块号,再将内存块号与页内偏移量拼接成物理地址,最后访问物理地址对应的内存单元。因此若快表命中,仅需访问内存一次。
  3. 如果没有命中页号,则需要访问内存中的页表,找到对应的页表项买得到页面存放的内存块号,再将内存块号与页内偏移量拼接成物理地址,最后访问该物理地址对应的内存单元。因此若快表未命中,则访问某个逻辑地址需要访问内存两次(找到页表项后需要同时将页表号和对应的物理地址放入快表)

一般来说,快表的命中率能达到90%以上

总结

快表地址变换机构

  1. 算页号、页内偏移量
  2. 检查页号合法性
  3. 查快表,若命中即可知道页面存放的内存块号,可直接执行 5
  4. 查页表,找到存放的内存块号,并且将页表项赋值到快表中
  5. 根据内存块编号与页内偏移量得到物理地址
  6. 访问目标内存单元

快表命中只需访问内存一次,未命中访问内存两次

两级页表

单级页表存在的问题?

所有页表必须连续存放,因此当页表过大需要很大的连续空间,需要占用很多个连续的页框
在一段时间内并非所有的页面都能使用到,因此没必要让整个页表常住内存

两级页表的原理,逻辑地址转换

将一级页表再次分页
逻辑地址结构:(一级页号,二级页号,页内偏移量)
分成的二级页表:又称为 页目录表/外层页表/顶级页表

如何实现地址转换?

  1. 按照地址结构将逻辑地址才分为三个部分
  2. 从PCB中读出页目录表的初始地址,根据一级页号查询目录表找到下一级页表在内存中的存放位置
  3. 根据二级页号查表,找到最终想要访问的内存块号
  4. 结合页内偏移量得到物理地址

如何解决单级页表的问题

可以在需要访问页面时才把页面调入内存(虚拟存储技术)。也可以在页表项中增加一个标志位,永固表示该页面是否已经被调入内存。

这里的缺页中断是内中断,由操作系统内核控制。

细节问题

多级页表中,各级页表的大小不能超过一个页面。若二级页表不够,可以分为三级页表,四级页表等等更多。
多级页表的访存次数(假设没有快表机制) N级页表访问内存的次数是N+1次。

基本分段存储方式

什么是分段(类似于分页管理中的“分页”)?

按照程序自身逻辑关系将进程的地址空间划分为若干个段,每个段都有一个段名,每段从0开始编址。

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

编译的时候会将段名编译成段号来区分各个段。

分段系统的逻辑地址由段号和段内地址(段内偏移量)所组成。段号的位数决定了每个进程最多可分为几个段,段内地址位数决定了每个段的最大长度

什么是段表?(类似于分页管理中的“页表”)

段表用来记录逻辑段到时机存储地址的映射关系
每个段对应一个段表项。各段表项长度相同,由段号(隐含的)、段长、基址组成。

如何实现地址转换?

  1. 由逻辑地址得到段号
  2. 段号与段表寄存器中的段长比较,如果段长<=段号,发起越界中断
  3. 由段表起始地址和段号找到段表中的对应项(段表项的长度是相同的)
  4. 根据段表的段长,检查段内地址是否越界(段长<=段内地址产生越界中断)
  5. 由段表中的“基址+段内地址”得到最终的物理地址
  6. 访问目标单元

分段与分页管理的对比

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

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

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

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

  • 分段的用户进程地址空间是二维的,程序员在表示一个地址时,要给出段名和段内地址。

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

  • 不能被修改的代码称为存代码和可重入代码(不属于临界资源,例如只输出字符串),这样的代码是可共享的。可修改的代码是不可共享的(并发可能导致数据的不一致性)

分段管理怎样实现资源共享?
选取一个段,只需让各个进程的段表项指向同一个段即可实现共享。

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

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

段页式存储管理方式

分页、分段中最大的优缺点

分页管理

  1. 优点:内存空间利用率很高不会产生外部碎片只会有少量的页内碎片

  2. 缺点:不方便按照逻辑模块实现资源共享和保护

分段管理

  • 优点:方便按照逻辑模块进行资源共享和保护
  • 缺点:如果段长过大,为其分配很大的连续空间会很不方便管理,还会产生外部碎片(类似于动态内存分配),虽然可以采用拼凑的技术解决但是很占用时间。

分段+分页----段页式管理方式

先将进程按照逻辑地址分段,再将各段分页,再将内存空间分为大小相同的内存块/页帧/物理块/页框;再将页面装入内存块中。

逻辑地址结构

由段号、页号、页内偏移量组成。

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

在上图中,系统按字节寻址,则
段号是16-31,占16个字节,一次每个进程最多有2^16=64K个段
页号是12-15,所以占4个字节大小,每个段最多有2^4=16页
页内偏移量占12个字节,因此每个页面大小是2^12=4096=4KB

分段是对用户可见的,而将各段分页时对用户不可见的。系统会根据段内地址自动划分页内偏移量和页号;因此段页式挂你是二维的。

段表、页表

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

地址转换

逻辑地址到到物理地址的映射过程

  1. 根据逻辑地址得到段号、页号、页内偏移量;段表寄存器中还有段表起始地址和段表长度,首先判断段号是否越界,若 段号>=段表长度,则产生越界中断;否则继续执行。

  2. 段表中含有页表长度和页表存放的内存块号;查询段表,找到对应的段表项,首先要知道段表项的长度是固定的,段表项的存放地址=段表寄存器中的段表起始地址+逻辑地址中的段号*段表长度。

  3. 根据段表项查找对应的页表内存块编号,找到页表中对应的页表项,检查页号是否合法,若页号>=页表长度;则越界中断,否则继续执行。

  4. 根据页表项中的内存块号、页内偏移量得到最终的物理地址。

  5. 访问目标单元。
    最终的过程一共需要访问内存三次。也可引入快表以段号和页号为关键字查询快表,即可直接找到最终的目标页面存放位置,若快表命中仅需访问内存一次。

虚拟内存的基本概念

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

连续分配:

  • 单一连续分配
  • 固定分区分配
  • 动态分区分配

非连续分配:

  • 基本分页存储管理
  • 基本分段存储管理
  • 基本段页式存储管理
    特征
    传统存储管理方式很多暂时用不到的数据也会长期占用内存,导致内存利用率不高
    一次性:作业必须一次性全部装入内存,才能开始工作。这回造成两个问题。
  1. 作业很大时,不能全部装入内存,导致大作业无法运行;
  2. 当大量作业要求运行时,由于内存无法容纳所有作业,因此只有少量作业再运行,导致程序并发性降低。

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

虚拟内存的定义和特征

基于局部性原理,在程序装入时,可以将程序中很快就会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行。
在程序运行过程中,当访问的资源不再内存中时,由操作系统费负责将所需要的信息从外存调入内存,然后继续执行程序。

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

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

虚拟内存的三个主要特征:

多次性:作业在运行时无需一次性全部装入内存,而是允许被多次调用内存。
对换性:在作业运行期间无需资源常住内存,而是可以进行换入与换出。
虚拟性:从逻辑上扩充内存容量,使用户看到的内存容量远大于实际容量。

实现虚拟内存技术的基础是建立在离散分配内存管理上的

离散分配与连续分配最明显的区别就是:在程序执行中,当访问的资源不在内存中,这时操作系统会将所需要的信息从外存调入内存,然后继续执行程序。若内存空间不够,操作系统会将内存中暂时用不到的信息换出内存。

请求分页管理方式

回顾:虚拟内存管理的实现方式主要有三种;一种是请求分页式存储管理;还有一种是请求段式存储管理;最后一种是请求段页式存储管理。

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

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

操作系统需要提供页面置换的功能,将暂时用不到的页面换出外存。还需要提供请求调页功能,将缺失的页面从外存调入内存。

请求分页管理方式

  • 页表机制
  • 缺页中断机制
  • 地址变换机构

页表机制

思考:与基本分页管理相比,请求分页管理中,为了实现“请求调页”,操作系统需要知道每个页面是否已经调入内存;如果还没有调入,那还是需要知道该页面在外存中存放的位置。当内存空间不够的时候,要实现页面置换,操作系统需要提供某些指标来决定到底换出哪一个页面;有的页面没有被修改过,就不用在浪费时间再写入外存。有的页面修改过,就需要将外存中的旧数据覆盖,因此,操作系统也需要记录各个页面是否被修改的信息。

请求分页的页表相比于基本分页存储管理的页表,含有以下字段;页号还是隐含在操作系统中的,内存块号,状态位(表示该内存块是否已经调入内存),访问字段(记录最近被访问的次数或者最近访问时间,供置换算法选择);修改位(页面调入内存后是否被修改过),外存地址(页面在外存的存放位置)

缺页中断

找到页表项后检查页面是否已在内存,若没在内存,会产生缺页中断(操作系统进行IO操作,将页面从外存调入内存);缺页中断处理中,需要将目标页面调入内存,有必要时还要换出页面;缺页中断属于内中断,输入内中断中的“故障”,可能被系统修复的异常;一条指令在执行过程中可能产生多次缺页中断。

地址变换机构

找到页表项,需要检查页面是否在内存中;若页面不在内存中,需要请求调页;若内存空间不够,需要调出页面;页面调入内存后,还需要修改响应的页表项字段。

如果引入快表后,在页面没有调入内存的情况下;先查快表,快表中没找到页号再查慢表,慢表发现未调入内存,调页(其实就是调入的页面对应的表项会直接加入快表)–下次查快表(命中)–访问目标内存单元。

页面置换算法

页面置换算法决定将那个页面换出内存

页面置换算法的种类

  • 先进先出置换算法(FIFO)
  • 最佳置换算法(OPT)
  • 最近最久未使用置换算法(LRU)
  • 时钟置换算法(CLOCK)
  • 改进型的时钟置换算法

最佳置换算法(OPT)

每次选择淘汰的页面将是以后永不使用,或者在最长时间内不再被访问的页面,这样可以保证最低的缺页率。
整个过程缺页中断发生了9次,页面置换发生了6次。缺页时未必发生页面置换。若有可用的空闲内存块,就不用进行页面置换。

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

先进先出置换算法(FIFO)

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

Belady异常---当为进程分配的物理块数增大时,缺页次数不减反增的异常现象。
只有FIFO算法会产生Belady异常。另外,FIFO算法虽然实现简单,但是该算法与进程实际运行规律不适应,因为先进入的页面也有可能最经常被访问到。因此算法性能差。

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

最近最久未使用置换算法:每次淘汰的页面是最近最久未使用的页面。
实现方法:赋予每个页面对应的页表项中,用访问字段记录该页面从上次被访问以来所经历的时间t。
当需要淘汰一个页面时,选择现有页面中t值最大的,也就是最近最久没有被使用到的页面。

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

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

时钟置换算法(Clock or NRU )

循环扫描各页面第一轮淘汰访问位=0的,并将扫描过的页面访问位改为1.若第一轮没选中,则进行第二轮扫描。
优缺点:实现简单,算法开销小;但未考虑页面是否被修改。

改进型的时钟置换算法

简单的时钟置换算法仅仅考虑到一个页面最近是否被访问过。事实上,如果被淘汰的页面没有被修改过,就不需要执行IO操作调回内存。只有页面修改过才需要写回内存。
因此,除了考虑到一个页面最近有没有被访问过之外,操作系统还应该考虑页面是不是被修改过。在其他条件不变的情况下,优先淘汰没有被修改过的页面,避免IO操作。这就是改进的时钟置换算法的思想。
修改为0代表没被修改过,修改位是1表示页面被修改过。

算法规则:将所有可能被置换的页面排成一个循环队列
第一轮:从当前位置开始扫描,直到遇到第一个(0,0)的帧用于替换。(0,0)代表最近没被访问,最近还没被修改。本次扫描不修改任何标志位(访问位的数字不变)
第二轮:如果第一轮扫描失败,则重新扫描,查找第一个(0,1)【代表最近没被访问但修改过的页面】的帧用于替换。本次扫描过的访问为设置成0。
第三轮:若第二轮扫描失败,则重新扫描,查找到第一个(0,0)的帧用于替换。本次扫描不作任何修改。(最近访问过且没修改过的页面)
第四轮:若第三轮扫描失败,则重新扫描,查找到第一个(0,1)的帧用于替换。(最近访问过并且修改过的页面)。

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

优点:算法开销小,性能也不错。

页面分配策略

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

在采用了虚拟存储技术的系统中,驻留集大小一般小于进程的总大小。
如果驻留集太小就会导致缺页频繁,好比来了100个进程,但是如果驻留集大小是1就会不停的缺页中断。频繁的置换页面。如果刚开始驻留集大小为100那么就不会出现缺页中断。驻留集太小的话,实际用于进程推进的时间就会很少。
驻留集太大的话,又会导致多到程序并发度下降,资源利用率降低。

操作系统给驻留集分配大小的方式有两种。固定分配和可变分配。

  • 固定分配:操作系统为每个进程分配一组固定数目的物理快,在进程运行期间不再改变。也就是说驻留集大小在运行期间是不变的。
  • 可变分配:先为每个进程分配一定数目的物理块,再进程运行期间,可根据情况做适当的增加或减少。驻留集的大小是可变的。

局部置换:发生缺页时只能选进程自己的物理块进行置换。
全局置换:可以将操作系统保留的空闲物理块分批给缺页进程,也可以将别的进程持有的物理块置换到外存,在分配给缺页进程。

固定分配局部置换:系统为每个进程分配一定数量的物理块,缺页时只能换出进程自己的某一页

可变分配全局置换:只要缺页就分配新的物理块,可能来自空闲物理块,也可能需要换出别的进程页面
可变分配局部置换:频繁缺页的进程,多分配一些进程块;缺页率低的回收一些物理块。知道缺页率合适。

何时调入页面

预调页策略:一般在进程运行前,调入页面
请求调页策略:进程运行时,发现缺页再调页

从何处调页

需要先知道内存中对换区与文件区的定义。

对换区采用连续存储方式,速度更快;文件区采用离散存储方式,速度慢。

对换区足够大:运行数据从文件区复制到对换区,之后所有的页面调入、调出都是在内存与对换区之间进行的。
**对换区不够大:**经常不修改的数据每次都从文件区调入;会修改的数据调到对换区,需要的时候再从对换区调入。
Unix方式:第一次使用的页面都从文件区调入;调出的页面都写回对换区,再次使用时从对换区调入。

抖动/颠簸现象:页面频繁的换入换出。主要原因就是分配给进程的物理块数量(驻留集)不够。

工作集:在某段时间间隔里,进程实际访问页面的集合。驻留集大小一般不能小于工作集大小。

猜你喜欢

转载自blog.csdn.net/qq_43672652/article/details/107677105