操作系统——(3)内存管理
本文最后更新于:2 个月前
1 内存的基础知识
1.1 什么是内存?有何作用?
内存可存放数据。程序执行前需要先放到内存中才能被CPU处理——缓冲CPU和硬盘之间的速度矛盾
1.2 指令的工作原理
指令的工作基于“地址”每个地址对应一个数据的存储单元
我们写的代码要翻译成CPU能识别的指令。这些指令会告诉CPU应该去内存的哪个地址读/写数据,这个数据应该做什么样的处理。
1.3 三种装入方式
1.3.1 绝对装入
在编译时,如果知道程序将放到内存中的哪个位置,编译程序将产生绝对地址的目标代码。装入程序按照装入模块中的地址,将程序和数据装入内存
编译、链接后得到的装入模块的指令直接就使用了绝对地址(编译的时候就把逻辑地址转换成物理地址)
绝对装入只适用于单道程序环境
1.3.2 可重定位装入
静态重定位:又称可重定位装入。编译、链接后的装入模块的地址都是从0开始的,指令中使用的地址、数据存放的地址都是相对于起始地址而言的逻辑地址。可根据内存的当前情况,将装入模块装入到内存的适当位置。装入时对地址进行“重定位”,将逻辑地址变换为物理地址(地址变换是在装入时一次完成的)
1.3.3 动态运行时装入
动态重定位:又称动态运行时装入。编译、链接后的装入模块的地址都是从0开始的。装入程序把装入模块装入内存后,并不会立即把逻辑地址转换为物理地址,而是把地址转换推迟到程序真正要执行时才进行。因此装入内存后所有的地址依然是逻辑地址。这种方式需要一个重定位寄存器的支持
采用动态重定位时允许程序在内存中发生移动
并且可将程序分配到不连续的存储区中;在程序运行前只需装入它的部分代码即可投入运行,然后在程序运行期间,根据需要动态申请分配内存;便于程序段的共享,可以向用户提供一个比存储空间大得多的地址空间
1.4 从写程序到程序运行
1.5 链接的三种方式
1.静态链接:在程序运行之前,先将各目标模块及它们所需的库函数连接成一个完整的可执行文件(装入模块)之后不再拆开
2.装入时动态链接:将个目标模块装入内存时,边装入边链接的链接方式
3.运行时动态链接:在程序执行中需要该目标模块时,才对它进行链接。其优点是便于修改和更新,便于实现对目标模块的共享
2 内存管理的概念
2.1 内存空间的分配和回收
2.2 内存空间的扩展
2.3 地址转换
为了使编程更方便,程序员写程序时应该只需要关注指令、数据的逻辑地址。而逻辑地址到物理地址的转换(这个过程称为地址重定位)应该由操作系统负责,这样就保证了程序员写程序时不需要关注物理内存的实际情况
2.4 内存保护
1)在CPU中设置一对上、下限寄存器,存放进程的上、下限地址。进程的指令要访问某个地址时,CPU检查是否越界
2)采用重定位寄存器(又称基址寄存器)和界地址寄存器(又称限长寄存器)进行越界检查。重定位寄存器中存放的是进程的起始物理地址。界地址寄存器中存放的是进程的最大逻辑地址
3 覆盖与交换
3.1 覆盖技术
早期的计算机内存很小,因此经常会出现内存大小不够的情况
覆盖技术的思想:将程序分为多个段(多个模块)。常用的段常驻内存,不常用的段在需要时调入内存。
内存中分为一个“固定区”和若干个“覆盖区”
需要常驻内存的段放在“固定区”中,调入后就不再调出(除非运行结束)
不常用的段放在“覆盖区”中,需要用到时调入内存,用不到时调出内存
必须由程序员声明覆盖结构,操作系统自动完成覆盖。
缺点:对用户不透明,增加了用户编程负担
3.2 交换技术
交换(对换)技术的设计思想:内存空间紧张时,系统将内存中某些进程暂时换出外存,把外存中某些已具备运行条件的进程换入内存(进程在内存与磁盘动态调度)
暂时换出外存等待的进程状态为挂起状态
挂起态又可以进一步细分为就绪挂起、阻塞挂起两种状态
注意:PCB会常驻内存不会被换出外存
4 连续分配管理方式
连续分配:指为用户进程分配的必须是一个连续的内存空间
4.1 单一连续分配
在单一连续分配方式中,内存被分为系统区和用户区。系统区通常位于内存的低地址部分,用于存放操作系统相关数据;用户区用于存放用户进程相关数据。
内存中只能有一道用户程序,用户程序独占整个用户区空间
4.2 固定分区分配
20世纪60年代出现了支持多道程序的系统,为了能在内存中装入多道程序,且这些程序之间又不会互相干扰,于是将整个用户空间划分为若干个固定大小的分区,在每个分区中只装入一道作业,这样就形成了最早的、最简单的一种课运行多道程序的内存管理方式
分区大小相等:缺乏灵活性,但是很适合用于一台计算机控制多个相同对象的场合
分区大小不等:增加了灵活性,可以满足不同大小的进程需求。根据常在系统中运行的作业大小情况进行划分
操作系统需要建立一个数据结构——分区说明表,来实现各个分区的分配与回收。每个表项对应一个分区,通常按分区大小排列。每个表项包括对应分区的大小、起始地址、状态(是否已分配)
当某用户程序要装入内存时,由操作系统内核程序根据用户大小检索该表,从中找到一个能满足大小的、未分配的分区,将之分配给该程序,然后修改状态为“已分配”
优点:实现简单,无外部碎片
缺点:
1)当用户程序太大时,可能所有的分区都不能满足需求,此时不得不采用覆盖技术来解决,但这又会降低性能
2)会产生内部碎片,内存利用率低
4.3 动态分区分配
动态分区分配又称为可变分区分配。这种分配方式不会预先划分内存分区,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。因此系统分区的大小和数目是可变的
4.3.1 系统要用什么样的数据结构记录内存的适用情况?
1、空闲分区表
:每个空闲分区对应一个表项。表项中包含分区号、分区大小、分区起始地址等信息
2、空闲分区链
:每个分区的起始部分和末尾部分分别设置前向指针和后向指针。起始部分处还可记录分区大小等信息
4.3.2 当很多个空闲分区都能满足需求时,应该选择哪个分区进行分配?
把一个新作业装入内存时,须按照一定的动态分区分配算法,从空闲分区表(或空闲分区链)中选出一个分区分配给该作业。由于分配算法对系统性能有很大的影响,因此人们对它进行了广泛的研究
4.3.3 如何进行分区的分配与回收操作?
假设系统采用数据机构是“空闲分区表”,如何分配?
分配
1)给对应分区减少分区大小,以及改变起始位置
2)分配的进程刚好有4MB,直接在空闲分区表中删除
回收
1)回收区的后面有一个相邻的空闲分区(两个相邻空闲分区合二为一)
2)回收区的前面有一个相邻的空闲分区
3)回收区前、后各有一个相邻的空闲分区
4)回收区的前、后都没有相邻的空闲分区
新增一个表项
注:各表项的顺序不一定按照地址递增顺序排列,具体的排列方式需要依据动态分区分配算法来确定
动态分区分配没有内部碎片,但是有外部碎片
内部碎片,分配给某进程的内存区域中,如果有些部分没有用上
外部碎片,是指内存中的某些空闲分区由于太小而难以利用
如果内存中空闲空间的总和本来可以满足某进程的要求,但由于进程需要的是一整块连续的内存空间,因此这些“碎片”不能满足进程的需求。
可以通过紧凑技术来解决外部碎片
5 动态分区分配算法
动态分区分配算法:在动态区分配方式中,当很多个空闲分区都能满足需求时,应该选择哪个分区进行分配?
5.1 首次适应算法
算法思想:每次都从低地址开始查找,找到第一个能满足大小的空闲分区
如何实现:空闲分区以地址递增的次序排列。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
5.2 最佳适应算法
算法思想:由于动态分区分配是一种连续分配方式,为各进程分配的控件必须是连续的一整片区域。因此为了保证当“大进程”到来时能有连续的大片空间,可以尽可能多地留下大片的空闲区,即,优先使用更小的空闲区
如何实现:空闲分区按容量递增次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
缺点:每次选最小的分区进行分配,会留下越来越多的、很小的、难以利用的内存块。因此这种方法会产生很多的外部碎片
5.3 最坏适应算法
又称最大适应算法
算法思想:为了解决最佳适应算法的问题——即留下太多难以利用的小碎片,可以在每次分配时优先使用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用
如何实现:空闲分区按容量递减次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区
缺点:每次都选最大的分区进行分配,虽然可以让分配后留下的空闲区更大,更可用,但是这种方式会导致较大的连续空闲区被迅速用完。如果之后有“大进程”到达,就没有内存分区可用了
5.4 邻近适应算法
算法思想:首次适应算法每次都从链头开始查找的。这可能导致低地址部分出现很多小的空闲分区,而每次分配查找时,都要经过这些分区,因此也增加了查找的开销。如果每次都从上次查找结束的位置开始检索,就能解决上述问题
如何实现:空闲分区以地址递增的顺序排列(可排成一个循环链表)。每次分配内存时从上次查找结束的位置开始查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区
6 基本分页存储管理的概念
6.1 什么是分页存储
操作系统以页框为单位为各个进程分配内存空间。进程的每个页面分别放入一个页框中。也就是说,进程的页面与内存的页框有一一对应的关系
各个页面不必连续存放,可以放到不相邻的各个页框中
6.2 重要的数据结构——页表
为了能知道的每个页面在内存中存放的位置,操作系统要为每个进程建立一张页表。
注:页表通常存在PCB(进程控制块)中
6.3 如何实现地址的转换
特点:虽然进程的各个页面是离散存放的,但是页面内部是连续存放的
如果要访问逻辑地址A,则
1)确定逻辑地址A对应的“页号”P
2)找到P号页面在内存中的起始地址(需要查页表)
3)确定逻辑地址A的“页内偏移量”
6.3.1 子问题:如何确定一个逻辑地址对应的页号、页内偏移量?
结论:如果每个页面大小为2^kB,用二进制数表示逻辑地址,则末尾K位即为页内偏移量,其余部分就是页号
结论:如果页面大小刚好是2的整数幂,则只需把页表中记录的物理块号拼接上页内偏移量就能得到对应的物理地址
6.4 逻辑地址结构
7 基本地址变换机构
基本地址变换机可以借助进程的页表将逻辑地址转换为物理地址
通常会在系统中设置一个页表寄存器(PTR),存放页表在内存中的起始地址F和页表长度M。
进程未执行时,页表的始址和页表长度放在进程控制块(PCB)中,当进程被调度时,操作系统内核会把它们放在页表寄存器中
在分页管理(页式管理)的系统中,只要确定了每个页面的大小,逻辑地址结构就确定了。因此,页式管理中地址是一维的。即,只要给出一个逻辑地址,系统就可以自动地算出页号、页内偏移量两个部分,并不需要显式地告诉系统这个逻辑地址中,页内偏移量占多少位
7.1 对页表项大小的进一步探讨
每个页表项的长度是相同的,页号是“隐含”的
结论:理论上,页表项长度为3B即可表示内存块号的范围,但是,为了方便页表的查询,常常会让一个页表项占更多的字节,使得每个页面恰好可以装得下整数个页表项
8 具有快表的地址交换机构
8.1 什么是快表(TLB)
快表,又称联想寄存器,是一种访问速度比内存快很多的高速缓存(TLB不是内存!),用来存放最近访问的页表项的副本,可以加速地址变换的速度。与此对应,内存中的页表常称为慢表
8.2 能否把整个页表都放在TLB中?
8.3 引入快表后,地址的变换过程
由于查询快表的速度比查询页表的速度快很多,因此只要快表命中,就可以节省很多时间
因为局部性原理,一般来说快表的命中率可以达到90%以上
8.4 局部性原理
时间局部性:如果执行了程序中的某条指令,那么不久后这条指令很有可能再次执行;如果某个数据被访问过,不久之后该数据很可能再次被访问。(因为程序中存在大量的循环)
空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问。(因为很多数据在内存中都是连续存放的)
上小节介绍的基本地址变换机构中,每次要访问一个逻辑地址,都需要查询内存中的页表。由于局部性原理,可能连续很多次查到的都是同一个页表项
TLB和普通Cache的区别——TLB中只有页表项的副本,而普通Cache中可能会有其他各种数据的副本
9 两级页表
9.1 单级页表存在的问题
9.2 如何解决单级页表的问题?
9.2.1 如何解决进程在内存中必须连续存储的问题?
将进程地址空间分页,并为其建立一张页表,记录各页面的存放位置
可以将长长的页表进行分组,使每个内存刚好可以放入一个分组
另外,要为离散分配的页表在建立一个页表,称为页目录表,或称外层页表,或称顶层页表
9.3 两级页表的原理、地址结构
问题二:没必要让整个页表常驻内存,因为进程在一段时间内可能需要访问某几个特定的页面
可以在需要访问页面时才把页面调入内存(虚拟存储技术)。可以在页表项中增加一个标志位,用于表示该页面是否已经调入内存
若想访问的页面不在内存中,则产生缺页中断(内中断),然后将目标页面从外存调入内存
9.4 需要注意的几个细节
1.若采用多级页表机制,则各级页表的大小不能超过一个页面
2.两级页表的访存次数分析(假设没有快表机构)
第一次访存:访问内存中的页目录表
第二次访存:访问内存中的二级页表
第三次访存:访问目标内存单元
10 基本分段存储管理
10.1 分段
进程的地址空间:按照程序自身的逻辑关系划分为若干个段,每个段都有一个段名(在低级语言中,程序员使用段名来编程),每段从0开始编址
内存的分配规则:以段为单位进行分配,每个段在内存中占据连续空间,但各段之间可以不相邻
分段系统的逻辑地址结构由段号(段名)和段内地址(段内偏移量)所组成。
段号的位数决定了每个进程最多可以分为几个段
段内地址位数决定了每个段的最大长度是多少
10.2 段表
每个段对应一个段表项,其中记录了该段在内存中的起始位置(又称“基址”)和段的长度
各个段表项的长度是相同的。由于段表项长度相同,因此段号可以是隐含的,不占存储空间。若段表项存放的起始地址为M,则K号段对应的段表项存放的地址为M+K*6
10.3 地址变换
10.4 分段、分页管理的对比
页是信息的物理单位。分页的主要目的是为了实现离散分配,提高内存利用率。分页仅仅是系统管理上的需要,完全是系统行为,对用户是不可见的。
段是信息的逻辑单位。分段的主要目的是更好地满足用户需求。一个段通常包含着一组属于一个逻辑模块的信息。分段对用户是可见的,用户编程时需要显式地给出段名。
页的大小固定且由系统决定。段的长度却不固定,决定于用户编写的程序。
分页的用户进程地址空间是一维的,程序员只需给出一个记忆符即可表示一个地址。
分段的用户进程地址空间是二维的,程序员在标识一个地址时,既要给出段名,也要给出段内地址。
分段比分页更容易实现信息的共享和保护。
不能修改的代码称为纯代码或可重入代码(不属于临界资源),这样的代码是可以共享的。可修改的代码是不能共享的(比如,有一个代码段中有很多变量,各进程并发地同时访问可能造成数据不一致)
11 段页式管理方式
11.1 分页、分段的优缺点分析
11.2 段页式管理
11.3 段页式管理的逻辑地址结构
11.4 段表、页表
一个进程会对应一个段表,但是一个进程会对应多个页表
12 虚拟内存的基本概念
12.1 传统存储管理方式的特征、缺点
一次性:作业必须一次性全部装入内存后才能开始运行。这回造成两个问题:
1)作业很大时,不能全部装入内存,导致大作业无法运行
2)当大量作业要求运行时,由于内存无法容纳所有作业,因此只有少量作业能运行,导致多道程序并发度下降
驻留性:一旦作业被装入内存,就会一直驻留在内存中,直至作业运行结束。事实上,在一个时间段内,只需要访问作业的一小部分数据即可正常运行,这就导致内存中会驻留大量的、暂时用不到的数据,浪费了宝贵的内存资源
12.2 局部性原理
12.3 虚拟内存的定义和特征
基于局部性原理,在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行
在程序执行过程中,当访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序
若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存
在操作系统的管理下,在用户看来似乎有一个比实际内存大得多的内存,这就是虚拟内存
12.4 如何实现虚拟内存技术
虚拟内存技术,允许一个作业分多次调入内存。如果采用连续分配方式,会不方便实现。因此,虚拟内存的实现需要建立在离散分配的内存管理方式基础上
13 请求分页管理方式
13.1 页表机制
与基本分页管理相比,请求分页管理中,为了实现“请求调页”,操作系统需要知道每个页面是否已经调入内存;如果还没有调入,那么也需要知道该页面在外存中存放的位置
当内存空间不够时,要实现“页面置换”,操作系统需要通过某些指标来决定到底换出哪个页面;有的页面没有被修改过,就不用再浪费时间写回外存。有的页面被修改过,就需要将外存中的旧数据覆盖,因此,操作系统也需要记录各个页面是否被修改的信息
13.2 缺页中断机构
在请求分页系统中,每当要访问的页面不在内存时,便产生一个缺页中断,然后由操作系统的缺页中断处理程序处理中断。
此时缺页的进程阻塞,放入阻塞队列,调页完成后将其唤醒,放回就绪队列。
如果内存中有空闲块,则为进程分配一个空闲块,将所缺页面装入该块,并修改页表中相应的页表项。
如果内存中没有空闲块,则由页面置换算法选择一个页面淘汰,若该页面在内存期间被修改过,则要将其写回内存。未修改过的页面不用写回外存
缺页中断是因为当前执行的指令想要访问的目标页面未调入内存而产生的,因此属于内中断。
一条指令在执行期间,可能产生多次缺页中断(例如:将逻辑地址A中的数据复制到逻辑地址B,而A、B属于不同的页面,则有可能产生两次中断)
13.3 地址变换机构
13.4 补充细节:
14 页面置换算法
14.1 最佳置换算法(OPT)
最佳置换算法:每次选择淘汰的页面将是以后永不使用,或者在最长时间呢不再被访问的页面,这样就可以保证最低的缺页率(缺页中断次数/访问页面次数)
最佳置换算法可以保证最低的缺页率,但实际上,只有进程执行的过程中才能知道接下来会访问到的是哪个页面。操作系统无法提前预判页面的访问序列。因此,最佳置换算法是无法实现的
14.2 先进先出置换算法(FIFO)
先进先出置换算法:每次选择淘汰的页面是最早进入内存的页面
实现方法:把调入内存的页面根据调入的先后顺序排成一个队里,需要换出页面时选择队头页面即可。队列的最大长度取决于系统为进程分配了多少个内存块
Belady异常——当为进程分配的物理块数增大时,缺页次数不减反增的异常现象
只有FIFO算法会产生Belay异常。另外,FIFO算法虽然实现简单,但是该算法与进程实际运行时的规律不适应,因为先进入的页面也有可能最经常被访问。因此,算法性能差
14.3 最近最久未使用置换算法(LRU)
14.4 时钟置换算法
时钟置换算法是一种性能和开销较均衡的算法,又称CLOCK算法,或最近未用算法
该算法的实现需要专门的硬件支持,虽然算法性能好,但是实现困难,开销大
14.5 改进型的时钟置换算法
简单的时钟置换算法仅考虑到了一个页面最近是否被访问过。事实上,如果被淘汰的页面没有被修改过,就不需要执行I/O操作写回外存。只有被淘汰的页面被修改过时,才需要写回外存
15 页面分配策略、抖动、工作集
15.1 页面分配策略、置换策略
固定分配:操作系统为每一个进程分配一组固定数目的物理块,在进程运行期间不再改变。即,驻留集大小不变
可变分配:先为每个进程分配一定数目的物理块,在进程运行期间,可根据情况做适当的增加或减少。即,驻留集大小可变
局部置换:发生缺页时只能选进程自己的物理块进行置换
全局置换:可以将操作系统保留的空闲物理块分配给缺页进程,也可以将别的进程持有的物理块换到外存,再分配给缺页进程
可变分配
可变分配全局置换:只要缺页就给分配新物理块
可变分配局部置换:要根据发生缺页的频率来动态地增加或减少进程的物理块
15.2 何时调入页面
15.3 从何处调入页面
15.4 抖动(颠簸)现象
15.5 工作集
工作集:指在某段时间间隔里,进程实际访问页面的集合
一般来说,驻留集大小不能小于工作集大小,否则进程运行过程中将频繁缺页
16 内存映射文件
内存映射文件——操作系统向上层程序员提供的功能(系统调用)
方便程序员访问文件数据
16.1 传统的文件访问方式
16.2 内存映射文件
多个进程可以映射同一个文件,实现共享
在物理内存中,一个文件对应同一份数据,当一个进程修改文件数据时,另一个进程可以立马”看到“