CPU和内存之间有三根总线,地址,数据,控制总线。CPU和内存之间用地址来查找数据,但是两者的地址并不总是一样的,CPU产生的是逻辑地址,而内存的就是物理地址。通常都是不一样的,所以需要地址映射。
正好是从编程人员的角度看,(不考虑解释执行)程序总是经过源程序编译,连接,运行三个阶段。在这个过程中,指令和数据就要调到内存。
地址捆绑的三种形式:
编译时:编译时就生成了绝对地址。MS-DOS的COM程序就是这样的。
加载时:编译时编译器生成可重定位代码,在加载时捆绑。
执行时:执行时才能绑定。
上面说的都是内存中的物理地址,对于编译时和加载时的地址,物理地址和CPU产生的逻辑地址都是一样的。但是对于运行时的绑定,逻辑地址(又称虚拟地址)和物理地址是不一样的。这时候就需要有一个地址映射的问题,这个问题由MMU(内存管理单元)来完成。
这个实现其实就像是分段寻址一样,有一个重定位寄存器,其实就是基址寄存器,cpu生成的都是逻辑地址,也就是说都是个偏移量,当要把数据放到内存里时,总是要将偏移量加上基址才是真正的物理地址。
用户处理的也都是逻辑地址,也就是说我们编程时查看的地址都是逻辑地址。
动态加载:
就是说只有当运行时,并且程序调用子程序时,链接程序才将子程序调到内存里,否则总是放到磁盘上存储的。它也是先检查是否子程序已经被加载,没有的时候才加载。
动态链接:
运行时才进行链接。否则程序需要的模块都将被调入到内存,浪费空间。内存的空间并不是很大,尤其它必须要装载操作系统内核和用户进程。当进程分配的空间不够大时,有两种策略:覆盖和交换。
覆盖:将内存中不再需要的空间覆盖掉,用来存放需要的数据。
交换:将暂时不用的空间的数据交换出去到辅存,需要时再调回来。
但是仍然,对内存空间的合理分配是必要的,不同方法:
1)连续分配内存:内存连续
2)分页技术:不需要连续的内存。
3)分段:用户思考。
4)连续分配内存:将内存划分为固定大小的很多分区,规定每个分区存放一个进程的数据。
这是基本思路,它的推广就是将分区想象成诸多的孔(hole),总是找合适的孔来存放进程。用孔的概念就是来说明它的一定的灵活性,孔是由一些大小不等的孔组成的,但是你看起来还是一个孔,如果进程比较小,就划出一个小孔,其他的不用,进程大的话就组成一个大孔。如果进程太多就等待,直到可以容纳为止。有一点动态分配的意思。所以它是变长分区的,而不用一个固定分区存放一个进程。
但是本质是一样的,进程分配的内存空间总是连续的。这样有好处,一个进程总是被固定在一定的范围之内,这样有利于进程保护:当进程出错时,它能影响的也只是自己所在的那一块内存,而不会导致大量的错误。但是这样会产生碎片。也就是两个进程之间总有些空间是没有使用也无法使用的。所以有了分页的方法:它允许进程的地址可以是非连续的。物理内存中还是分为固定大小的片段,叫做帧,对应的,逻辑内存(也就是cpu产生的逻辑地址的空间)也分为同样大的片段,叫做页,再同样的,辅存中对应的叫做块。
由于cpu产生的都是逻辑的地址,于是我们完全可以使它是连续的,因为用户使用的都是逻辑地址,所以我们在查看地址时看到的总是连续的,我们看到的就是逻辑内存。但是在实际的内存中它可以不是这样的。
想想吧,那么我们编程时查看的连续的内存,以及我们画的长方形的内存图,都是逻辑内存,物理内存我们是没法知道的,因为那是操作系统根据帧表分配的。帧表是什么呢?帧是物理内存中的内存片段,它是对应页的,所以帧表就是记录那些被使用的页。比如用1表示,另外不用的用0表示,当新的进程要求分配内存空间时,操作系统就查看帧表就行了,哪些部分是1,忙着呢,哪些部分是0,好可以用!
这些连续的东西对应到实际的内存的物理空间,如果用连续内存分配的方法,就是一块地址完全对应。但是用分页呢,逻辑地址看上去是连续的,但是它映射到物理地址上就可以有一些处理方法了,这里用的就是一张页表,很简单的记录着cpu上逻辑的地址对应着内存中的哪个物理地址。好像很多事我们都是这样做的。剩下的就是页的大小的问题了,想想吧,如果页太大,比如大到完全容纳一个进程,那么就和连续内存分配一样了,但是如果页太小,小到内存中一个内存单元就是一个片段,是不是也好像没问题?但是这样的话页表就太可怕了,1G的内存需要1G项个地址么?不能也不需要,所以总要设定一个合适的页大小。
其实这样的方法也就是一种重定位,逻辑地址是一个偏移量,不同的页就是不同的基址,然后基址加偏移就是物理地址了。
采用分页的好处呢?它不会产生外部碎片。但是如果到最后一个页内,它很可能会产生内部碎片的。另外,页表也需要额外的开销。
分段:
我们分析程序时,总是考虑它在内存中是怎样的。我们知道全局变量和常量是放在一起的。代码段是放在栈区的,动态分配的数据是放在堆区的。但是这些区真的存在么?即使不存在,我们查看内存时,栈区的地址都是连续的,堆区的地址也都是连续的,好像它就是各个区占一部分连续内存的。
但是你从前面方法中能看得出来么?连续内存分配是将整个进程的数据都一排溜的放在一起,分页只是分了内存片段,但是对于具体数据它是透明的,它没有对各种数据,指令还是纯数据做不同的处理。
分段的技术允许编译器定义自己的段,我们说的堆区,栈区就是一种编译器设定的。也就是这样,编译器才能够识别编程代码。这样它的很多功能就可以实现了,比如指令和数据放在不同的地方,数据是属于用户的可以修改,指令却是属于操作系统的,它可以被定义为只读,不能被改变。
分段仍然需要逻辑地址和物理地址的映射。只不过它需要的是段表,段表记录不同的数据位于什么“段”,它当然也是用地址标记的,但就好象是给内存的段取个名字一样。地址从几到几,代码段,从几到几,堆区……是不是已经很好理解了?
其实它和分页是差不多的,分页也可以这么理解。只不过分页是固定大小的。而分段却是动态分配的,并且它有不同的“使命”;
分段也会有内存碎片。
分段和分页各有优缺点,段表和页表都需要额外的开销。分页不会产生外部碎片,但是它隔离了逻辑地址和物理地址,并且毫无章法,完全动态。分段会产生外部碎片,但是它从我们的思考考虑,给内存加了别名。现在的思路总是取长补短,两者合并,分段加分页。
关键词:
硬件支持:到底采用什么方法来进行内存管理最大的决定因素是硬件支持。逻辑地址到物理地址的映射都是靠硬件来实现的。无论是连续分配还是段表还是页表,都是一帮子地址寄存器,基址寄存器记录内存片段的开始,而另一个寄存器保存长度信息等等,编程中的变长数组(其实就是堆)就是这么做的。
碎片:固定大小分配单元(分页)会产生内部碎片。而不定长的方法(多分区和分段)会产生外部碎片。
共享:分段和分页时进程总是分为了多个模块,如果它们保存的是特定的功能,那么这些模块可以是共享的。而连续分配总是一段数据都是属于一个进程。
保护:仍然是分段和分页的多个模块,如果它们保存的是特定的功能,可以设置是否只读,这就是保护。
本文转载于“http://blog.csdn.net/csulimao/article/details/9275909”