OS中的内存管理的发展历史

来源:互联网 发布:java总是安装失败 编辑:程序博客网 时间:2024/05/16 10:23

对于一项技术来说,了解其发展过来的历史和进程其实很重要

任何技术的初衷不管多么的复杂,都不是一蹴而就的,都是慢慢发展而来的,其初衷往往都很简单。

因此,通过对技术的发展历史的了解,能够理清这项技术的思路。

在这里,主要讨论OS如何为各个进程分配运行的内存空间。

在孙钟秀的《操作系统》这本书中,MM的发展过程中有以下几个里程碑式的事件

1.      OS最初的单任务系统

2.      OS从单任务处理变为多任务并行处理

3.      MMU的开始使用

4.      虚拟内存概念的出现

 

l  OS最初的单任务系统:

此时的操作系统内核的管理其实不复杂,就那么多内存给一个任务用,想怎么用就怎么用

l  OS从单任务处理变为多任务并行处理

本来系统中给一个任务用的内存现在就需要分一分,几个进程一起用。

那么关于多个进程如何一起分享内存,主要的方法就是把内存划分为独立的几块,分别给各个进程使用,关于如何划分成“块”,那就有好几种方法。

1)        固定内存区域划分(其实也就是嵌入式系统中常说的内存池式的管理方法)

一开始出现的就是这种分配方法,就是将系统内存划分为几种固定大小的内存块如4K,8K,32K,每个进程获取一个内存块来使用,只能从上面举例的几种固定大小的内存块中选择一种块。这种分配方法可行是可行的,但是总是会给人一种很奇怪的感觉,因为这不是最合适和最直观的分配系统内存的方式,分配的“块”的大小是固定的,如16K,32K,但是进程的大小并不是精确的等于这些值的,这样比如一个28K的进程,就只能分配给它一个32K的内存区域,明显有4K大小的空间被浪费了。按照普通人的理解,最好的方法就是给每个进程分配与之大小相同的内存区域,就是进程大小是15K,就分15K的内存给它,另外一个内存大小是17K,就分配一个17K的内存给他。但是历史的一开始,这种方式并没有被采用。原因是在多任务并行处理的OS中,每个任务在编程时侯的起始地址都是从0开始,但是在放到内存后,不可能所有的进程的起始地址都放到0,很显然需要各个进程的起始内存位置要错开,这就叫做地址的变换。地址的变换有下面两种。

a)        静态地址重定位

程序在被调入内存运行时一次性的将所有的内存地址转变,比如程序在0×8地址上存放了一条命令,这条命令的要求从0×67上读数据。如果这个程序在内存中被分配的“内存块”的起始地址是0×100的话,那么很显然,在0×108地址上,应该改为从0×167读数据。所有的内存读取操作都需要经过这个步骤。这个步骤是在装载程序到内存的时候一次性对所有的地址进行的。跟下面的动态地址重定位相比,这种方法不需要使用特别的硬件来实现(我个人比较困惑的是,我没有找到详细的资料来讲解这个变换是如何进行的,根据我已有的经验,我只知道有一种方法是可行的,就是在编译的时候通过ld脚本来指定程序在内存中运行的起始地址,而且程序的编写完全采用相对寻址的方式,装载程序时,直接将程序复制到前面定好的起始地址就可以了。)

b)        动态地址重定位

这种方式,程序被装载入内存的时候不进行地址的转换,直接放进去,而在执行程序时,对于每一行命令中的地址,都通过硬件,也就是MMU来进行地址转换。每执行一条命令就转换一次。这种方式的好处是很灵活,缺点是需要硬件MMU的支持以及每次执行命令都转换这个机制本身也占用系统的资源。
此时我们就可以回答上面的问题了,为什么最早的阶段出现的是“固定内存区域划分”的管理方式呢?因为在那个时候,MMU还没有出现。在没有MMU的系统上,只能通过静态地址重定向来进行地址转换,这个机制很死板,基本上也就定死了只能够采用这种“固定内存区域”管理,整个系统划分为几个大小各异的内存区域,而且每个内存区域在划分完之后就不能够变动了。在没有MMU的嵌入式操作系统上,一般很多都是采用这种“内存池”的管理方法。

l  MMU的开始使用

MMU在系统中出现了之后,出现了逻辑地址和实际物理地址的概念,这两个地址如何映射是能够控制的,这就让上面所提到的“可变内存区域划分机制”的实现成为了可能。

2)        可变内存区域划分:

上面的分配方法机制比较简单,缺点也很明显,固定大小的“块”容易形成浪费,例如本来只需要28K的内存的进程,但是系统中只有32K的块没有28K的块,那么它只能够选择32K的内存,这就形成了浪费。很容易想到的解决方法就是,把整个物理内存想象为一块大蛋糕,每当有进程被调入系统执行时,便从这块大蛋糕上随机的切下一块大小正正好的给它,下次再有进程要调进来的时候,再从蛋糕上面切一块正正好的给它。这中方式在没有MMU(广义的MMU,并非专指那些页式内存管理中的MMU)的时候,这种方法实现起来是不现实的。在MMU出现了之后,这种灵活的方式实现起来就很容易了,对于每个进程,只要用一个寄存器记录其的起始地址,就可以了。
在上面的两种内存管理方式中,有一些概念也是很重要的:

u 内存的分配策略的制定,最先适应算法,下次适应算法,最坏适应算法,快速适应算法,伙伴系统。。

u 内存的回收策略的制定,伙伴系统。。

u 内存的保护机制,载入时判断,动态检查

3)        分页/分段内存区域划分:

MMU的出现使得基于段页的内存管理流行起来,上面的1)和2)管理方法,不管是固定内存区域划分还是可变内存区域划分,每个进程分得的一小块内存蛋糕,在物理内存上都是连续的!!注意,确实是连续的!!在系统的初始化阶段,这种连续的缺点还不怎么能体现出来,当系统运行了一段时间之后,系统的内存伴随着进程的调入和调出,被切割成支离破碎的一部分时,就像蛋糕被东一刀,西一刀的胡乱切割时,此时会出现这样一种情况:系统中的总内存是充足的,但是却是分散的,而新的进程调入时,要求的却是一块连续的内存空间,即使要求的这块空间并不大,但是系统也有可能不能满足这个要求!!
这种对连续内存空间的要求导致了系统必须花费额外的精力来整理内存,将分散的空闲内存收集起来,并将它尽量的整理成连续内存,这要不断的移动数据,显然会提高系统的负荷!!
应对的策略就是离散化,也即是此处所说的页式管理,其精神就是将内存划分为固定大小的页,比如1M的内存,每页大小设为4K时,此时系统共有250个页,每次进程要求内存时,系统以页为单位分配给进程内存。注意,此时的页可以是不连续的!!
例如,进程需要12K的内存时,可以将进程的前1/3的内容存放在系统中空闲的第8个页框中,中间的1/3放在第13个页框中,最后的1/3存放在第17个页框中,这样,没有分配连续的12K的空间,进程也被调进了内存了。这就是离散化。很明显的是,对于每个进程我们还需要一个转换表,记录进程中的哪些内存存放于物理内存中的那几个页框中。而这张表就叫做页表。
在基于页式内存管理的系统中,每一个进程都需要一张这样的表,来记录本进程中被存放在内存的什么位置。

l  虚拟内存概念的出现

在MMU和分页式内存管理出现之后,就为虚拟内存的出现打好了硬件方面的基础,所谓虚拟内存的概念,是基于MMU和分页式内存管理的。

在使用了MMU之后,内存的分配就通过页、页框和页表之间的关系来实现的。

但是随着应用程序的越来越大和操作系统管理进程数量的越来越多和内存容量的限制,如进程大小为1GB,那么将整个进程调入内存执行的方式显然就不现实了。

于是出现了虚拟内存的概念,也即调入进程时,先将进程的一部分调进来运行,当运行到没有调入内存的部分时,产生一个缺页中断,然后将缺的那一页也调进来,然后继续执行。

具体的虚拟内存的实现就不展开了,各种书上都有,先列出在虚拟内存管理上的一些重要概念:

        a)页面的装入策略和清除策略

        b)页面的分配策略

        c)页面的替换


转载自:http://my.unix-center.net/~xiaoshe/2010/04/25/os%E7%9A%84%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E5%8E%86%E5%8F%B2%E5%8F%98%E8%BF%81%E4%BB%A5%E5%8F%8A%E6%89%80%E4%BA%A7%E7%94%9F%E7%9A%84%E5%87%A0%E7%A7%8D%E7%AE%A1%E7%90%86%E6%96%B9%E5%BC%8F%E7%AE%80/

原创粉丝点击