1. 内存管理
1. 综述
为了与intel 8086 实地址(real mode)操作兼容,继续保持段地址模式。段地址模式中使用几个关键的段寄存器,如:CS, DS, SS, ES, 在80386模式下增加了FS, GS。这些段寄存器在保护模式下作用与实模式下根本不同,在保护模式下是作为GDT(全局描述苻表)的下表,在保护模式下,段描述保存在描述府表中,起地址保存在CPU的GDTR寄存器中,这个在内核初始化中被确定。段与内存描述府PID中的域MM对应,在80386中内存寻址通过两重映射定义,一重是段映射,从逻辑地址到线性地址,另一重是页映射,从线性地址到物理地址。这两重映射都由CPU内的硬件电路完成,速度非常快。这两重映射提供了近视相同内存保护功能,Linux更多使用的是页映射。应为:物理内存的管理以页为单位(页大小是4K或者是4M),页能提高内存的使用效率,页大小固定,是磁盘块大小的整数倍,有利于解决虚拟内存的问题,另外其他种类的CPU对段支持不是很好,像PowerPC, RISC类的。Linux内存管理提供了两种内存的管理,一种是物理内存的管理,管理对象是物理地址,另外一种是虚拟内存的管理(进程内存的管理),管理对象是线性地址,针对的是进程。
段管理
如前所示,段在Linux没有广泛应用,从Linux进程的创建过程中看,实际上所有的进程使用的可以分为两类,一类是内核进程,另外一类是用户进程。两类进程被严格区分,以起到保护的作用。两类进程的线性空间也严格区分,线性地址空间分别是4G和3G。在Linux初始化中,就出现了几个段描述府,分别是KERNEL__CODE,KERNEL__DATA。段管理是得到进程的线性地址空间,这个空间对于大部分的进程都是一致的,就是4G,内核也没有太多的数据结构和算法来处理,唯一要理解的就是GDT,这个表的作用以及它在进程运行过程中的作用。
进程虚拟空间管理(线性地址空间的管理)
所有进程都不会直接操作物理地址,这是保护模式和实模式方式的最大区别,因此这也提供了更好的内核保护。但这样的做法是有代价的,一个是硬件上的,CPU增加了复杂度,外是必须在物理内存中使用一些块,来完成地址的映射。另外是软件上的,必须有一定的数据结构来管理如此大的空间(4G)。线性地址空间的管理是为了得到进程的物理内存空间,从线性到物理的映射对于运行的进程来说是完全透明的,进程只管理线性空间的分配,因此在进程中,malloc类的函数它们分配都是线性空间,就是在目录表中找到空缺的项,填入这个线性空间,为管理做准备,malloc不管物理内存的映射,这个由缺页中断完成,这个中断是内核中非常重要的中断,它处理分配实际物理内存的工作,这个工作的意外情况多,并且和物理内存管理相联系。Linux采用了三级映射,实际上在32位CPU中两级映射就够了,但为了照顾到64位的CPU(Linux能在64位的CPU上运行),所以中间一级在32位的CPU中是空的,就直接映射到第三级中去了。在虚拟空间管理中要注意一些问题,一个是页表的管理,这个应该是每一个进程有一个具体的页表,页表占用的内存也是挺大的,想象一下如果真得把4G的空间都使用了,一个页表是4字节,一个页表是4K,那么所有这些页表占用了4M的空间。另外就是所有的进程都是一样的虚拟空间,都是从0开始,到4G,所以在进程切换的过程中有一个必不可少的过程就是CR3的内容切换,这个寄存器保存这页表的起始地址。为了管理页表,在进程内存管理中,引入了AVL数来管理虚拟内存块,这里就涉及到AVL插入,删除,旋转,合并等复杂的操作,目的就为了一个加快查找的速度。这个可以说是内存中比较复杂的算法和数据结构了。
物理内存的管理
物理内存的管理,Linux参照了《Unix操作系统的设计》这本书和SUN操作系统的slab算法。具体位对物理内存块采用了伙伴算法,对于slab算法目前没有细看,但算法很复杂。
Linux的内存管理分为三个模块,每一个模块都有具体的功能和接口。上述中没有设计到的是cache的管理和swapd的管理,前者是完全硬件实现,对于操作系统也是透明的,后者涉及到文件系统的管理,这里也没有详细说明。