第三章 内存管理

3. 内存管理

3.1 内存管理的概念

3.1.1 内存管理的基本原理和要求

内存的基础知识

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

内存管理的概念

操作系统作为资源的管理者,需要管理那些?

  1. 负责内存空间的分配与回收。
  2. 提供某种技术从逻辑上对内存空间进行扩充(虚拟技术
  3. 提供地址转换功能,负责程序的逻辑地址物理地址的转换。
  1. 内存保护,保证各进程在各自存储空间内运行,互不干扰。
    • 在CPU中设置一对上下限寄存器,存放进程的上,下限地址。进程的指令要访问某个地址时,CPU检查是否越界。
    • 采取重定位寄存器(存放进程的起始物理地址 )和界地址寄存器(存放进程中最大的逻辑地址
进程的内存映像

#define不会单独分配空间,而是在编译的时候就将代码中的X替换成了1024了,它虽然放在紫色区域,但它和常量截然不同。

css 复制代码
[ 进程 ](内存)
-----内核区:
		----- 进程控制块PCB。
		----- 内核栈,内核数据结构
-----用户区
		-----用户栈:在函数大括号内定义的局部变量,函数调用时传入的参数。
		-----共享库的存储映射区:被调用的库函数
		-----堆:由malloc/free分配回收的数据
		-----未初始化的数据段(BSS):未初始化的全局/静态变量
		-----已初始化数据段(Data):已初始化的全局/静态变量
		-----只读代码/数据:程序代码。由const关键字修饰的常变量
-----未使用区(预留空间)

3.1.2 连续分配管理方式

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

单一连续分配

内存被分为了系统区用户区 ,内存中只能有一道用户程序,用户程序独占整个用户区域空间。如下图中,只能有一个进程A

  • 优点:实现简单,无外部碎片。
  • 缺点:只能用于单用户,单任务的操作系统中,有内部碎片;存储器利用率低。
固定分区分配

为了能在内存中装入多道程序,且这些程序之间又不会相互干扰,于是将整个用户空间划分为若干个固定大小的分区。

  • 分区大小相等:大小固定,不能满足不同大小的进程需求,缺乏灵活性。
  • 分区大小不等:增加了灵活性,nice。

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

  • 优点:实现简单,无外部碎片
  • 缺点:当用户程序太大时候,可能所有分区都不满足,此时不得不采取覆盖技术来解决,但这又会降低性能,而且也会产生内部碎片,内存占用率低。
动态分区分配

这种分配方式不会预先划分内存分区,而是在进程装入内存时,根据进程的大小动态地建立分区

操作系统用什么样的数据结构记录内存使用情况

  • 空闲分区表
  • 空闲分区链

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

当一个新作业装入内存时,需按照一定的动态分区分配算法,从空闲分区表和空闲分区链中选择一个。

如何进行分区的分配与回收

如果两个空闲分区相邻,需要进行合并。如果回收区前后都有,同样也得合并。

如果回收前后都没有空闲分区,就需要新增空闲分区。

各表的顺序不一致,具体要看动态分区分配算法来确定。

  • 动态分区没有内部碎片,但是有外部碎片,有些空闲分区太小了,虽然加起来满足,但是无法连续,所以用不上。 (可以通过紧凑技术来解决碎片)
动态分区分配算法

首次适应算法(First Fit)

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

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

最佳适应算法

  • **算法思想:**由于动态分区分配是一种连续分配方式,因此为了大进程到来能有连续的大片空间,可以尽可能多的留下大片的空闲区,优先使用小的空闲区。

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

  • 缺点:会留下越来越多,很小的,难以利用的碎片,产生了很多外部碎片。

最坏适应算法

  • 算法思想: 为了解决最佳适应算法产生的外部碎片,可以在每次分配优先使用最大的连续空闲区,这样分配后剩余的空闲就不会太小。
  • 如何实现: 空闲分区按容量依次递减,每次分配内存时,顺序查找空闲分区表(链),找到大小能满足要求的第一个空闲分区。
  • 缺点:每次都选择大分区分配,但是如果大分区用完,之后又有大进程来了,就没有内存分区可以用了。

邻近适应算法

  • 算法思想: 首次适应算法每次都从链头开始查找,这也导致了低地址部分会出现很小的空闲分区,每次分配查找,都要经过这些分区,也增加查找的开销,如果可以每次都从上次查找结束的位置开始索引,就能解决上述问题。
  • 算法实现: 空闲分区以地址递增排序(可排成一个循环队列)每次从上次查找结束的位置开始查找空闲分区表(链),找到一个合适的分区。

3.1.3 基本分页存储管理

内存 空间分为一个个大小相等的分区 ,每个分区就是页框/页帧/物理块,页框号从0开始。

进程的逻辑地址 空间也分为与页框大小相等的一个个部分,每个部分被称为页/页面页号也是从0开始。

操纵系统以页框为单位为各个进程分配 内存空间,进程的页面内存的页框一一对应的关系。

各个页面不必连续存放,可以放到不相邻的各个页框中。

页表

为了知道进程的每个页面在内存中哪里,操作系统为每一个进程创建了一种数据结构---页表,其存放在PCB(进程控制块)中。

快表

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

两级页表

单级页表存在的问题

  1. 页表必须连续存放,因此,当页表很大时,需要占用很多个连续的页框
  2. 没必要让整个页表常驻内存,因为进程在一段时间内可能只需要访问某几个特定的页面

我们可以为页表再次创建一个"页表",称为页目录表,外层页表,顶层页表。

对于问题2,我们可以在需要访问页面的时候再把页面调入内存(虚拟内存存储技术) 给一个页表项增加一个标志位,若访问的页面不在内存,则产生缺页中断,然后将页面从外存调入内存。

3.1.4 基本分段存储管理

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

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

段表

程序分多个段,各段离散的装入内存,需从物理内存中找到各个逻辑段存放的位置,因此,每个进程建立了一张段映射表。--- "段表"

  1. 每个段对应一个段表项,其中记录了该段在内存中的起始位置(又称基址)和段长。
  2. 各个段表项的长度是相同的,段号可以省略。

段表也可以引入快表机构,加快地址变换速度。

分页和分段的对比

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

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


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

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


对比点 逻辑地址 物理地址
谁生成 CPU(程序) 内存硬件
用户可见吗 用户/程序员可见 用户不可见
存在位置 进程虚拟地址空间 真实内存单元
转换关系 通过段表、页表、段页式映射 最终访问用物理地址

逻辑地址:

分页是由页号和页内偏移量组成。

分段是由段号和段内偏移量组成。

物理地址:

分页 的用户进程地址空间为一维 的,程序员只需要给出一个记忆符表示地址就好了。

分段 的用户进程地址空间为二维 的,需要标记段长度 ,和段地址


分段分页 更容易实现信息的共享和保护,因为分段是按照逻辑划分,它共享和保护这段逻辑就好了,而分页很有可能出现整个页框中一半是需要保护的,另一半不需要保护,就会混乱。


3.1.5 段页式管理

段页式系统的逻辑地址由段号,页号,页内地址(页内偏移量)组成。

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

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

每个页面对应一个页表项,里面含有页号,页面存放的内存块,每个页表项长度相同,页号也是隐含的。

特性 分页(Paging) 分段(Segmentation) 段页式(Segmentation with Paging)
划分依据 固定大小的页划分逻辑空间与物理内存 按**逻辑模块(段)**划分 先按逻辑段 划分,再在段内按划分
优点 1. 消除外部碎片2. 支持虚拟内存,分配简单 1. 符合程序逻辑 ,支持共享与保护 1. 结合两者优点:既有逻辑保护/共享 ,又消除外部碎片
缺点 1. 产生内部碎片 2. 共享/保护不灵活 1. 产生外部碎片2. 段长不固定,管理复杂 1. 地址转换复杂(需两级查表)2. 硬件开销大
透明性 对程序员透明 对程序员可见 对程序员部分可见(逻辑上按段,物理上按页)
地址结构 [页号, 页内偏移] [段号, 段内偏移] [段号, 页号, 页内偏移]
典型用途 现代OS的虚拟内存实现 提供模块级共享/保护 现代主流处理器(x86)使用,如段页结合管理内存

3.2 虚拟内存管理

3.2.1 虚拟内存的基本概念

传统存储管理连续分配非连续分配,很多用不到的数据也会长期占用内存,导致内存利用率不高。

  • 一次性:作业必须一次性全部装入内存后才能开始运行。如果作业很大,无法装入内存,则导致大作业无法运行。
  • **驻留性:**一旦作业装入内存,就会一直留在内存中,直到作业结束,但事实上并不是进程中所有数据都要同时访问,有时候只需要一部分就行了,这就导致了浪费内存资源。

那上述的缺点,就可以用虚拟存储技术来解决。

在程序装入时,可以将程序中很快会用到的部分装入内存,暂时不用的部分留在外存。在执行过程中,所访问的信息不存在 ,再由操作系统将所需信息从外存调入内存 ,若内存空间不足 ,则由操作系统负责将内存中暂时用不到的换出到外存 ,在用户看来似乎比实际内存大的多,这就是虚拟内存,所以它具有以下三个特征:

  • 多次性:作业无需一次性全部装入内存,可多次调入内存。
  • 对换性:允许在作业运行过程中,将作业换入,换出。
  • 虚拟性:从逻辑上扩充了内存,使用户看到的内存容量,远大于实际容量。

虚拟内存的实现:

  • 请求分页存储管理
  • 请求分段存储管理
  • 请求段页式存储管理

3.2.2 请求分页管理方式

  • 请求调页:当所需数据内存中不存在时,由操作系统从外存调入内存。
  • 页面置换:当内存中空间不足时候,由操作系统将内存中暂时不用的页面换出外存。

这俩功能的实现,也需要一个数据结构来存储,在基本分页存储中有页表,在请求分页管理系统中也要有页表


3.2.3 页面置换算法

当内存中满了,需要新加页面时,就需要用到页面置换算法 ,将内存中暂时不用的信息换出到外存。页面的换入,换出需要磁盘I/O,会有较大的开销,因此,应该追求更少的缺页率

最佳置换算法(OPT)

每次选择淘汰的页面都是以后永不使用 ,或者在最长时间内不再被访问的页面 ,这样可以保证最低的缺页率,但是操作系统无法预知未来啊,所以这个算法是实现不了的,只能模拟一下。

先进先出置换算法(FIFO)

每次选择淘汰 的页面是最早进入内存的页面

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

上图可以发现,为进程分配的物理块数增大时,缺页次数不减反增,这就是Belady异常所以它的性能很差。

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

每次淘汰 的页面是最近最久未使用的页面

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

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

时钟置换算法(CLOCK)

这是一种性能和开销比较均衡的算法,又称CLOCK算法或最近未用算法

简单的CLOCK算法实现 :在页面设置一个访问位 ,将内存中的页面链接成一个循环队列,当某页被访问时,设置为1,当需要页面置换的时候,就去队列中找,如果是0就换出,是1就设置为0暂不换出,这样子即使全为1,下一轮也有页面可换,所以这个算法最多会进行两次扫描

简单时钟置换算法仅考虑到一个页面最近是否被访问过,而如果页面没有被修改,就不需要执行I/O操作写回外存。只有淘汰的页面被修改过 ,才需要写回外存。所以条件相同的时,应优先淘汰没有修改过的页面 ,避免I/O操作,这就是改进时钟置换算法的思想。修改位 = 1 / 0 表示修改/未修改。

(访问位,修改位)--->(1,1)表示近期访问又被修改。

算法实现:将所有可能被置换的页面排成一个循环队列,然后最多有四轮扫描,每轮失败则进行下一轮。

  • 第一轮:寻找(0,0)最近没有访问,没有修改的不修改任何标志位,找到后进行置换。
  • 第二轮:寻找(0,1)最近没有访问,但修改过的将访问位设置为0,若找到则进行替换。
  • 第三轮:寻找(0,0)最近访问过,没有修改不修改任何标志位,找到后进行置换。
  • 第四轮:寻找(0,1)最近访问过,并且修改过的肯定会找的,然后进行置换。

3.2.4 页框分配

**驻留集:**指请求分页存储管理中给进程分配的物理块的集合。在采用虚拟存储技术的系统中,驻留集大小比进程大小要小。

驻留集太大,会导致多道程序并发度下降。驻留集太小,会导致缺页频繁。

内存分配策略

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

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

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

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

上面可以搭配为,固定分配局部置换可变分配局部置换可变分配全局置换

策略类型 页框分配是否可变 置换范围 优点 缺点
固定分配局部置换 本进程内部 独立性强,互不影响 资源利用率低
可变分配局部置换 本进程内部 灵活,效率较高 开销较大
可变分配全局置换 系统全局 利用率最高 进程互相影响

何时调入页面:

  1. 预调页策略:主要用于进程的首次调入,由程序员指出应该调入哪些部分。
  2. 请求调页策略:进程在每次发现缺页的时候才将所缺的页面调入内存。

何处调入页面:

  1. 系统拥有足够对换区,页面的调入调出都是从对换区进行,速度快。
  2. 系统缺少足够的对换区,凡是不会修改的数据,直接从文件区调入内存,对于可能修改的数据,进入对换区。
  3. UNIX方式,运行进程之前,进程有关的数据全部放在文件区,页面第一次使用,是从文件区调入内存,如果置换的话,是换出到对换区。

刚刚换出页面又要换入内存,马上又要换出内存,这种频繁的页面调度就是抖动现象,主要原因就是分配给进程的物理块不够。

3.2.5 内存映射文件

内存映射文件 --- 操作系统像上层程序员提供的功能(系统调用)

内存映射文件 是 将磁盘文件的一部分或全部映射到进程的虚拟内存空间 ,从而允许进程像访问内存一样直接读写文件内容的一种技术。

  • 方便程序员访问文件数据。
  • 方便多个进程共享同一个文件。
相关推荐
FmixZA15 小时前
【香橙派开发笔记】中文界面与输入法配置
笔记
代码小将15 小时前
java泛型笔记
java·笔记
报错小能手15 小时前
C++笔记(基础)string基础
开发语言·c++·笔记
卡布叻_星星17 小时前
前端JavaScript笔记之父子组件数据传递,watch用法之对象形式监听器的核心handler函数
前端·javascript·笔记
aramae18 小时前
Linux开发工具入门:零基础到熟练使用(二)
linux·运维·服务器·网络·笔记
Suckerbin19 小时前
burpsuite网络安全学院: JWT attacks靶场通关
网络·笔记·安全·web安全·网络安全
智者知已应修善业20 小时前
【C++无数组矩阵对角线平均值保留2位小数】2022-11-18
c语言·c++·经验分享·笔记·算法·矩阵
环球经济报21 小时前
“常小豚苏超限定款”公益联名:以热爱之名守护生态赛场
笔记
半导体守望者1 天前
TR帝尔编码器GSD文件 PROFIBUS XML PROFINET EtherCAT 文件 ADH CDH CMV等
xml·经验分享·笔记·机器人·自动化·制造
路弥行至1 天前
C语言入门教程 | 第一讲:C语言零基础入门教程:第一个程序到变量运算详解
c语言·开发语言·经验分享·笔记·单片机·其他·课程设计