来都来了不点个赞真说不过去啦🌹🌹🌹🌹🌹🌹
一、内存管理的基本概念
相对地址和绝对地址
编译时产生的指令只关心“相对地址”,实际放入内存中时再想办法根据起始位置得到“绝对地址”。
Eg: 编译时只需确定变量x存放的相对地址是100(也就是说相对于进程在内存中的起始地址而言的地址)。CPU 想要找到x在内存中的实际存放位置,只需要用进程的起始地址+100即可。
相对地址又称逻辑地址(程序的起止地址加上偏移量),绝对地址又称物理地址。
内存管理需要进行的基本操作
- 内存分配和回收:通过分配给程序所需的内存空间,并回收不再使用的空间,以供后续使用。
- 内存扩充:这包括将非连续的内存空间映射到连续的虚拟地址空间,以及将连续的内存空间进行分段或分页,以适应不同程序的需求。
- 内存共享和保护:这是指多个程序共享同一内存空间时,需要对内存进行保护,防止一个程序对另一个程序的干扰或破坏。
- 地址变换:包括静态地址重定位和动态地址重定位。静态地址重定位是在虚拟空间程序执行之前由装配程序完成地址映射,而动态地址重定位是在程序运行过程中要访问数据时再进行地址变换(即在逐条指令执行时完成地址映射)。
二、内存空间分配与回收
⭐内存空间分配与回收理解及应用
1.连续分配管理方式
1.1单一连续分配
- 在单一连续分配方式中,内存被分为系统区和用户区。系统区通常位于内存的低地址部分,用于存放操作系统相关数据;用户区用于存放用户进程相关数据。
- 内存中只能有一道用户程序,用户程序独占整个用户区空间。
- 优点: 实现简单 ;无外部碎片;可以采用覆盖技术扩充内存;不一定需要采取内存保护(eg:早期的 PC操作系统MS-DOS)。
- 缺点:只能用于单用户、单任务的操作系统中;有内部碎片;存储器利用率极低。
1.2固定分区分配
- 固定分区分配是一种多道程序存储管理方式,它将用户内存空间分为若干个固定大小的区域,每个区域装入一道作业。有空闲分区时即可从外存的作业队列选择并装入,如此循环。分区一般在MB级别,分区大小相等,有利于一台计算机控制多个相同对象,具有灵活性。
- 这种分配方式的优点:简单、无内部碎片,适用于多道程序系统。
- 缺点:多个进程不能共享一个主存区,当程序小于一个分区大小时,会产生内部碎片造成空间浪费。此外,如果程序远小于一个分区大小时,也需要采取覆盖技术。
1.3动态分区分配
动态分区分配又称为可变分区分配。这种分配方式不会预先划分内存分区,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。因此系统分区的大小和数目是可变的。
使用这种方式的话,我们需要考虑以下三个问题。
1.系统要用什么样的数据结构记录内存的使用情况?
- 空闲分区表
- 空闲分区链
2.当很多个空闲分区都能满足需求时,应该选择哪个分区进行分配?
使用动态分区算法,这个将在下面进行详细介绍。
3.如何进行分区的分配与回收操作?
-
如何分配 -----------> 使用动态分区算法之后,修改数据结构即可
-
如何回收-------------------------------> 牢记一点即可,会把相邻的空闲区域合并为一个。
1.4内部碎片和外部碎片
- 动态分区分配没有内部碎片,但是有外部碎片。
- 内部碎片,分配给某进程的内存区域中,如果有些部分没有用上。
- 外部碎片,是指内存中的某些空闲分区由于太小而难以利用。
- 如果内存中空闲空间的总和本来可以满足某进程的要求,但由于进程需要的是一整块连续的内存空间,因此这些进程“碎片”不能满足进程的需求。可以通过紧凑((拼凑,Compaction)技术来解决外部碎片。
1.5动态分区分配算法
1.首次适应算法
算法思想:从内存起始位置开始,依次查找第一个符合大小要求的空闲分区进行分配。
实现:空闲分区以地址递增的次序排列,找到大小能满足要求的第一个空闲分区。
分区号 | 大小(MB) | 起止地址(从小到大) | 状态 |
1 | 20 | 6 | 空闲 |
2 | 30 | 28 | 空闲 |
3 | 20 | 60 | 空闲 |
4 | 10 | 84 | 空闲 |
当前进来一个作业需要26MB的空间,从分区号为一的开始找找到分区号2的时候发现了可以满足大小,所以分区2就被分配出去了,变为了下面这样
分区号 | 大小(MB) | 起止地址 | 状态 |
1 | 20 | 6 | 空闲 |
2 | 20 | 60 | 空闲 |
3 | 10 | 84 | 空闲 |
2.最佳适应算法
算法思想:在所有空闲分区中选择最小的能够满足作业需求的区块进行分配。
实现:空闲分区按容量递增次序链接。每次分配以后都要把最小的分区调到最前面。
分区号 | 大小(MB)按次升序排列 | 起止地址 | 状态 |
1 | 10 | 84 | 空闲 |
2 | 20 | 60 | 空闲 |
3 | 20 | 8 | 空闲 |
4 | 30 | 30 | 空闲 |
当前进来一个作业需要26MB的空间,从分区号为一的开始找找到分区号4的时候发现了可以满足大小,所以分区4就被分配出去了,变为了下面这样
分区号 | 大小(MB)按次升序排列 | 起止地址 | 状态 |
1 | 10 | 84 | 空闲 |
2 | 20 | 60 | 空闲 |
3 | 20 | 8 | 空闲 |
3.最坏适应算法
算法思想:选择最大的能够满足作业需求的空闲分区进行分配。
实现:空闲分区按容量递减次序链接。每次分配内存时顺序查找空闲分区链
分区号 | 大小(MB)按次降序排列 | 起止地址 | 状态 |
1 | 30 | 30 | 空闲 |
2 | 20 | 60 | 空闲 |
3 | 20 | 8 | 空闲 |
4 | 10 | 84 | 空闲 |
当前进来一个作业需要26MB的空间,从分区号为一的开始找找到分区号1的时候发现了可以满足大小,所以分区1就被分配出去了,变为了下面这样
分区号 | 大小(MB)按次降序排列 | 起止地址 | 状态 |
1 | 20 | 60 | 空闲 |
2 | 20 | 8 | 空闲 |
3 | 10 | 84 | 空闲 |
4.循环首次适应算法
算法思想:从上一次分配结束的位置开始查找第一个满足要求的空闲分区分配给进程。
实现:空闲分区以地址递增的顺序排列(可排成一个循环链表)。每次分配内存时从上次查找结束的位置开始查找空闲分区链
5.算法对比
2.非连续分配管理方式
2.1基本分页存储管理
基本分页存储管理的思想――把内存分为一个个相等的小分区,再按照分区大小把进程拆分成一个个小部分。
需要了解的几个基本概念:
1.页框(内存块):物理内存被划分成固定大小的块,每个块称为一页框或内存块,通常大小为 4KB 或 8KB。
2.页:将进程的地址空间划分成大小相同的块,称为页面或页。每个页的大小与内存块的大小相同,通常为 4KB 或 8KB。
3.页表:为了实现虚拟地址到物理地址的转换,需要建立页表。页表是一张映射关系表,将进程的虚拟页号与物理页框号进行映射,用于指示页面在物理内存中的位置。页表是通常是PCB中的。
- 块号:块号对应的是物理地址
- 页号:页号可以是通过计算得出的,一般是隐式,页号=逻辑地址/页面大小(取整)
- 偏移地址: 偏移地址=逻辑地址-页号*页面大小
- 页表项长度: 页表项长度是指每个页表项占多大的存储空间,也就是2^n 刚刚好大于页的个数,通常要把二进制转化为字节
4.逻辑地址:在分页管理系统中的逻辑地址是由页号和页偏移量组成的,我们可以通过页偏移量的位数计算出每一页的大小。
m内存块的起始地址=块号*内存块大小
eg:在某计算机系统中,页面大小是50B。某进程逻辑地址空间大小为200B,则逻辑地址 110 对应的页号、页内偏移量是多少?
页号=逻辑地址/页面大小(取整)=110%50=2
偏移地址=逻辑地址-页号*页面大小= 110-50*2=10
Eg:假设某系统物理内存大小为4GB,页面大小为4KB,内存总共会被分为2^32/ 2^12=2^20个内存块,因此内存块号的范围应该是0~2^20 - 1。因此至少要20个二进制位才能表示这么多的内存块号,因此至少要3个字节才够(每个字节8个二进制位,3个字节共24个二进制位)。每个块号用三个字节来表示。
将进程地址空间分页之后,操作系统该如何实现逻辑地址到物理地址的转换?
- 要算出逻辑地址对应的页号
- 要知道该页号对应页面在内存中的起始地址
- 要算出逻辑地址在页面内的“偏移量”
- 物理地址 = 页面始址+页内偏移量
2.2基本分段存储管理
进程的地址空间:按照程序自身的逻辑关系划分为若干个段,每个段都有一个段名(在低级语言中,程序员使用段名来编程),每段从0开始编址。
内存分配规则 : 以段为单位进行分配,每个段在内存中占据连续空间,但各段之间可以不相邻。
- 分段系统的逻辑地址结构由段号(段名)和段内地址(段内偏移量)所组成。
- 段号的位数决定有多少个段
- 段号的偏移量位数决定了段是多大
段和页是差不多的就是段的内存空间分配的大小的不定的,所以说段也会要找到自己的物理地址就有会段表的概念
在段式管理中找到对应的地址需要通过两个参数,一个是段号(段名),一个是段内地址,但是在页式管理中我们就只需要通过逻辑地址去计算就可以了,这也是两个管理的差别。
2.3段页式存储管理
段页式存储管理是一种结合了段式存储管理和页式存储管理优点的存储管理方式。它先将程序分为多个逻辑段,再将每个段内空间分为若干页,并把页式存储管理中的“页”缩小到物理内存的一个固定大小,以实现更高效的空间利用率。
段页式的逻辑地址由:段号、页号、页内偏移地址组成
- 段号的位数决定了每个进程最多可以分几个段
- 页号位数决定了每个段最大有多少页
- 页内偏移量决定了页面大小、内存块大小是多少
三、虚拟内存基本概念
虚拟内存的定义:
- 基于局部性原理,在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行。
- 在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。
- 若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。
- 在操作系统的管理下,在用户看来似乎有一个比实际内存大得多的内存,这就是虚拟内存。
易混知识点:
虚拟内存的最大容量是由计算机的地址结构( CPU寻址范围)确定的
虚拟内存的实际容量= min(内存和外存容量之和,CPU寻址范围)
如: 某计算机地址结构为32位,按字节编址,内存大小为512MB,外存大小为2GB.
则虚拟内存的最大容量为2^32B= 4GB 。 虚拟内存的实际容量=min (2^32B,512MB+2GB)= 2GB+512MB
⭐虚拟内存特征
- 多次性:无需在作业运行时一次性全部装入内存,而是允许被分成多次调入内存。
- 对换性:在作业运行时无需一直常驻内存,而是允许在作业运行过程中,将作业换入、换出。
- 虚拟性:从逻辑上扩充了内存的容量,使用户看到的内存容量,远大于实际的容量。
1.请求页式管理
1.1页表机制
与基本分页管理相比,请求分页管理中,为了实现“请求调页”,请求调页就是把不在内存中的数据拿答内存里面。
- 操作系统需要知道每个页面是否已经调入内存;
- 如果还没调入,那么也需要知道该页面在外存中存放的位置。
- 当内存空间不够时,要实现“页面置换”,操作系统需要通过某些指标来决定到底换出哪个页面;
- 有的页面没有被修改过,就不用再浪费时间写回外存。有的页面修改过,就需要将外存中的旧数据覆盖,因此,操作系统也需要记录各个页面是否被修改的信息。
因此页表会增加四个字段来上面的信息。
⭐1.2缺页中断机制
假设此时要访问逻辑地址 = (页号,页内偏移量)= (0,1024)
在请求分页系统中,每当要访问的页面不在内存时,便产生一个缺页中断,然后由操作系统的缺页中断处理程序处理中断。
此时缺页的进程阻塞,放入阻塞队列,调页完成后再将其唤醒,放回就绪队列。
如果内存中有空闲块,则为进程分配一个空闲块,将所缺页面装入该块,并修改页表中相应的页表项。
如果内存中没有空闲块,则由页面置换算法选择一个页面淘汰,若该页面在内存期间被修改过,则要将其写回外存。未修改过的页面不用写回外存。
缺页中断是因为当前执行的指令想要访问的目标页面未调入内存而产生的,因此属于内中断
1.3地址变换
当要访问的页面对应的页表项的状态位为N时,硬件地址变换机构会立即产生一个缺页中断信号。CPU响应此中断后,将原进程阻塞,转去执行中断处理程序。缺页中断的处理程序负责将缺页调入内存,并相应地修改进程的页表。中断返回后,原进程就可以重新进行地址变换,继续运行。
2.页面置换算法
2.1最佳置换算法
选择将来永不使用,或者在最长时间内不再被访问的页面予以淘汰。这种算法是最理想的,但是实际上很难实现,因为无法预知未来的页面访问情况。
对于下面的例子是,在发生了缺页中断之后就会往后面去找内存块中的数据那一个是最晚被使用的就先把他替换出去,看下面的访问第一次2的时候就是,找找找,首先找到0先被使用,1也被使用,而7是最后被使用的所以就把7替换出去。
2.2先进先出置换算法
总是淘汰最早进入内存的页面,即选择在内存中停留时间最久的页面予以淘汰。实现简单,只需要将进程已调入内存中的页面,按照先后顺序连接成一个队列,设置一个替换指针,总是指向最老的页面。但是该算法与进程实际的规律并不相适应,因为在进程中,有些页面经常被访问,比如含有全局变量、常用函数、例程等的页面,FIFO不能保证这些页面不会被淘汰。
下面的例子:
会有一个链表用来表示当前内存块的分配顺序,队头是最早的,队尾是最晚的,所以当发生缺页中断的时候,就替换对头的内存块的数据,把当前的放到队尾。
Belady异常―一当为进程分配的物理块数增大时,缺页次数不减反增的异常现象。
只有FIFO算法会产生Belady异常。
2.3最近最久未使用算法
根据页面调入内存之后的使用情况来选择淘汰的页面。具体来说,LRU算法选择最近最久未使用的页面予以淘汰。这种算法能更好地反映页面的使用情况,但是需要记录每个页面的访问时间,实现起来比较复杂。
实现方法:赋予每个页面对应的页表项中,用访问字段记录该页面自上次被访问以来所经历的时间t。当需要淘汰一个页面时,选择现有页面中t值最大的,即最近最久未使用的页面。
如是考试手算的话,就是从当前页面往前面找,找到的最后一个出现在内存块中的页面被替换。
2.4时钟置换算法
简单的CLOCK 算法实现方法:为每个页面设置一个访问位,再将内存中的页面都通过链接指针链接成一个循环队列。当某页被访问时,其访问位置为1。当需要淘汰一个页面时,只需检查页的访问位。如果是0,就选择该页换出;如果是1,则将它置为0,暂不换出,继续检查下一个页面,若第一轮扫描中所有页面都是1,则将这些页面的访问位依次置为0后,再进行第二轮扫描(第二轮扫描中一定会有访问位为0的页面,因此简单的CLOCK算法选择一个淘汰页面最多会经过两轮扫描)
eg:假设系统现在有四个内存块,并且当前有一些的页面引用串:1324638
首先要初始化循环队列
当出现了缺页中断的情况
插入6的时候,首先会发现访问为全都是1,第一次访问全变为0,如何从队头开始替换,指针指向下一个。
插入3的时候,因为当前就是数据块3,所以当前访问位变为1,指针指向后面一个
插入8的时候,当前访问位为0,直接替换,执行往后移动
2.5改进型的时钟置换算法
简单的时钟置换算法仅考虑到一个页面最近是否被访问过。事实上,如果被淘汰的页面没有被修改过,就不需要执行I/o操作写回外存。只有被淘汰的页面被修改过时,才需要写回外存。
因此,除了考虑一个页面最近有没有被访问过之外,操作系统还应考虑页面有没有被修改过。在其他条件都相同时,应优先淘汰没有修改过的页面,避免I/o操作。这就是改进型的时钟置换算法的思想。修改位=0,表示页面没有被修改过;修改位=1,表示页面被修改过。
为方便讨论,用(访问位,修改位)的形式表示各页面状态。如(1,1)表示一个页面近期被访问过,且被修改过。
算法规则: 将所有可能被置换的页面排成一个循环队列
第一轮:从当前位置开始扫描到第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
第二轮:若第一轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。本轮将所有扫描过的帧访问位设为0
第三轮:若第二轮扫描失败,则重新扫描,查找第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
第四轮:若第三轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。
由于第二轮已将所有帧的访问位设为0,因此经过第三轮、第四轮扫描一定会有一个帧被选中,因此改进型CLOCK置换算法选择一个淘汰页面最多会进行四轮扫描
对比
四、内存映射文件
- 内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区域,同时将物理存储器提交给此区域,内存文件映射的物理存储器来自一个已经存在于磁盘上的文件,而且在对该文件进行操作之前必须首先对文件进行映射。
- 使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,这使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。
说白了也就是把手动read/write数据变成操作系统自己帮你read/write数据。