在理解linux内存管理之前需要知道80x86的分段和分页单元把逻辑地址转换为物理地址的有关内容。整个系统的性能取决于如何优先地管理动态内存。从两个角度介绍:连续物理内存处理、非连续内存区的处理。
Linux采用4KB页框大小作为标准的内存分配单元。内核把物理页作为内存管理的基本单位。内存管理单元(MMU,管理内存并把虚拟地址转换为物理地址的硬件)通常以页为单位进行处理。MMU以页大小为单位来管理系统中的页表(这也是页表的由来)。从虚拟内存的角度来看,页就是最小单位。
1)页描述符
内核必须记录每个页框的当前状态。页框的状态信息保存在一个类型为page的页描述符中,所有的页描述符存放在meme_map数组中。下面为struct page的结构体,其中省去了两个联合结构体。
struct page{ unsigned long flags; atomic_t _count; atomic_t _mapcount; unsigned long private; struct address_page *mamming; pgoff_t index; struct list_head lru; void *virtual; }
flags 一组标志,也对页框所在的管理区进行编号,包含多达32个用来描述页框状态的标志;
_count 页的引用计数器。为-1,则相应页框空闲,并可被分配给任一进程或 内核本身,如果大于0,说明页框被分配给了一个或多个进程,或用于存放一些内核数据结构。
上图为node、page、zones之间的区别联系
2)内存管理区
一个页框就是一个内存存储单元,可用于任何事情。任何种类的数据页都可以存放在任何页框中,没有限制。
每个页描述符都有到内存节点和到节点内管理区(包含相应页框)的链接。
内存区,也就是说具有连续的物理地址和任意长度的内存单元序列。伙伴算法采用页框作为基本内存区,这适合于对大块内存的请求,但对于小内存区的请求同样需要办法。
1)slab分配器
新的算法基于下列前提:
slaba分配器把对象分组放进高速缓存。包含高速缓存的主内存区被划分为多个slab,每个slab由一个或多个连续的页框组成,这些页框中既包含已分配的对象,页包含空闲的对象。
1)高速缓存描述符
每个高速缓存都是由kmem_cache_t类型的数据结构来描述的,高速缓存中的每个slab都有自己的类型为slab的描述符。
2)普通和专业高速缓存
高速缓存被分为两种类型:普通和专用。普通高速缓存只由slab分配器用于自己的目的,而专用高速缓存由内核的其余部分使用。
把内存区映射到一组连续的页框是组号的选择,这样利用高速缓存和较低的访问时间,不过,如果对内存区的请求不是很频繁,那么,通过连续的线性地址来访问非连续的页框这样一种分配模式就会很有意义。这种模式的有点避免了外碎片,缺点是必需打乱内核页表。linux在以下几个方面使用非连续内存区:为活动的交换区分配数据结构、为模块分配空间、给某些I/O驱动程序分配缓冲区。
1)非连续内存区的线性地址
要查找线性地址的一个空闲区,可以行PAGE_OFFSET开始查找(通常为0xc0000000,既第4个GB的起始地址),下图为第4个GB的线性地址:
为非连续内存区保留的线性地址空间的其实地址由VMALLOC_START宏定义,而末尾地址由VMALLOC_END宏定义。
2)非连续内存区的描述符
每个非连续内存区都对应着一个类型为vm_struct的描述符。这些描述符在一个简单的链表中。get_vm_area()函数在线性地址VMALLOC_STRAT和VMALLOC_END之间查找一个空闲区域。该函数使用两个参数:将被创建的内存区的字节大小(size)和指定空闲区类型的标志(flag)。步骤执行如下:
3)分配连续的内存区
vmlloc()函数给内核分配一个非连续内存区,参数size表示请求内存区的大小。如果这个函数能够满足请求,就返回新内存区的起始地址,否则,返回一个NULL指针:
函数首先将参数size设定为4096(页框大小)的整数倍。然后,vmalloc()调用get_vm_area()来创建一个新的描述符,并返回分配给这个内存区的线性地址。flags初始化为VM_ALLOC标志,意味着通过使用vmalloc()函数,非连续页框被映射到一个线性地址空间。然后vmalloc()函数调用kmalloc()来请求一组连续页框,这组连续页框足够包含一个页描述符指针数组。然后memset,接着重复调用alloc_page()函数,每一次为区间中nr_pages个页的每一个分配一个页框,并把对应页描述符的地址存放在area->pages数组中。到这里,已经得到一个新的连续线性地址空间,并且已经分配了一组非连续页框来映射这些线性地址,最后重要的是修改内核使用的页表项,以此表明分配给非连续内存区的每个页框现在对应着一个线性地址,这个线性地址被包含在vmalloc()产生的非连续线性地址区间中。这就是map_vm_area()所要做的。
3)释放非连续内存区
vfree()函数释放vmalloc()创建的非连续内存区。与vmalloc()一样,内核修改主内核页全局目录和它的子页表中的相应项,但是映射第4个GB的进程页表的项保持不变。
参考资料:《linux内核设计与实现》 《深入理解linux内核》