slab内存分配器是linux内核中比较经典的内存分配器,目前已经被slub内存分配器取代了。不过为了致敬经典,更是为理解slub分配器做铺垫,我还是会从slab分配器讲起。
这个问题其实很好回答,之所以提出slab分配器,是因为buddy system只能按page对齐来分配内存。然而大多数情况下,需要的内存size都不是按page对齐的,如果直接通过buddy system分配内存,就会出现很大的内存碎片,内存碎片即分配了却没有使用,也无法再被分配的内存。
正是由于buddy system的这种限制,slab分配器应运而生。slab分配器的底层依托于buddy system,上层却对用户提供了更加灵活的内存分配服务。
slab分配器工作机制主要包括两点:
当然,以上两点只是slab分配器最本质的工作机制,我们后面会介绍细节。
slab是slab内存分配器从buddy system申请页面的基本单位。然而slab的大小不是固定的,slab从属于某个kmem cache实例,不同的kmem cache实例,其slab的大小是不同的。slab的大小必须是2^order个pages,order不能超过buddy system所支持的最大的order。
slab内存分配器从buddy system分配了slab之后,会将其挂在对应的kmem cache实例的node节点。
object是slab内存分配器对外提供的申请内存的基本单位。slab内存分配器从buddy system申请了buddy之后,会将其拆分成一个个object,并缓存在kmem cache实例的cpu_cache中,用户申请内存时,其实获取的就是一个个object。
一旦object缓存耗尽,就会重新从buddy system申请slab,并将其拆分成object,放入内存池。
slab内存分配器中的cache跟硬件cache无关,是一个纯软件的概念。slab内存分配器有两种cache,一个是slab的cache,一个是object的cache。slab内存分配器从buddy system获取页面后,会将其加入kmem cache的node节点,这个就是slab的cache;将slab拆分成多个object,并将object加入kmem cache的cpu_cache内存池,这个就是object的cache;可以看到这两种cache实际是对共同的物理页面的两种缓存形式。
预分配多个面向用户的kmem cache实例
这些kmem cache实例预分配,其初始信息定义在kmalloc_info中。这些kmem cache实例的object size是预定义好的。普通用户一般使用这些预分配的kmem cache实例即可,当然这些kmem cache实例对用户来说也是不可见的,用户通过调用kmalloc函数,就会根据所要分配的memory size找到最合适的kmem cache实例。
用户直接用使用预分配的kmem cache有一个很明显的缺陷,就是会产生内存碎片。因为用户申请的memory size并不总是等于kmem cache所维护的object size,slab分配器会选择object size不小于memory size并且两者差值最小的kmem cache,这就会产生内存碎片。
用户动态分配的kmem cache实例
对于一些有想法的用户,可以调用kmem_cache_create动态分配kmem cache实例,通过该kmem cache申请内存不能再使用通用的kmalloc,而需要使用kmem_cache_alloc函数来分配。
好处也是显而易见,需要多大的内存,就创建多个的object,不存在内存碎片。那么为什么内存不抛弃预分配的kmem cache实例,完全采用该方案呢?其实稍微思考一下就明白了,用户需要分配的memory size五花八门,每一种memory size所需要分配的次数也是不确定的,完全采用这种方案会造成更大的浪费。
slab内存分配器维护了多个kmem cache实例,调用kmem_cache_create创建kmem cache实例的时候,会设置object size。而slab size是根据object动态选择的,选择的步骤如下:
理解一个模块,必须要先理解其数据结构,以及各个数据结构之间的关系,一旦将这些搞清楚,代码逻辑顺理成章就看明白了。当然,要理解数据结构,必须要辅以阅读代码逻辑,二者也算是相辅相成。
另外,我并不赞成一开始就将数据结构中所有的成员全部搞清楚,学习是一个迭代的过程,一开始只需要将主要的成员及成员关系搞清楚就可以了。后面随着对模块理解的深入,之前一些难以理解的成员,可能自然而然就理解了。
slab分配器的数据结构不多,也很简单,数据结构之间的关系也不复杂。所以slab分配器并不复杂,我用一个词来给slab分配器贴上一个标签的话,我会选择优雅这个词。
结构体定义如下(删除了一些无关紧要的成员):
struct kmem_cache {
struct array_cache __percpu *cpu_cache;
/* 1) Cache tunables. Protected by slab_mutex */
unsigned int batchcount;
unsigned int limit;
unsigned int shared;
unsigned int size;
struct reciprocal_value reciprocal_buffer_size;
/* 2) touched by every alloc & free from the backend */
unsigned int flags; /* constant flags */
unsigned int num; /* # of objs per slab */
/* 3) cache_grow/shrink */
/* order of pgs per slab (2^n) */
unsigned int gfporder;
/* force GFP flags, e.g. GFP_DMA */
gfp_t allocflags;
size_t colour; /* cache colouring range */
unsigned int colour_off; /* colour offset */
struct kmem_cache *freelist_cache;
unsigned int freelist_size;
/* constructor func */
void (*ctor)(void *obj);
/* 4) cache creation/removal */
const char *name;
struct list_head list;
int refcount;
int object_size;
int align;
struct kmem_cache_node *node[MAX_NUMNODES];
};
该结构体是slab内存分配器最顶层的结构体,其他结构体基本上都从属于该结构体。
成员 | 解释 |
---|---|
cpu_cache | 该kmem cache实例中object的缓存 |
batchcount | |
limit | |
shared | |
size | |
reciprocal_buffer_size | |
flags | 描述内存属性的标识 |
num | 一个slab中object的数量 |
gfporder | 一个slab的order,即2^order个连续物理页面 |
allocflags | 内存分配的标识 |
colour | |
colour_off | |
freelist_cache | 为freelist分配空间的kmem cache实例 |
freelist_size | freelist的空间的大小,freelist是一段连续的内存空间,这段内存空间存放object 的索引,所以freelist size跟object的数量有关。freelist区域可以存放在slab内部,也可以存放在slab外面,此时需要专门为freelist分配空间,就会用到freelist_cache. |
ctor | |
name | kmem cache实例的名字 |
list | |
refcount | |
object_size | object的size |
align | |
node | 用于管理该kmem cache实例中所有的slab |
结构体定义如下(删除了一些无关紧要的成员):
struct kmem_cache_node {
spinlock_t list_lock;
#ifdef CONFIG_SLAB
struct list_head slabs_partial; /* partial list first, better asm code */
struct list_head slabs_full;
struct list_head slabs_free;
unsigned long num_slabs;
unsigned long free_objects;
unsigned int free_limit;
unsigned int colour_next; /* Per-node cache coloring */
struct array_cache *shared; /* shared per node */
struct alien_cache **alien; /* on other nodes */
unsigned long next_reap; /* updated without locking */
int free_touched; /* updated without locking */
#endif
};
该结构体用于管理所有的slab。
成员 | 解释 |
---|---|
slabs_partial | 包含空闲和非空闲object的slab放在该链表 |
slabs_full | 不包含空闲object的slab放在该链表 |
slabs_free | 只包含空闲object的slab放在该链表 |
num_slabs | slab的数量 |
free_objects | 空闲object的数量 |
free_limit | |
colour_next | |
shared | |
alien | |
next_reap | |
free_touched |
结构体定义如下(删除了一些无关紧要的成员):
struct array_cache {
unsigned int avail;
unsigned int limit;
unsigned int batchcount;
unsigned int touched;
void *entry[]; /*
* Must have this definition in here for the proper
* alignment of array_cache. Also simplifies accessing
* the entries.
*/
};
该结构体用于管理所有的object。
成员 | 解释 |
---|---|
avail | 可用的object的数量 |
limit | |
batchcount | |
touched | |
entry | 指向object的指针,每一个成员指向一个object |