一、概述
内核管理内存分为两个部分,一个是管理内核空间的内存,一个是管理用户进程空间的内存。现在的操作系统都引入了虚拟内存来对内存进行造作,使用虚拟内存,可以保护内核的安全,而且可以使应用程序使用连续的、比时间内存更大的内存空间,平时我们使用的内存地址就是虚拟内存地址,然后由内存管理单元转化成真正的物理地址后,才能进行内存的操作。
32位的Linux系统可以访问4GB的虚拟内存(物理内存不一定有这么大),并且将其分成两部分:低3GB是应用程序地址空间,高1GB是内核地址空间。注意:内核中所有的线程共享这1GB的地址空间,而每个应用进程都是有独立私有的3GB地址空间。
二、内存的划分
内核将内存按照Node->Zone->Page来进行划分,通常我们的单机系统,整个内存就一个Node,固不用考虑Node。
1、页(Page)
内核通过页的机制来管理整个物理内存,将物理内存分成许多大小相等的页,并将其映射到虚拟内存上。Linux用page结构来表示每个物理页的当前状态,而不是描述物理页中的数据。它只与物理页有关,与虚拟页无关,而且它对物理页的描述是暂时的,即使页中所包含的数据继续存在,但由于交换等原因,它们可能不再和同一个物理页面相关联。Page中有一个virtual域表示页的虚拟内存,对于内核的1GB虚拟内存空间,有些内存是固定映射到内核虚拟空间的,这些内存的virtual是固定的,而有些内存(高端内存)不能固定映射到内核地址空间,则该域是动态变化的。
2、区(Zone)
由于硬件的限制,有些页位于特定的物理地址上,不能将其用于一些特定的任务,所以要把物理内存的页进行分类,即分成不同区,用于完成不同的任务。分区主要用来解决两方面的问题:一是:有些硬件只能用某些特定的内存来进行DMA.二是:一些体系结构的物理内存范围比虚拟内存大的多,因此,有些内存不能永久的映射到内核空间上。物理内存分为三个区(每个区域的空间范围不是固定的,取决于具体的体系结构):
ZONE_DMA(0~16MB):DMA内存的分配区域
ZONE_NORMAL(16~896MB):正常映射的内存区域
ZONE_HIGHMEN(896MB~):高端内存区域,其中的页不能永久的映射到内核地址空间,只能做动态映射。
对于DMA和Normal两个区,内核直接把物理地址和内核虚拟地址进行永久映射了,即这些物理页所对应的Page中的virtual总是不变的,也就是说虚拟地址0xC0000000(内核空间的首地址,3GB处)直接映射到物理地址的首地址0。而对于高端内存,物理页不能永久映射,只能在分配物理页后动态分配,即内核虚拟空间中剩余的地址(0xC0000000+896MB~0xFFFFFFF的最后128M线性地址)由物理内存剩余的空间896M以上的物理页框(4GB-896MB)非直接映射。有3种映射方法:非连续内存区映射,永久内核映射,临时内核映射(固定映射),具体参考:http://blog.chinaunix.net/uid-20775243-id-2555019.html
用户空间当然可以使用高端内存,而且是正常的使用,内核在分配那些不经常使用的内存时,都用高端内存空间(如果有),所谓不经常使用是相对来说的,比如内核的一些数据结构就属于经常使用的,而用户的一些数据就属于不经常使用的。用户在启动一个应用程序时,是需要内存的,而每个应用程序都有3G的线性地址,给这些地址映射页表时就可以直接使用高端内存。而且还要纠正一点的是:那128M线性地址不仅仅是用在这些地方的,如果你要加载一个设备,而这个设备需要映射其内存到内核中,它也需要使用这段线性地址空间来完成,否则内核就不能访问设备上的内存空间了。总之,内核的高端线性地址是为了访问内核固定映射以外的内存资源。实际上高端内存是针对内核一段特殊的线性空间提出的概念,和实际的物理内存是两码事。进程在使用内存时,触发缺页异常,具体将哪些物理页映射给用户进程是内核考虑的事情。在用户空间中没有高端内存这个概念。
Linux把系统的页划分为区,形成不同的内存池,这样就可以根据用途进行分配了。例如:ZONE_DMA内存池让内核有能力为DMA分配所需的内存。如果需要这样的内存,那么内核就可以从ZONE_DMA中按照请求的数目取出页。内核将页分区只是为了管理页而采取的一种逻辑上的分组。
三、内核空间内存的分配与回收
1、内核中的每个区的物理内存页是通过BUDDY算法来进行分配与回收的,它是基于页的伙伴分配算法。
2、当区中的页分配好之后,会用slab分配器来进行对象的分配,slab分配器主要用于需要频繁分配与回收的对象,这样可以节省开销。具体的算法是:slab分配器把不同的对象划分成高速缓存组,每种对象对应一个高速缓存,例如一个高速缓存用于存放进程描述符,而另一个用于存放索引节点对象。然后再把高速缓存划分成若干个slab。每个slab由若干个页组成(通常是一个页),每个slab被划分成该高速缓存所对应的对象。每个高速缓存中有三种状态的slab,分别是:满、部分满、空。当需要一个该对象时,就从部分满的slab中分配一个对象。当所有的slab都满时,就需要从高速缓存中创建一个新的slab。
四、进程地址空间
进程的地址空间范围是0~3GB一般由高端物理内存(>896MB)映射,但是这并不代表它就有权访问所有的虚拟地址。当进程被调度运行时,会被分配一个唯一的进程地址空间,每个进程只能访问自己的进程地址空间。Linux的用户进程的起始地址一般从0x08048000开始,比如进程A可访问的空间是0x08048000~0x08049000,进程B可访问的是0x0804A000~0x0804C000等。当然,不同的进程可以共享同一段进程地址空间,如线程。
每个进程地址空间又分成若干内存区域,每一个区域代表一种内存对象,比如:代码段、数据段、bss段、用户空间栈等。
进程、进程地址空间、内存区域三者之间是通过如下建立联系的:进程描述符task_struct的mm域指向进程的内存描述符mm_struct (内核使用内存描述符表示进程的地址空间),mm_struct中的mmap指向进程地址空间中的内存区域vm_area_struct链表,vm_area_struct中的mm_struct指向该内存区域所属的进程地址空间。