内存管理是内核是最复杂同时也是最重要的一部分,其中就涉及到了多种内存分配器,如果内核初始化阶段使用的bootmem分配器,分配大块内存的伙伴系统,以及其分配较小块内存的slab、slub和slob分配器。
1.bootmem分配器
bootmem分配器用于在启动阶段早期分配内存。该分配器用一个位图来管理页,位图比特位的数目与系统中物理内存页的数目相同。比特位为1表示已用页,比特位为0,表示空闲页。在需要分配内存时,分配器逐位扫描位图,直至找到一个能提供足够连续页的位置,即所谓的最先最佳或最先适配位置。
该分配提供了如下内核接口:
内核接口
说明
alloc_bootmem
alloc_bootmem_pages(size)
按指定大小在ZONE_NORMAL内存域分配内存
alloc_bootmem_low
alloc_bootmem_low_pages(size)
功能同上,只是从ZONE_DMA内存域分配内存。
free_bootmem
释放内存
每个分配器必须实现一组特定的函数,用于内存分配和缓存:
kmalloc、__kmalloc和kmalloc_node是一般的内存分配函数。
kmem_cache_alloc、kmem_cache_alloc_node提供特定类型的内核缓存。
2.slab分配器
功能:提供小的内存块,也可用作一个缓存。
分配和释放内存在内核代码上很常见。为了使频繁分配和释放内存所导致的开销尽量变小,程序员通常使用空闲链表。当分配的内在块不再需要时,将这块内存插入到空闲链表中,而不是真正的释放掉,这种空闲链表相当于内存块缓冲区。但这种方法的不足之处是,内核没有一种手段能够全局地控制空闲链表的大小,实时地更新这些空闲链表的大小。事实上,内核根本也不可能知道有多少空闲链表存在。
为了解决上述问题,内核心提供了slab层或slab分配器。它作为一个通用的内核数据结构缓冲层。slab层使用了以下几个基本原理:
经常使用的数据结构一般来说会被经常分配或释放,所以应该缓存它们。
频繁地分配和释放内存会导致内在碎片(不能找到合适的大块连续的物理地址空间)。为了防止这种问题,缓冲后的空闲链表被存放到连续的物理地址空间上。由于被释放的数据结构返回到了空闲链表,所以没有导致碎片。
在频繁地分配和释放内存空间在情况下,空闲链表保证了更好的性能。因为被释放的对象空间可立即用于下次的分配中。
如果分配器能够知道诸如对象大小、页大小和总的缓冲大小时,它可以作出更聪明的决定。
如果部分缓冲区对每-CPU变量,那么,分配和释放操作可以不需要SMP锁。
如果分配器是非一致内存,它能从相同的内存结点中完成分配操作。
存储的对象可以被着色,以防止多个对象映射到同一个缓冲。
linux中的slab层就是基于上述前提而实现的。
slab层将不同的对象进行分组,称之为“缓冲区(cache)”。一个缓冲区存储一种类型的对象。每种类型的对象有一个缓冲区。kmalloc()的实现就是基于slab层之上的,使用了一族通用的缓冲区。这些缓冲区被分成了一些slab。这些slab是由一个或多个物理上连续的页组成的。每个缓冲区可包含多个slab。
每个slab包含有一些数量的对象,也即被缓冲的数据结构。每个slab问量处于三种状态之间:满、部分满、空。当内核请求一个新的对象时,它总是先查看处于部分满状态的slab,查看是否有合适的空间,如果没有,则空的slab中分配空间。
每个缓冲区由一个kmem_cache结构来表示。该结构包含了三个链表:slabs_full,slabs_partial和slabs_emppty。存储在一个kmem_list3结构中。
slab分配器接口
接口名称
说明
kmem_cache_create
分配一个cache
kmem_cache_destroy
销毁一个cache
kmem_cache_alloc
从一个cache中分配一个对象空间
kmem_cache_free
释放一个对象空间到cache中
这些接口不宜在中断上下文中使用。
旁观者的姓名永远爬不到比赛的计分板上。