操作系统第三章---内存
基础概念
知识点框架
内存:用于存放数据的硬件。程序执行前需要先放到内存中才能被CPU处理
如何寻找内存中的数据(寻找数据的地址)
数据的地址编址方式有两种:
按字节编址,即一个字节(1B)一个地址
按字编址,即一个字(2B)一个地址
常用的数量单位
2
10
=
1
K
2
20
=
1
M
2
30
=
1
G
2^{10}=1K \qquad 2^{20}=1M\qquad 2^{30}=1G
210=1K220=1M230=1G
注:有的题目会告诉我们内存的大小,让我们确定地址长度应该是多少(即要多少个二进制位才能表示相应数目的存储单元)
举例:
内存大小4GB,即
2
32
B
2^{32}B
232B,就需要32个二进制来表示
进程的运行原理—指令
我们在指令中使用地址的时候,CPU会根据地址去寻找数据,但是实际在机器指令中并不知道该进程的数据会被放到什么位置,所以一般地址都是逻辑地址(相对地址),而不是实际物理地址。
逻辑地址:就是只需要知道进程的首地址,以及该数据相对于首地址的相对位置就可以
物理地址:就是知道该数据在内存中的地址
从写程序到程序运行
我们在编写代码的时候一般用的都是高级语言,并且还需要经过编译,下面来讲述一下过程,如下图所示
首先我们会编写程序
然后经过编译变成机器语言
再通过一定的连接变成一个完整的装入模块
最后将装入模块放入内存(而装入的时候,就是逻辑地址转换为物理地址的时候)
这个程序内一般放的都是相对位置,所以在装入的时候需要完成逻辑地址到物理地址的转换,有三种方法:绝对装入(单道程序阶段,那时还没有操作系统,全靠编译器)、静态重定位(早期的多道批处理操作系统)、动态重定位(现代操作系统)
绝对装入
绝对装入:在编译时,如果知道程序将放到内存中的哪个位置,编译程序将产生绝对地址的目标代码。装入程序按照装入模块中的地址,将程序和数据装入内存。(其实就是一开始就知道在内存中的哪个位置,直接就这么写了)
举个例子:
如果逻辑位置是80,但是一开始就知道是从100开始,那就可以直接写成180
只用于单道程序环境(因为没人抢他的内存)
程序中使用的绝对地址,可在编译或汇编时给出,也可由程序员直接赋予。通常情况下都是编译或汇编时再转换为绝对地址。(就是说一般是不用的)
静态重定位
静态重定位:又称可重定位装入。编译、链接后的装入模块的地址都是从0开始的,指令中使用的地址、数据存放的地址都是相对于起始地址而言的逻辑地址。可根据内存的当前情况,将装入模块装入到内存的适当位置。装入时对地址进行“重定位”,将逻辑地址变换为物理地址( 地址变换是在装入时一次完成的)。
这个就是在装入的时候一口气算好他的位置,然后改成物理地址
但是!!!这个缺点很严重:
在一个作业装入内存时,必须分配其要求的全部内存空间,如果没有足够的内存,就不能装入该作业。作业一旦进入内存后,在运行期间就不能再移动,也不能再申请内存空间。
动态重定位
动态重定位:又称动态运行时装入。编译、链接后的装入模块的地址都是从0开始的。装入程序把装入模块装入内存后,并不会立即把逻辑地址转换为物理地址,而是把地址转换推迟到程序真正要执行时才进行。因此装入内存后所有的地址依然是逻辑地址。这种方式需要一个 重定位寄存器的支持。
就是设置一个寄存器包含起始地址的值,然后等到寻找指令中地址的时候,作相加操作
采用动态重定位时,允许程序在内存中移动
并且可将程序分配到不连续的存储区中:在程序运行前只需装入它的部分代码即可投入运行,然后在程序运行期间,根据需要动态申请分配内存:便于程序段的共享,可以向用户提供一个比存储空间大得多的地址空间
链接
刚刚在程序编译完成以后会有很多模块(毕竟我们不可能把代码都写在一个文件里,所以会有很多个文件)
所以把这些模块集合成一个装入模块的时候我们需要进行链接,有三种方式:静态链接、装入是动态链接、运动时动态链接
静态链接
在程序运行之前,先将各目标模块及它们所需的库函数连接成一个完整的可执行文件(装入模块),之后不再拆开。(直接在没装进去之前就连接好)
装入时动态链接
将各目标模块装入内存时,边装入边链接的链接方式。(这个应该就是要装进去的时候再链接,应该是多了一丝丝修改的机会,可以插入别的代码段先进去)
运行时动态链接
在程序执行中需要该目标模块时,才对它进行链接。其优点是便于修改和更新,便于实现对目标模块的共享(这个就是要运行的时候再把它们链接起来,回旋的余地更大)
知识点回顾
内存管理
知识点框架
操作系统肯定是需要对内存进行管理的,管理的方面有内存空间的分配与回收(这个就是给进程多少内存、用完了怎么收回来等)、内存空间的扩充(这个就是运行内存小于运行软件的总内存,在逻辑上进行扩充)、地址转换(这个就是上面说的逻辑地址变成物理地址)、存储保护(这个就是保证各进程在各自存储空间内运行,互不干扰)
内存空间的分配与回收
内存空间的分配与回收有两种方式:连续分配管理方式和非连续分配管理方式
这个两种方式的区别就是:连续分配是为用户进程分配的必须是一个连续的内存空间而非连续分配就不需要是连续的了
连续分配管理方式
这个有三种方式:单一连续分配、固定分区分配和动态分区分配
单一连续分配
在单一连续分配方式中,内存被分为系统区和用户区。系统区通常位于内存的低地址部分,用于存放操作系统相关数据;用户区用于存放用户进程相关数据。
内存中只能有一道用户程序,用户程序独占整个用户区空间。
优点:实现简单;无外部碎片;可以采用覆盖技术扩充内存;不一定需要采取内存保护(eg: 早期的PC操作系统MS-DOS )
缺点:只能用于单用户、单任务的操作系统中;有内部碎片;存储器利用率极低。
内部碎片就是内存中有没有被利用起来的空间
外部碎片就是内存中的空闲区域太小了用不了
(区别就是一个能用,但是空出来了;一个有点空间但是不能用)
注:这个空间未必就是真的小了,只是相对于要进来的进程,所有空间都不满足,就出现了外部碎片
外部碎片这个问题可以用紧凑来解决,就是把位置都往前挪,合并起来
固定分区分配
20世纪60年代出现了支持多道程序的系统,为了能在内存中装入多道程序,且这些程序之间又不会相互干扰,于是将整个用户空间划分为若千个固定大小的分区,在每个分区中只装入一道作业,这样就形成了最早的、最简单的一种可运行多道程序的内存管理方式。
固定分区有两种:分区大小相等和分区大小不等
分区大小相等:缺乏灵活性,但是很适合用于用一台计算机控制多个相同对象的场合(比如:钢铁厂有n个相同的炼钢炉,就可把内存分为n个大小相等的区域存放n个炼钢炉控制程序)
分区大小不等:增加了灵活性,可以满足不同大小的进程需求。根据常在系统中运行的作业大小情况进行划分(比如:划分多个小分区、适量中等分区、少量大分区)
操作系统需要建立一个数据结构----分区说明表,来实现各个分区的分配与回收。每个表项对应一个分区,通常按分区大小排列。每个表项包括对应分区的大小、起始地址、状态( 是否已分配)
放上图片就很好理解了
优点:实现简单,无外部碎片。
缺点: a. 当用户程序太大时,可能所有的分区都不能满足需求,此时不得不采用覆盖技术来解决,但这又会降低性能; (这个就是单纯的覆盖技术会降低性能)b. 会产生内部碎片,内存利用率低。(这个就是如果有一个10MB的进程,但是分区最大只有12MB,第二档次就是8MB,那么进程只能放入12MB,这样就会有2MB的空间变成内部碎片)
动态分区分配
动态分区分配又称为可变分区分配。这种分配方式不会预先划分内存分区,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。因此系统分区的大小和数目是可变的。(eg: 假设某计算机内存大小为64MB,系统区8MB,用户区共56MB…)
这是有3个进程进入内存
就像上面的固定分区表一样,动态分区也会有一个数据结构来存储内存使用情况(通常使用空闲分区表和空闲分区链)
下图为空闲分区表(这里是上图中进程2和进程3退出进程以后的空闲分区表):
下图为空闲分区链
当有很多个空闲分区都能满足需求时,应该选择哪个分区进行分配
把一个新作业装入内存时,须按照一定的动态分区分配算法(这个算法在下面具体介绍),从空闲分区表(或空闲分区链)中选出一个分区分配给该作业。由于分配算法算法对系统性能有很大的影响,因此人们对它进行了广泛的研究
如何进行分区的分配与回收操作
如果是新来的进程进入分区1,并且大小为4MB,做如下更改
如果新来的进程放入的是分区3
下面就是回收
如果我们要回收进程4,就是把它们相邻的空闲区域合二为一
总结,不管在哪,能合并就得合并
注:各表项的顺序不一定按照地址递增顺序排列,具体
的排列方式需要依据动态分区分配算法来确定。
动态分区算法有四种:首次适应算法、最佳适应算法、最坏适应算法、邻近适应算法
首次适应算法
每次都从低地址开始查找,找到第一个能满足大小的空闲区
空闲分区以地址递增的次序排列。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
举例:
目前内存使用情况如下图所示:
一种是空闲分区表形式,一种是空闲分区链形式;进来的进程大小为15MB
按照首次适应算法,找到的第一个,就直接放进去
然后又来一个8MB的进程,这次按顺序查找是第二个分区,再次插入
这个其实也挺简单,就是从小到大找,能放进去就直接放进去(这里的空闲分区就不修改了,他没放图,我也懒得自己做,很好理解吧)
最佳使用算法
算法思想:由于动态分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片区域。因此为了保证当“大进程”到来时能有连续的大片空间,可以尽可能多地留下大片的空闲区,即,优先使用更小的空闲区。
空闲分区按容量递增次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表) ,找到大小能满足要求的第一个空闲分区。
举例:
这是目前分区使用情况,如下图所示:
空闲分区表示也如图所示:
因为他要求的是按大小来排,所以最一开始就是区域最小的,如果新进程大小是9
那么他会放入10MB区域
然后因为按照容量大小排序,因此空闲分区表的顺序需要修改
缺点:每次都选最小的分区进行分配,会留下越来越多的、很小的、难以利用的内存块。因此这种方法会产生很多的外部碎片。
最坏适应算法
又称为最大适应算法
为了解决最佳适应算法的问题----即留下太多难以利用的小碎片,可以在每次分配时优先使用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用。
如何实现:空闲分区按容量递减次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
举例:
当前内存使用情况如下图所示:
空闲分区如下所示:
因为这里每次放入都是最大容量的,所以也是按照容量来排,只是这次是最大的在上面,这次新进程的大小为3MB
他就直接放入第一个分区,因为如果连第一个分区都放不下,后面容量更小的分区也就放不下了
后面的图我就不放了,反正都是这个意思,空闲分区的排列也是遵循相同原则
缺点:每次都选最大的分区进行分配,虽然可以让分配后留下的空闲区更大,更可用,但是这种方式会导致较大的连续空闲区被迅速用完。如果之后有“大进程”到达,就没有内存分区可用了。
邻近适应算法
首次适应算法每次都从链头开始查找的。这可能会导致低地址部分出现很多小的空闲分区,而每次分配查找时,都要经过这些分区,因此也增加了查找的开销。如果每次都从上次查找结束的位置开始检索,就能解决上述问题。
空闲分区以地址递增的顺序排列(可排成- -个循环链表)。每次分配内存时从上次查找结束的位置开始查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
这个其实就是一个循环链表的便利查找,讲一遍具体过程
举例:
当前内存的使用情况如下图所示:
空闲分区链如下所示:
现在有一个新进程的大小为5
我们从4开始找,找到6,放入第二个分区中
然后又来新进程的大小为5MB,他会从1开始查找,到10,然后插入
也就是说他每次开始查找都是从上一个查找结束的位置开始的
首次适应算法每次都要从头查找,每次都需要检索低地址的小分区。但是这种规则也决定了当低地址部分有更小的分区可以满足需求时,会更有可能用到低地址部分的小分区,也会更有可能把高地址部分的大分区保留下来(最佳适应算法的优点)
邻近适应算法的规则可能会导致无论低地址、高地址部分的空闲分区都有相同的概率被使用,也就导致了高地址部分的大分区更可能被使用,划分为小分区,最后导致无大分区可用(最大适应算法的缺点)
综合来看,四种算法中,首次适应算法的效果反而更好
四种算法对比
知识点回顾
内存扩充
内存扩充的目的是解决程序大小超过物理内存大小内存总和
有三种技术可以实现:覆盖技术、交换技术和虚拟存储技术
覆盖技术
将程序分为多个段(多个模块)。常用的端常驻内存,不常用的段在需要时调入内存。
内存中分为一个“固定区”和若干个“覆盖区”。
需要常驻内存的段放在“固定区”中,调入后就不再调出(除非运行结束)
不常用的段放在“覆盖区”需要用到时调入内存,用不到时调出内存
举例:
首先是只有A会调用B和C,然后在B中会调用D,C中会调用E和F
可以看出A是会执行的,并且B和C不会同时执行,D、E、F不会同时执行,因此就是一个固定区放A、开一个覆盖区放B或者C,开另一个覆盖区放D或E或F(为什么是或,因为这个区里面一次只可能放一个,调入嘛)
缺点:必须由程序员声明覆盖结构(就是什么时候覆盖,覆盖哪个),这样对用户不透明,增加了用户编程负担
覆盖技术只用于早期的操作系统中,现已成为历史
交换技术
内存空间紧张时,系统将内存中某些进程暂时换出外存,把外存中某些已具备运行条件的进程换入内存(进程在内存与磁盘间动态调度)
前面处理机调度也讲了,中级调度是内存调度,就是用来实现这个技术的
首先,我们会在磁盘的对换区保存被换出的进程:
具有对换功能的操作系统中,通常把磁盘空间分为文件区和对换区两部分。文件区主要用于存放文件,主要追求存储空间的利用率,因此对文件区空间的管理采用离散分配方式(这个在后面讲);对换区空间只占磁盘空间的小部分,被换出的进程数据就存放在对换区。由于对换的速度直接影响到系统的整体速度,因此对换区空间的管理主要追求换入换出速度,因此通常对换区采用连续分配方式( 学过文件管理章节后即可理解)。总之,对换区的I/0速度比文件区的更快。
什么时候进行交换
交换通常在许多进程运行且内存吃紧时进行,而系统负荷降低就暂停。例如:在发现许多进程运行时经常发生缺页,就说明内存紧张,此时可以换出一些进程;如果缺页率(缺页。。。也要在后面讲)明显下降,就可以暂停换出。
应该换出哪些进程
可优先换出阻塞进程;可换出优先级低的进程;为了防止优先级低的进程在被调入内存后很快又被换出,有的系统还会考虑进程在内存的驻留时间
注:进程的PCB是常驻内存的,不会被换出去!!!
知识点回顾
地址转换
这个前面已经有过讲述了,就不在赘述一遍
内存保护
内存保护有两种方法:在CPU中设置一对上下限寄存器、设置一个基址寄存器和界地址寄存器
在CPU中设置一对上下限寄存器
就是在逻辑地址转换成物理地址的时候,上限寄存器存储首地址,下限寄存器存储尾地址,小于上限或者大于下限都会被禁止访问
设置一个基址寄存器和界地址寄存器
这个方法和动态重定位很像,基址寄存器存放首地址,界地址寄存器存放长度