1 内存管理的内容
内存管理的内容有: 内存空间的分配与回收、内存空间的扩充、地址转换、存储保护。
内存空间的分配与回收主要是为了解决以下问题:
(1) 操作系统如何为进程分配空间。
(2) 对内存空间的管理,如哪些空间已经被分配,哪些空间还空闲。
(3) 进程结束后如何将进程内存空间回收的问题。
内存扩展主要是为了解决下面问题:
如果一个软件的运行内存超过了物理内存,即无法将软件运行需要全部数据全部放入内存,这时操作系统怎么处理?
地址转换是为了解决:操作系统怎么实现逻辑地址与物理地址的转换。
逻辑地址到物理地址的转换称为地址重定位。之前已经说过,有三种方式:绝对装入、静态重定位和动态重定位。
内存保护是为了解决:进程并发执行前进,如何保证各个进程在各自的存储空间内运行,互不干扰。
2 内存保护
内存保护是保证各个进程在自己的内存空间内运行,不会越界访问。
内存保护有两种方式:设置上下限寄存器、重定位寄存器和界地址寄存器判断
2.1 设置上下限寄存器
在CPU中设置一对上、下限寄存器,存放进程的上、下限地址。进程的指令要访问某个地址时,CPU检查是否越界。
如下,假设进程1的逻辑地址空间为0-179,实际物理地址为100-279,如果进程想要访问的地址超过这个范围时,访问会被阻止。
2.2 重定位寄存器+界地址寄存器
采用重定位寄存器(又称基址寄存器)和界地址寄存器(又称限长寄存器)进行越界检查。重定位寄存器中存放的是进程的起始物理地址。界地址寄存器中存放的是进程的最大逻辑地址。
如果CPU想访问一个逻辑地址,先和界地址寄存器的值比较,如果比界地址寄存器的值大,则抛越界异常,如果没有越界,则与重定位寄存器中的起始物理地址相加,判断是否越界。
以进程1为例,物理地址100~279,所以重定位寄存器值100,界地址寄存器值179,当进程1要访问逻辑地址为80的内存空间时,因为80 < 179,所以第一个判断没有抛异常;然后在和重定位寄存器的值相加得到真实访问的物理地址为180 < 279,所以允许访问 。
3 内存空间扩充
内存空间的扩充是为了让那些大小超过了物理内存总和的程序运行。
内存空间扩充有两种方式:覆盖技术和交换技术。
3.1 覆盖技术
覆盖技术的思想:将程序分为多个段(模块),常用的段常驻内存,不常用的段在需要时调用内存。
内存中分为一个“固定区”和若干个“覆盖区”。
需要常驻内存的段放在固定区中,调入后在运行结束前就不再调出。不常用的段放在覆盖区,需要用到的时候调入内存,用不到时调出内存。
必须由程序员声明覆盖结构,操作系统完成自动覆盖。
缺点:对用户不透明,增加了编程的难度。覆盖技术只用于早期的操作系统。
3.2 交换技术
交换技术的设计思想:当内存空间紧张时,系统将内存中某些进程暂时换出外存,把外存中某些已经具备运行条件的进程换入内存(进程在内存与磁盘间的调度——中级调度)。
问题一:被换出的进程保存在磁盘(外存)的什么位置?
具有对换功能的操作系统中,通常把磁盘空间分为 文件区和 对换区两个部分。文件区主要用于存放文件,主要 追求存储空间利用率,因此对文件区空间管理采用 离散分配方式;对换区空间只占磁盘空间的一小部分, 被换出的进程数据就存放在对换区。由于对换的速度直接影响到系统的整体速度,因此对换区空间的管理主要追求换入和换出速度,因此通常对换区采用 连续分配方式。总之,对换区的I/O速度比文件区的更快。
问题二:什么时候应该交换?
交换通常在许多进程运行且内存吃紧时进行,而系统负荷降低就暂停。
问题三:应该换出哪些进程?
优先考虑换出阻塞进程、优先级低的进程。为了防止优先级进程被调入内存后很快又被换出,所以需要考虑进程在内存中的驻留时间。
注:PCB会常驻内存,不会被换出外存。PCB中保存着进程在外存中位置等。
4 内存空间的分配与回收
内存空间的分配分为连续分配和不连续分配。这里只说连续分配。
连续分配:指为用户进程分配的必须是一个连续的内存空间。
4.1 单一连续分配
在单一连续分配方式中,内存被分为系统区和用户区。系统区通常位于内存的低地址部分,用于存放操作系统相关的数据;用户区用于存放用户进程相关数据。
内存中只能有一道用户程序,用户程序独占整个用户区空间。
优点:实现简单,无外部碎片。
缺点:只能用于单用户、单任务的操作系统,有内部碎片,存储器利用率极低。
内部碎片:分配给某进程的内存区域中,如果有些区域没有用上,就是“内部碎片”。
外部碎片:指内存中的某些空闲分区由于太小而难以利用。
4.2 固定分区分配
多道程序的系统的出现,为了能在内存中载入多道程序,将用户空间划分为若干个固定大小的分区,在每个分区中只装入一道作业。
固定分区分配分为:分区大小相等和分区大小不等。
分区大小相等:缺乏灵活性,但是适用于用一台计算机控制多个相同对象的场合。
分区大小不等:增加了灵活性,可以满足不同大小的进程需求,可以根据常在系统中运行的作业大小情况进行划分。
如何管理这些分区
如何管理这些分区——分区说明表,来实现各个分区的分配与回收。每个表项对应一个分区,通常按分区的大小排列。每个表项包括对应分区的大小,起始地址、状态。
当某用户程序要装入内存时,由操作系统内核程序根据用户程序大小检索该表,从中找到一个满足大小的、未分配的分区,将之分配给该程序,然后修改为已分配。
优点:无外部碎片,实现简单。
缺点:(1) 当用户程序太大时,可能所有的分区都不能满足需求,此时不得不采用覆盖技术来解决,但这又会降低性能。(2) 会产生内部碎片,内存利用率低。
4.3 动态分区分配
动态分区分配又称可变分区分配。这种分配方式不会预先划分内存分区,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小恰好适合进程的需要。
假设某计算机内存大小为64MB,系统区8MB,用户区56MB….
系统用什么样的数据结构记录内存使用情况?——空闲分区表或空闲分区链
当一个新的进程进入内存时,须按照一定的动态分区分配算法,从空闲分区表(或空闲分区链)中选出一个分区分配给该进程。有四种动态分区分配算法
4.3.1 首次适应算法
算法思想:每次都从低地址开始查找,找到第一个能满足大小的空闲分区。
实现:空闲分区以地址递增的次序排列。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到第一个空闲分区。
4.3.2 最佳适应算法
算法思想:由于动态分区是一种连续分配方式,为各进程分配的空间必须是连续一整片区域。因此为了保证当大进程到来时有连续的大片空间,可以尽可能多的留下大片的空闲去,即优先使用更小的空间。
实现:空闲分区按容量递增次序排序。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足的第一个空闲分区。
缺点:每次都选最小的分区进行分配,会留下越来越多的、很小的、难以利用的内存块。因此这种方法会产生很多的外部碎片。每次有新的进程分配空间后,由于要维持内存空间从小到大的顺序,所以需要调整空闲数据表或空闲数据链的结构。
4.3.3 最坏适应算法
算法思想:为了解决最佳适应算法的问题——有太多外部碎片,可以在每次分配时使用最大的连续空闲区,这样分配后剩余空闲区不会太小。
实现:空闲分区按容量递减次序链接。每次分配空间时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
缺点:每次都选最大的分区进行分配,虽然可以让分配后留下的空闲去更大,更可用,但是这种方式会导致较大的连续空闲去被迅速使用完。如果之后又有大进程达到,就没有内存分区可用了。
4.3.4 临近适应算法
算法思想:首次使用算法每次都从链头开始查找。这可能会导致低地址部分出现很多小的空闲分区,而每次分区都要经过这些分区,因此也增大了查找的开销。如果每次都从上次查找结束的位置开始检索,就能解决上述问题。
实现:空闲分区以地址递增的顺序排序,可排成一个循环链表。每次分配内存时从上次查找结束的位置开始查找,找到大小能满足要求的第一个空闲分区。
首次适应算法每次都要从头查找,每次都需要检索低地址的小分区,但是这种规则也决定了当低地址部分有更小的分区可以满足要求时,会更有可能用到低地址的小分区,也有可能把高地址部分的大分区保留下来(最佳适应算法的优点。)
邻近适应算法的规则可能会导致无论是低地址、高地址部分空闲分区都有相同的概率被使用,也就导致了高地址部分的大分区更有可能被使用,划分为小分区,最后导致无大分区可用(最大适应算法的缺点)。
综合来看,四种算法中,首次适应算法的效果反而更好。
4.4 内存空间的回收
内存空间回收比较简单,如果回收内存后,回收区前后可以合并的化,就将两个或两个以上的空闲去合并为一个即可。
以空闲分区表为例
如果进程4被回收,那么就将那进程4占用的内存和前后的空闲区合并为一个空闲区即可。
同理,如果回收进程的前后没有空闲区,那么在空闲表中增加一个分区即可。