本文主要是介绍操作系统对内存的管理:分配与回收,虚拟内存,内存容量的扩充,内存保护,补充(链接方式、装入方式),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
内存:即内存条,也称主存储器(简称主存),用于存放数据。
为了缓和CPU和外存(磁盘)的速度矛盾,外存的程序先放入内存才能被CPU处理。
内存地址从0开始,每个内存地址对应一个存储单元。
存储单元的大小依据计算机的编址(按字节编址,则一个存储单元大小是1个字节;按字编址,32位计算机的一个存储单元大小是一个字即32位即4个字节,64位计算机中一个存储单元大小是一个字即64位即8个字节)。
参考:主存储器【计算机组成原理】
内存的分配与回收
内存的分配:程序装入内存时,为进程分配内存空间,并记录内存分配情况(内存分配表)。
内存的回收:程序撤销或者主动归还内存资源时,收回进程占用的内存空间,并修改内存分配表。
1、连续分配管理方式:给每个进程分配的内存区域必须是连续的。
内部碎片:内存中,已经分配给了进程,但没用上的那部分区域。
外部碎片:内存中,部分空闲区域太小,难以分配给进程。
(1-1)单一连续分配:内存的用户区只能给一道用户程序。适用于早期的单道程序系统。
内存分为系统区(存放操作系统相关的数据)和用户区(用户进程相关数据)。
采用栅栏寄存器(存放系统区和用户区的界限地址)进行内存保护,避免用户访问或修改系统区。
实现简单,没有外部碎片,但只能一道程序,内存利用率低,产生内部碎片。
(1-2)固定分区分配: 内存的用户区分成固定大小的分区(分区大小相等或者分区大小不等)。适用于多道程序。需要分区说明表(数据结构)记录各个分区的分配和回收情况,通常按分区大小排列。
实现简单,无外部碎片;但有内部碎片,内存利用率低,若所有分区都无法满足大程序,用覆盖技术解决,将降低性能。(分区大小相等)缺少灵活性,不利于大程序;(分区大小不等)增加了灵活性,满足不同大小的进程需求。
(1-3)动态分区分配(可变分区分配):在进程装入内存时,根据进程大小动态建立分区。适用于多道程序。需要空闲分区表/空闲分区链(数据结构)记录各个空闲分区。需要一定的算法动态安排分配。回收分区时需注意合并分区。
没有内部碎片,但有外部碎片。但可用“拼凑”技术解决。(“拼凑”技术即紧凑技术:若各空闲内存空间都不够分配给进程,但总和超过进程大小,将分配给各进程的空间挪到一起,将零碎的空闲空间拼凑成一个大的连续的空闲空间)
首次适应算法 (First Fit) | 最佳适应算法 (Best Fit) | 最坏适应算法 (Worst Fit) | 邻近适应算法 (Next Fit) |
---|---|---|---|
分区排序: 按地址递增 | 分区排序: 按容量递增 | 分区排序: 按容量递减 | 分区排序: 按地址递增 |
从头开始查找, 从第一个大小适合的分区分配 | 优先从大小最适合的分区分配 | 优先从容量最大的分区分配 | 从上次查找结束位置开始, 从第一个大小适合的分区分配 |
综合看性能最好。算法开销小(不需重新排序) | 大分区容易被保留下来。但容易有外部碎片,算法开销大(需按容量重新排序) | 减少难以利用的小碎片。但不利于大进程,算法开销大(需按容量重新排序) | 算法开销小(不需重新排序)。但可能会使高地址的大分区也被用完 |
动态分区分配的回收分区:
- 回收区的前后没有空闲分区:空闲分区表/空闲分区链中,添加表项/节点。
- 回收区的前或后有空闲分区:空闲分区表/空闲分区链中,修改对应的表项/节点。
- 回收区的前后都有空闲分区:空闲分区表/空闲分区链中,合并对应的表项/节点(删除一个表项/节点,并修改对应的表项/节点)。
2、非连续分配管理方式:给每个进程分配的内存空间是离散的(不连续的)。
(2-1)分页存储管理:
- 内存划分成大小相等的分区(称为页框/页帧/物理块/物理页面/内存块),每个分区有编号(称为页框号/页帧号/物理块号/物理页号/内存块号),页框号从0开始。
- 进程也划分成与页框大小相等的部分(称为页/页面),每个部分有编号(称为页号/页面号),页号从0开始。
- 以页框为单位为进程分配内存空间。程序执行时,进程的页面复制到内存中的页框中。
- 进程的各个页面在内存中的存放是离散的,但每个页面内部的存放是连续的。
- 通过页表(数据结构)来记录进程的页面与实际存放的内存块的映射关系。
页表(又称慢表):【数据结构】
- 在内存中,通常存放在系统区的进程控制块(PCB)中。
- 记录进程的页面与实际存放的内存块的映射关系(包括页号,内存中的页框号等)。
- 一个进程对应一张页表。一个页面对应一个页表项。页号从0开始。
- 页表的页表项是连续存放的,因此页号可隐含(即不记录页号,不占存储空间)。
- 页面大小:一个页面占多少存储空间。
- 页表长度:页表中有多少页表项,即有多少页面。
- 页表项长度:一个页表项占多少存储空间。
页表太大时,采用多级页表机制:
一个页面对应一个页表项,将页面分组,使用多个页表。内存中一个页框存放一个页表(页表长度 = 页框大小 / 页表项长度)。
例如:两级页表
- 二级页表(若干个):记录页面在内存的地址。
- 页目录表(又称外层页表/顶层页表,一个):所有二级页表的目录。记录二级页表在内存的地址。
(若使用虚拟存储技术,页目录表在内存,其他页表在外存中需要时才调入内存。各级页表中还将记录是否在内存、在外存的地址等信息)
地址转换机构:
页表寄存器(PTR):
存放 页表在内存中的起始地址F 和 页表长度M。
- 进程未执行前,页表在内存中的起始地址和页表长度是存放在进程控制块(PCB)中。
- 进程被调度时,操作系统内核才将页表在内存中的起始地址和页表长度放入页表寄存器中。
页表长度用于判断逻辑地址中的页号是否越界(若页号页表长度,则页号越界)。
快表(又称联想寄存器):
- 英文缩写TLB,Translation Lookaside Buffer。
- 在CPU中。访问速度比内存中的页表(慢表)快。
- 存放最近访问过的页表项的副本。(即存放页表的部分副本)
逻辑地址 到 物理地址 的转换:
逻辑地址(又称虚拟地址/相对地址):程序员视角的地址。
物理地址(又称绝对地址):实际在内存中的地址。
页号 = 逻辑地址 / 页面大小 【除法取整数部分】
页内偏移量(又称页内地址) = 逻辑地址 % 页面大小 【除法取余数部分】
若逻辑地址是二进制表示,且页面大小是2的整数幂(例如:),则:
页内偏移量为逻辑地址的末尾k位。逻辑地址的其他二进制位为页号。
页表的起始地址:进程执行前,在PCB中;进程执行时,在页表寄存器中。
页表中页面对应页表项的起始地址:页表的起始地址 + 页号 * 页表项长度
页面在内存中的起始地址:页框号 * 页框大小
实际的物理地址:页面在内存中的起始地址 + 页面偏移量
地址转换过程:
- 根据逻辑地址,获取页号和页内偏移量。
- 根据页表寄存器中的页表长度,检查逻辑地址中的页号是否越界,若越界,发出越界中断。
- 在快表中查询,若快表命中,直接获得页面在内存中的起始地址。若快表未命中,根据页表寄存器中的页表起始地址和逻辑地址中的页号,在内存的页表中查询,获取页面在内存中的起始地址。
- 根据页面在内存中的起始地址和页内偏移量,得到实际的物理地址。
- 根据实际的物理地址,在内存获取相应的数据。
(2-2)分段存储管理
- 程序按照自身逻辑分成若干个段,用户给每个段定义段名,每个段的段长不同。
- 以段为单位为进程分配内存空间。
- 各段在内存的存放是离散的;每个段的内部的存放是连续的。
- 通过段表记录进程的各段与段在内存中的地址的映射关系。
- 段表寄存器存放段表的起始地址F和段表长度M。进程未执行前,F和M存放在PCB中。
段表:
- 在内存中。记录进程的段号、段长、段在内存中的起始地址(即段基址)等。
- 一个段表项对应一个段,段表中有多少段表项就表示进程有多少段。
- 每个段表项长度相同,段表项连续存放,段号可隐含(即不占存储空间)。
段表的起始地址:进程执行前,在PCB中;进程执行时,在页表寄存器中。
段表中段对应段表项的存放地址:段表起始地址+段号*段表项长度
段在内存中的起始地址:对应段表项中的段基址
实际的物理地址:段在内存中的起始地址+段内地址
用户需提供段名和段内地址。编译程序会把段名转换为段号。
逻辑地址可拆分为段号和段内地址。段号的位数代表进程最多分多少段;段内地址的位数代表段长最大是多少。
地址转换过程:
- 通过用户提供的逻辑地址,获取段号和段内地址。
- 根据段表寄存器中的段表长度,判断逻辑地址中的段号是否越界,若段号段表长度,则越界,发出越界中断。
- 在快表中查,若快表命中,直接找到对应段表项。若快表未命中,根据段表寄存器中的段表起始地址和逻辑地址中的段号,在内存的段表中查,获得对应段表项。
- 根据段表项中的段长,判断逻辑地址中的段内地址是否越界,若段内地址段长,则越界,发出越界中断。
- 根据段表项中的段基址和逻辑地址中的段内地址,获取实际的物理地址。
- 根据实际的物理地址,从内存获取相应数据。
分页存储管理和分段存储管理的区别:
分页存储管理 | 分段存储管理 |
---|---|
以页框为单位为进程分配内存空间 | 以段为单位为进程分配内存空间 |
页框大小相同 | 段长不同 |
页表记录页号、在内存中的页框号 | 段表记录段号、段长、在内存中的起始地址 |
用户只需提供助记符 即分页的地址空间是一维的。 | 用户需提供段名和段内地址 即分段的地址空间是二维的。 |
页是信息的物理单位。 分页由系统完成,对用户不可见 | 段是信息的逻辑单位。 分段对用户可见,用户显示给出段名 |
提高内存的空间利用率。 不会产生外部碎片,会产生少量内部碎片。 | 方便用户编程,方便信息共享和保护。会产生外部碎片。 注:只有纯代码/可重入代码可以共享(纯代码/可重入代码:不能修改的信息。不属于临界资源) |
(2-3)段页式存储管理(分段和分页的结合)
- 内存分成若干个大小相等的页框。
- 程序按自身逻辑分段,各段再分成若干个大小相等的页面。页面大小与内存的页框大小相同。
- 通过段表记录各段的各页面在内存中的地址。段表包括段号、页表长度、页表在内存中的起始地址。
- 段表寄存器存放段表的起始地址F和段表长度M。进程未执行前,F和M存放在PCB中。
- 一个进程对应一个段表,一个段表项对应一个段,一个段对应一个页表,一个页表项对应一个页面。
用户提供段名和段内地址,编译程序将段名转换为段号,系统将段内地址转化为页号和页内偏移量。
逻辑地址可拆分成:段号、页号、页内偏移量(又称页内地址)。段号的位数表示进程最多分多少段,页号的位数表示每个段最多有多少页面,页内偏移量的位数表示页面大小是多少或内存的页框大小是多少。
地址转换过程:
- 根据逻辑地址,获得段号、页号、页内偏移量。
- 根据段表寄存器中的段表长度,判断逻辑地址中的段号是否越界,若段号段表长度,则越界,发出越界中断。
- 根据段号和页号,在快表中查,若快表命中,直接获得页面在内存中的页框号,跳转到8。
- 若快表未命中,根据段表寄存器中的段表起始地址和逻辑地址中的段号,获得对应段表项。
- 根据段表项中的页表长度,判断逻辑地址中的页号是否越界,若页号页表长度,则越界,发出越界中断。
- 根据段表项中的页表起始地址和逻辑地址中的页号,在页表中查询,获得对应页表项。
- 根据页表项中的页面在内存中的页框号和逻辑地址中的页内偏移量,获得实际的物理地址。
- 根据实际的物理地址,在内存获取相应的数据。
虚拟内存
(以请求分页存储管理为例)
在离散分配的基础上,物理上内存容量不变,逻辑上通过外存进行了扩充,使用户感觉内存容量比实际的大很多,就是虚拟内存。
传统存储的特征:一次性,驻留性。即程序一次性调入内存,并常驻内存,直到运行结束。
虚拟内存的特征:多次性,对换性,虚拟性。即程序存放在外存中,程序装入时,很快使用的页面先调入内存。程序运行时,用到的页面再调入内存;若内存空闲空间不够,通过一定置换算法,先将暂时用不到的页面从内存调出到外存,再将需要的页面调入内存。
虚拟内存需具备请求调页和页面置换功能。即需要的页面调入内存,空间不够时,不需要的页面调出到外存。
虚拟内存基于局部性原理。时间局部性:现在访问的信息将来可能仍访问。空间局部性:现在访问的信息附近的将来可能被访问。
地址转换过程:
- 根据逻辑地址,获取页号和页内偏移量。
- 检查页号是否越界,若越界,发出越界中断。
- 在快表中查询,若快表命中,直接获得页面在内存中的起始地址。若快表未命中,在内存的页表中查询,根据页表项的状态位,判断页面是否调入内存,若已调入内存,获取页面在内存中的起始地址,并将页表项复制到快表。若未调入内存,发生缺页中断,系统进行中断处理(将页面从外存调入内存),获得页面在内存中的起始地址,修改页表项并将页表项复制到快表。
- 根据页面在内存中的起始地址和页内偏移量,得到实际的物理地址。
- 根据实际的物理地址,在内存获取相应的数据。
传统存储管理 | 虚拟内存 |
---|---|
页表包括页号、在内存的地址。 | 页表包括页号、在内存中的地址、状态位(是否在内存)、访问字段(最近访问几次或上次访问时间,用于页面置换做参考)、修改位(调入内存后是否修改)、在外存中的地址。 |
无 | 需要缺页中断机构(需判断页面是否已在内存,若不在,则发生缺页中断,系统将页面从外存调入内存,有可能需要页面置换)。 缺页中断属于内中断。一条指令可能多次缺页中断。 |
无 | 页表项需要通过状态位检查页面是否在内存中; 若页面不在内存,需要请求调页; 若内存空间不够,需根据一定算法进行页面置换(将某页面从内存调出到外存,需要的页面调入内存); 页面调入内存后,需修改页表项,并将页表项复制到快表中。 |
页面分配策略:
驻留集:操作系统为进程分配的内存块的集合。
抖动/颠簸:分配的内存空间太小或者页面置换算法设计不当,会导致页面频繁调入调出。
工作集:某时间间隔里,进程实际被访问页面的集合。驻留集大小一般不能小于工作集大小。
页面分配、置换策略:
- 固定分配局部置换:系统给进程分配的内存空间大小固定,缺页时进程在自己的内存区域进行页面置换。
- 可变分配局部置换:系统给进程分配一定的内存空间,缺页时进程在自己的内存区域进行页面置换;系统检测到页面置换频繁或着缺页率低,将适当调整分配给进程的内存空间。
- 可变分配全局置换:系统给进程分配一定的内存空间,缺页时系统将分配空闲内存空间,若没有空闲内存空间,将任意进程的未锁定页面调出到外存,再将需要的页面调入内存。
何时调入页面:
- 预调页策略:进程运行前,进程首次调入时,由程序员指出调入部分,一次将若干个相邻的页面调入内存。
- 请求调页策略:进程运行期间,发生缺页时将页面从外存调入到内存。
从何处调入页面:
- 外存的对换区足够大:页面从外存的文件区复制到外存的对换区,在对换区和内存之间调入调出。
- 外存的对换区不大:不会被修改的数据直接从文件区调入内存(不需要写回外存,下次需要再从文件区调入内存);会被修改的数据复制到对换区,在对换区和内存之间调入调出。
- UNIX方式:第一次使用的页面都从文件区调入内存,调出的页面放入对换区,需要时再从对换区调入。
在外存和内存之间调入调出,需要启动I/O操作,将产生一定的系统开销。
缺页率 = 访问的页面不在内存的次数 / 访问页面总次数
为获取更大的虚拟地址空间,除了磁盘(硬盘),还会扩展到固态硬盘,甚至网络硬盘。
参考:虚拟存储
虚拟存储技术是内存容量扩充的一种方式。
内存保护
多道程序时,内存中存放多个进程,各个进程必须只能访问各自的内存区域,确保不会越界访问。
即本进程的内存区域:可读可写;非本进程的内存区域:不可读写;共享的内存区域:根据授权。
- 方法1:重定位寄存器(又称基址寄存器)+界地址寄存器(又称限长寄存器)。分别存放进程在内存中的起始地址和最大逻辑地址。
- 方法2:下限寄存器+上限寄存器。分别存放进程在内存中的起始地址和结束地址。
补充:
链接方式:静态链接、装入时动态链接、运行时动态链接。
- 静态链接:程序装入内存前,就已经将各目标模块和相关的库函数链接成完整的可执行文件(装入模块),也就确定了完整的逻辑地址。链接完成后,装入模块再装入内存运行。
- 装入时动态链接:程序在装入内存时,边装入边链接,形成完整的逻辑地址。链接完成后,已在内存。
- 运行时动态链接:程序在运行时,使用到的模块装入内存并链接,没有用到的模块不用装入内存链接。链接完成后,已在内存。
装入方式:绝对装入、可重定位装入(又称静态重定位)、动态运行时装入(又称动态重定位)。
- 绝对装入:单道程序系统,编译时就知道在内存的位置,编译程序将逻辑地址转为物理地址。装入程序按照装入模块中的地址将相关数据装入内存中。(装入模块中的地址已经是物理地址)
- 可重定位装入:又称静态重定位。装入模块的地址从0开始,指令或数据的地址都是相对于起始地址的逻辑地址;装入内存时对地址进行重定位,一次将逻辑地址全部转为物理地址。(装入模块中的地址是逻辑地址,装入内存时转为物理地址。运行期间不能移动,不能再申请内存)
- 动态运行时装入:又称动态重定位。装入模块的地址从0开始,指令或数据的地址都是相对于起始地址的逻辑地址;装入内存时依然是逻辑地址。需要重定位寄存器存放装入模块在内存中的起始地址。程序运行时,通过重定位寄存器中的起始地址和逻辑地址得到物理地址。(装入模块中的地址是逻辑地址,装入内存时依然是逻辑地址,运行时转为物理地址。内存地址可以不连续,运行期间可以移动)
程序放入内存运行时,系统会建立相应的进程。进程在内存中包括两部分:程序段(存放指令等)和数据段(存放变量等)。
这篇关于操作系统对内存的管理:分配与回收,虚拟内存,内存容量的扩充,内存保护,补充(链接方式、装入方式)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!