3.1 交换技术
我们在使用电脑的时候,打开的软件一般都很多,占用的内存也很大,而我们实际的物理内存只有4G、8G或16G,但是却能跑超过物理内存的大小的程序,这种超载的处理方式,有两种:交换技术和虚拟内存。这一篇先介绍交换技术。
交换技术如图:
进程1睡眠的时候,会把进程1换出内存,保存在磁盘中,把要运行的进程2换入内存,这就是交换技术。
3.1.1 标准交换
标准交换是内存和备份存储之间进行移动。备份存储通常是快速硬盘。当CPU调度器决定要执行一个进程时,它调用分派器。分派器检查队伍中的下一个进程是否在内存中。如果不在并且没有空闲内存区域,那么分派器会换出(swap out)当前位于内存中的一个进程,并换入(swap in)所需进程。然后,重新加载寄存器,并且将控制权转移到所选进程。(这个就是进程的上下文切换,可以看向进程篇)
-
换出时间
假设用户进程的大小为200MB,并且备份存储传输速度为200MB/s(在某东看的数据)。200M进程换入或换出内存时间为1s。总共换入换出就是2s。现在电脑的内存基本是8G,除去操作系统的2G(windows占2G),一共有6G,那如果需要全部换入换出需要60s。这个速度如果是上下文切换的话,黄花菜都凉了。
显然,上下文切换不可能一整个内存都换入换出,只能把进程相关的内存进行换入换出,这个在后面会分析。
-
什么进程可以被换出
进程完全处于空闲状态的优先被换出,或者是在阻塞状态下的进程。
-
换出的位置
磁盘空间会被分为文件区和swap区,对换出去的数据就保存在swap区。
现代操作系统并不使用标准交换。标准交换时间太多。有两种变种都是有使用:
- 正常情况下,禁止交换;当空闲内存(为被操作系统或进程使用的内存)低于某个阈值时,启用交换。当空闲内存的数量增加了,就停止交换。
- 交换进程的部分,以降低交换时间。
3.1.2 移动系统的交换
虽然用于PC的服务器大多数操作系统支持一些交换变形,移动系统通常不支持任何形式的交换。
原因如下:
- 移动设备采用闪存,而不是空间更大的硬盘作为它的永久存储。
- 闪存写入次数的限制
- 内存与闪存之间的吞吐量差。
当空闲内存降低到一定阈值以下时,苹果IOS,不是采用交换,而是要求应用程序自愿放弃分配的内存。只读数据(如代码)可从系统中删除,以后如有必要再从闪存重新加载。已修改的数据(如堆栈)不会被删除。然而,操作系统可以终止任何未能释放足够内存的应用程序。
Android不支持交换,而采用类似IOS使用的策略。如果没有足够可用的空闲内存,则它可以终止进程。然后在终止之前,Android将其应用程序状态写到闪存,以便它能快速重新启动。
3.1.3 例子
先看看进程在内存中运行的样子(也就是上一篇写的运行时重定位):
当前运行的是进程1,进程1PCB中的基地址为2000,早已经保存在重定位寄存器中了,所以当执行到call 40的时候,就会重新进行重定位,重定位成call 2000+40,这样就能找到合适的位置。
接下来看进程切换到进程3:
进程到进程3的时候,就会把进程3的PCB的基地址写入到重定位寄存器中,这样进程3运行的时候跟进程1是没区别,都是代码中的代码加上重定位寄存器,拿到结果地址再去寻找。
接下来就看交换了:
如果当前需要运行进程2,并且没有空间分配给进程2了,此时进程1又处在可被换出状态,进程2就会换入到进程1的位置,并且进程2的PCB的基地址被改成了2000,重定位寄存器也会修改成2000,这样进程2就可以在内存中执行了。
3.2 分区
从上面学习到了交换技术,那操作系统是怎么分配内存给进程的,接下来我们来学习一下之前分区的方方法。(现在没用了,不过可以学习相关思想。)
3.2.1 固定分区分配
-
分区大小相等
最为简单的内存分配方法之一:将内存分为多个固定大小的分区。
哈工大的老师这个举的例子很妙,就是一块大面包,有几个孩子在,我们一般都是平分处理。
但是平分的话,也有一个很大的缺陷,有的孩子胃口大,吃的比较多,有的孩子胃口小,吃的小。
所以有如下缺点:
- 进程内存过小:如果一些程序需要的内存比较少,达不到10M,那也分配10M内存给这个程序,明显照常内存浪费。
- 进程内存过大:如果一些程序需要的内存比较大,一个分区就包含不少,所以也会出现问题。
- 每一个分区可以只包含一个程序,因此,多道程序的程度受限于分区数。
-
分区大小不等
竟然平分不公平,那可以把面包分成大小不等,由孩子自己来选择。
虽然这样分面包,小孩子可以自由选择,但是也会同样存在孩子胃口不固定的问题;也就是程序需要的内存是未知的,所以同样也存在上面的内存过小或内存过大问题。
3.2.2 动态分区分配
既然上面的分法不太好,那就每个孩子需要面包的时候,孩子需要多少,我们再分多少,这个就是动态分区分配。
我们先以动画的方式来看看这个分配的方式:
进程1需要6M内存,内存分配了6M,进程2需要12M,内存分配了12M,进程3需要2M,内存分配了2M。这就是动态分区分配,需要多少分配多少。
-
管理
既然是动态分区分配,那肯定需要管理了,在没有看过管理方案的时候,总是以为需要管理分配出去的内存和空闲的内存,其实不然,只要管理好空闲内存就可以了。分配出去的内存,由进程管理,等到进程不用的时候再释放(应该是这样的,哈哈哈)。
虽然王道考研说的有两种方式:①空闲分区表 ② 空闲分区链
不过实际系统上,大部分都是用空闲分区链,因为需要多次的合并,减少节点等操作,比较适合链表结构,不过不管是空闲分区表和空闲分区链意思是一样,就是把空闲分区管理起来,下面就来看看怎么管理的。
刚开始进程1,进程2没进去内存空闲,空闲分区表
起始地址 | 大小 |
---|---|
10M | 40M |
进程1开始加载进内存,大小为6M,内存分配了6M给进程1,剩下的空闲分区表
起始地址 | 大小 |
---|---|
16M | 34M |
之后进程2,再次加载进内存,内存继续分配12M给进程2,剩下的空闲分区表
起始地址 | 大小 |
---|---|
28M | 22M |
这时候进程1,结束运行,并释放了6M内存,这时候空闲分区表(详细回收下面在描述)
起始地址 | 大小 |
---|---|
10M | 6M |
28M | 28M |
这样就有两块空闲内存了。
-
分配
如果这时候进程3,大小2M来申请内存,这时候内存又怎么分配???
这个时候就需要算法了,下面介绍几个算法:
①首次适配:内存管理器沿着空闲分区表/链开始搜索,直到找到第一个满足条件的空闲区。
优点是一种速度比较快的算法,因为它尽可能少的搜索链表结点。
②最佳适配:需要搜索整个链表,找出能够容纳进程的最小的空闲区。
缺点:每次都选择最小的分区进行分配,会留下来越来越多的、很小的、难以利用的内存块,也就是内存碎片(外部碎片)。
③最差适配:由于最佳适配会分裂出很多非常小的空闲区,为了避免这一问题,有想到了一种,总是分配最大的可用空闲区,使新的空闲区比较大从而可以继续使用。
缺点:不断的分配最大的空闲区,会导致较大的空闲区被用完,如果再来一个大进程,那就没有内存可用了。
④下次适配:下次适配算法其实跟首次适配算法相同,不同点是每次找到合适的空闲区时都记录当时位置,以便在下次寻找空闲区时从上次结束的地方开始搜索。而首次适配是每次都从开始搜索。
缺点:下次适配算法的缺点,低地址和高地址空闲分区都有相同概率被使用,从而导致没有大分区使用。(首次适配如果在低地址区就满足了,就不会往高地址区搜索了)
总结:最后还是接的首次适配算法,性能比较好。
-
回收
回收就是进程使用完了,需要把这块内存释放,由空闲分区表/链接收这块内存,其实回收是比较简单的,主要记得这一思想就行:
如果在空闲分区表/链中这块释放内存没有相邻的,就需要新创一个表项,保存这个信息。
如果有相邻的,就进行合并。
-
碎片
内存碎片是怎么产生的,想必到这里我们都知道了吧,就是进程不断的运行,内存管理不断的给新进程分配内存,不断的回收旧程序的内存,但是在这期间,总因为进程大小不一样,会产生各种不同的碎片,这些碎片就是内存碎片,因为这些碎片比较小,可能不一样够再次分配给进程,所以这些碎片暂时不能使用。
内存碎片也有方法解决:
第一个就是内存紧缩:啥时候内存紧缩呢?其实就是把内存从新调整个位置,把内存整体往上移动,这样就把之前又碎片的地方都用上了,不过这种方法,都不会使用,因为这种操作会很耗CPU,在3.1.1节的时候就计算过了,一个16G的内存移动一次需要多久,还有就是移动后的内存,进程是不是又要重新定位,也是一个很费时间的。
第二个就是分段和分页:这个我们下一篇来介绍。
3.3 总结
想不到这一篇写这么久,这一篇其实很重要,虽然交换技术和分区技术现在不怎么用了,但是现在使用的都是这两种技术进化版,所以这两个技术确实有学习的必要,做到一个承上启下的作用。