在上节中我们已近介绍了,操作系统在内存管理中发挥的两个功能:地址转化、存储保护这节我们着重探讨:内存空间的分配和回收。不妨再来回顾下操作系统在内存管理方面提供了那些功能。
基本概念: 操作系统对内存的划分和动态管理。
带来的好处: 方便用户实用存储器、提高内存利用率、通过虚拟技术从逻辑上扩充内存。
OS提供的功能: ①内存空间的分配和回收 ②地址转化 ③内存扩充 ④存储保护
内部碎片: 已经分配给作业但不能被利用的内存空间
外部碎片: 系统中还没有分配给作业,但是由于碎片太小而无法分配给申请内存空间的新进程的存储块。
通俗讲: 某个作业所占的内存区域如果没有装满,就是内部碎片,而作业与作业之间,如果有内存区域没有分配给某个作业,但是又不能分配给任何作业,就是外部碎片。
历史: 早期的计算机系统,内存空间特别小,有时即使只存放一个作业,但是仍然不能一次全部装入内存。对此,我们引入了覆盖技术。
细说: 由于程序运行时候并非任何时候都要访问程序及数据的各个部分(尤其是大程序),因此可把用户空间分成一个固定区和若干覆盖区。常用部分放在固定区(输入后就不在调出,除非运行结束)、不常用功能,在其他程序模块中实现,平时存放在外存中,在需要用到时装入内存。
例子:
A()//8KB
{
B();//8KB
C();//10KB
}
B()//8KB
{
D();//12KB
}
C()//10KB
{
E();//4KB
F();//10KB
}
特点: 打破了必须将一个进程全部信息装入内存后才能运行的限制,但是当同时运行程序的代码量大于主存时仍不能运行,此外,内存中能够更新的地方只有覆盖区的段,不在覆盖区中的段会常驻内存。必须由程序员声明覆盖结构,操作系统完成自动覆盖。缺点:对用户不透明,增加了用户编程负担。覆盖技术只用于早期的操作系统中,现在已成为历史。
思想: 内存空间紧张时,系统将内存中某些进程暂时换出外存,把外存中某些已具备运行条件的进程换入内存(进程在内存与磁盘间动态调度)、我们熟知的中级调度就是用的这种技术。
换出、换入: 把处于等待状态(或在CPU调度原则下被剥夺运行权利)的程序从内存移到辅存,把内存空间腾出来。把准备好竞争CPU运行的程序从辅存移到内存,这一个过程又称换入。
注意:
例子: 一个CPU 采用时间片轮转调度算法的多道程序环境。某一进程时间片到,内存管理器将这一进程换出,将另外一个进程换入刚刚释放的内存空间。同时CPU调度器可以将时间片分配给其他已经在内存中的进程。每个进程用完时间片都与另外一进程交换。理想情况下,内存管理器的交换速度够快,总有进程在内存中可以执行。
交换技术主要在不同进程之间,而覆盖是同一进程之中。由于覆盖技术要求给出程序段之间的覆盖结构,使得其对用户和程序员不透明,所以对于主存无法存放用户程序的矛盾。现代操作系统是通过虚拟内存技术 来解决的,覆盖技术已经成为了历史;而交换在现代操作系统中任具有较强的生命力。
由操作系统完成内存空间的分配和管理,使得程序设计人员摆脱存储空间分配的麻烦,提高编程效率,为此,系统应该记住内存空间的使用情况:实施内存的分配;回收系统或用户释放的内存空间。
连续分配管理顾名思义:为用户分配一块连续的空间。主要有3种方式:①单一连续分配 ②固定分区分配 ③动态分区分配
在单一连续分配方式中,内存被分为系统区和用户区。系统区通常位于内存的低地址部分,用于存放操作系统相关数据;用户区用于存放用户进程相关数据。内存中只能有一道用户程序,用户程序独占整个用户区空间,自然也不需要操作系统提供存储保护。(直接把用户区给整个用户程序)
优点:实现简单;无外部碎片;可以采用覆盖技术扩充内存;不一定需要采取内存保护(eg:早期的 PC 操作系统 MS-DOS)。
缺点: 只能用于单用户、单任务的操作系统中;有内部碎片;存储器利用率极低
简述: 20世纪60年代出现了支持多道程序的系统,为了能在内存中装入多道程序,且这些程序之间又不会相互干扰,于是将整个用户空间划分为若干个固定大小的分区,在每个分区中只装入一道作业,这样就形成了最早的、最简单的一种可运行多道程序的内存管理方式。当内存当有空闲区时候,便可从外存的后备作业队列中,选择合适大小的作业装入该分区,如此循环。一般采用静态重定位装入内存。
分区说明表: 且为了实现固定分区分配,OS需要建立一个数据结构:分区说明表,来实现各个分区的分配与回收。每个表项对应一个分区,通常按分区大小排列。每个表项包括对应分区的大小、起始地址、状态(是否已分配)。当某用户程序要装入内存时,由操作系统内核程序根据用户程序大小检索该表,从中找到一个能满足大小的、未分配的分区,将之分配给该程序,然后修改状态为“已分配”。
分区相等: 缺乏灵活性,程序太大时候,一个分区又不足以装入该程序,导致程序无法运行。
分区不等: 可以把内存划分为:多数较小的分区,适量中等大小分区,少数大分区。
优点: a.多道程序系统最简单的存储分配。b.实现简单,无外部碎片。
缺点: a. 当用户程序太大时,可能所有的分区都不能满足需求,此时不得不采用覆盖技术来解决,但这又会降低性能;b.程序小于固定空间大小 会产生内部碎片,内存利用率低。
应用: 不能实现多进程共享一个主存区,所以存储空间利用率低。适用于:某些控制多个相同对象的控制系统。(共享是指多进程都使用同一个主存段)
简述: 这种分配方式不会预先划分内存分区,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。因此系统分区的大小和数目是可变的。
为了实现动态分区分配,系统中也必须设置相应的数据结构来记录内存的使用情况。常用的数据结构:
1)空闲分区表:每个空闲分区对应一个表项。表项中包含分区号、分区大小、分区起始地址等信息。
2)空闲分区链: 用链头指针将内存中的空间分区链接起来,构成空闲分区链。实现办法:每个空闲分区的若干字节用来存放控制信息(前后指针、分区大小等)。
根据上面过程我们发现:动态分区在最开始的时候很好,但是随着时间的推移,内存中会产生越来越多的外部碎片,导致内存利用率随之下降,克服外部碎片,我们可以使用紧凑(Compaction) 技术,但是这需要硬件支持,相对耗时。(紧凑技术相当于Windows系统中的磁盘整理程序,只不过后者是针对外存空间的紧凑。)所以决定内存利用率的关键在于:分配算法
参考cm_cyj_1116的博客
将一个作业存入内存,应按照一定的分配算法从空闲分区表中选。如果选取的空闲分区的容量比作业空间大,那么将该空闲分区的一部分给作业,剩下的部分仍然留在空闲分区中,同时需要修改空闲分区表中的有关信息。那么如何从空闲分区中选择呢?
1)首次适应算法(First Fit):
空闲分区按照地址递增的次序用链表串成一个队列,每次需要为一个进程分配内存时都从队首开始找。顺着链队列(链式存储的队列),找到大小能满足要求的第一个空闲分区。
优点: 优先利用存储低地址部分的空闲分区,从而保留高地址部分的大的空闲分区。
缺点: a.由于低地址部分不断划分,致使低地址端留下许多难以利用的很小的空闲分区(外部碎片)
b.每次查找又都是从低地址部分开始的(顺序访问),无疑增加了“查找可用空间”的开销。
2)临近适应算法(Next Fit):
算法思想: 该算法别名:循环首次适应算法,即在首次适应算法的基础上把队列改成循环队列,而不是每次从队首开始找空闲分区,而是从上次找到的空闲分区的下一个分区开始找。
如何实现: 空闲分区以地址递增的顺序排列(可排成一个循环链表)。每次分配内存时从上次查
找结束的位置开始查找空闲分区链,找到大小能满足要求的第一个空闲分区。
优点: 使得空闲空间分配更均匀,减少了查找空闲分区的开销。
缺点: 导致缺乏大的空闲分区。
3)最佳适应算法(Best Fit):
算法思想: 由于动态分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片区域。因此为了保证当“大进程”到来时能有连续的大片空间,可以尽可能多地留下大片的空闲区,即,优先使用更小的空闲区。
算法实现: 空闲分区按容量递增次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区
优点: 总能分配给作业最适合的分区,并且保留大的分区。
缺点: 虽然成为“最佳”,每次最佳分配后会留下很小的难以利用的内存块,会产生最多的外部碎片。
4)最坏适应算法(Worst Fit):
算法思想: 为了解决最佳适应算法的问题——即留下太多难以利用的小碎片,可以在每次分配时优先使用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用。
算法实现: 空闲分区按容量递减次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
优点: 这样使分给作业后剩下的空闲分区比较大,足以装入其他作业。
缺点: 由于最大的空闲分区总是因首先分配而被划分,当有大作业到来时,其存储空间的申请会得不到满足。
科学家Knuth和Shore对FF、BF、WF三种方法对内存空间的利用情况做了模拟实验。
结果: 一般情况而言:FF优于BF优于WF,宁外在算法实现过程中,BF和WF要对可用块进行排序或者遍历查找,而FF和NF,只需要简单的查找。这告诉我们:设计操作系统时候,不仅仅考虑内存利用率,算法的开销也是一个重要因素。
碎片: 是指内存中无法被利用的存储空间。在分区存储管理方式下,系统运行一段时间后,内存中的碎片会占据相当多的数量空间。
紧凑: 将存储器中所有已分配分区移动到主存的一段,使本来分散的多个小空闲区连成一个大的空闲区。这种通过移动把多个分散的小分区拼接成一个大分区的方法叫做:紧凑。
紧凑时机:
1)某个分区回收时立即进行紧凑,这样在主存中总是只有一个连续的空闲区。但是拼接很费时,紧凑频率会使得系统开销加大。
2)找到不到足够大的空闲分区且总容量可以满足作业要求时进行紧凑,这种方式的频率比上一种方案低得多,但空闲分区的管理稍微复杂一些。
动态重定位分区分配算法与动态分区分配算法基本相同。差别在于:这种算法增加了紧凑功能,通常是找不到足够大的空间分区满足作业要求,而系统中空闲分区容量总和大于作业要求进行紧凑。
优点:
1)实现了多道程序共用主存(共用是指多进程同时存在于主存中的不同位置);【对比固定分区分配】
2)管理方案相对简单、不需要更多开销;
3)实现存储保护的手段比较简单
缺点:
1)主存利用不够充分,存在外部碎片
2)无法实现多进程共享存储器信息(共享是指多进程都使用同一个主存段);
3)无法实现主存的扩充,进程地址空间受实际存储空间的限制;【相比虚拟内存】