前言
前文通过存储桶了解了 slab 分配器最简单的实现方式,在后续的 linux 版本中,slab 分配器被正式提出,并用于解决内核中小样本分配问题。笔者打算先从 2.6.18 入手进行 slab 分配器的学习
主要参考资料为 《深入Linux内核架构》
slab 分配器
物理页并不是内存管理的最小单位,为了减少内部碎片,提出了 slab 分配机制。该机制其实是内存桶的复杂版,主要入口函数为 kmalloc 。其核心思想与内存桶类似,即在物理页上进一步划分连续的相同大小对象,并对此进行管理。在此使用 《简说Linux之slab机制》 做的图,能够很好地说明slab层的设计思想
即,cache_chain 关联了多个不同大小的 kmem_cache,每个 kmem_cache 关联一个 slabs ,slabs 由多个 slab 组成,每个 slab 又对应多个 page,每个 page 按照 kmem_cache 对应大小平分了多个 object 空间。实际上,slab 的实现与 linux 0.12 版本中 内存桶的思想较为类似。 《深入Linux内核架构》中也有相关图示,如下
- kmem_cache :管理一整个结构。不同类型对象的 kmem_cache 不同,不同 size 也对应不同的 kmem_cache
- array_cache: 用于进行 slab 缓存。当需要分配对象时,先从缓存中找,找不到再去 slab 空闲链表中获取。不同 cpu 对应不同 array_cache。这么做属于本地缓存,避免了多核竞争需要上锁带来的消耗。
- slab链表:可以看到 kmem_cache 中涉及空闲、部分空闲、用尽三种类型的 slab 链表,其串联了相应的 slab 。要注意的是 slab 头部结构与 Obj 内容在同一页帧中
而每个 kmem_cache 又通过双向链表组织
主要数据结构
struct cache_sizes malloc_sizes[];
struct cache_sizes {
size_t cs_size;
kmem_cache_t *cs_cachep;
kmem_cache_t *cs_dmacachep;
};
struct kmem_cache {
/* 1) per-cpu data, touched during every alloc/free */
struct array_cache *array[NR_CPUS];
/* 2) Cache tunables. Protected by cache_chain_mutex */
unsigned int batchcount;
unsigned int limit;
unsigned int shared;
unsigned int buffer_size;
struct kmem_list3 *nodelists[MAX_NUMNODES];
gfp_t gfpflags;
size_t colour; /* cache colouring range */
unsigned int colour_off; /* colour offset */
struct kmem_cache *slabp_cache;
unsigned int slab_size;
unsigned int dflags; /* dynamic flags */
const char *name;
struct list_head next;
// 省略部分属性
};
struct kmem_list3 {
struct list_head slabs_partial; /* partial list first, better asm code */
struct list_head slabs_full;
struct list_head slabs_free;
unsigned long free_objects;
unsigned int free_limit;
unsigned int colour_next; /* Per-node cache coloring */
spinlock_t list_lock;
struct array_cache *shared; /* shared per node */
struct array_cache **alien; /* on other nodes */
unsigned long next_reap; /* updated without locking */
int free_touched; /* updated without locking */
};
struct array_cache {
unsigned int avail;
unsigned int limit;
unsigned int batchcount;
unsigned int touched;
spinlock_t lock;
void *entry[0]; /* 柔性数组
};
struct slab {
struct list_head list;
unsigned long colouroff;
void *s_mem; /* including colour offset */
unsigned int inuse; /* num of objs active in slab */
kmem_bufctl_t free;
unsigned short nodeid;
};
对于上述各结构之间的关系,笔者绘制如下关系图进行描述
简单地,slab 分配过程为:
- 遍历 malloc_size 找到合适大小的 kmem_cache
- 先从 kmem_cache 的 array 判断 entry 中是否有空闲的 obj ,若有则直接返回地址
- 若无则获取 nodelists 中的 slabs_partial ,遍历 slab 看是否有可用的 obj,找到后将 obj 放入 entry 中,并返回地址
接下来会根据源码对上述过程进行细化
kmalloc (2.6.18)
slab分配器的分配入口为 kmalloc,其调用链如下
kmalloc
--- __kmalloc ==> __do_kmalloc
--- __find_general_cachep : 根据 size 和 flags, 从 malloc_sizes 中获取对应的 kmem_cache
--- __cache_alloc ==> ____cache_alloc : 根据 kmem_cache 进行分配
__find_general_cachep 很简单,通过 size 遍历 malloc_sizes 找到合适的 kmem_cache 即可
static inline struct kmem_cache *__find_general_cachep(size_t size,
gfp_t gfpflags)
{
struct cache_sizes *csizep = malloc_sizes;
while (size > csizep->cs_size)
csizep++;
if (unlikely(gfpflags & GFP_DMA))
return csizep->cs_dmacachep;
return csizep->cs_cachep;
}
____cache_alloc 函数中,首先通过 cpu_cache_get 获取到对应 kmem_cache 在当前 CPU 下的 array_cache。并判断是否为空,不为空直接获取 objp。否则要执行 cache_alloc_refill 从具体 slab 结构中获取。
具体地,array_cache 中的 entry 为指针数组,存放缓存 obj 的指针。跟踪 array_cache 的定义会发现,entry 被定义为 void *entry[0]。即这是一个长度为0的数组?实际上这是一个小知识点,该类型数组在 c语言中称为柔性数组,参考 《C语言柔性数组》,其只会出现在结构体最后一个属性,可以后续动态的为其申请不定长度的内存,进行使用。
static inline void *____cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
void *objp;
struct array_cache *ac;
check_irq_off();
ac = cpu_cache_get(cachep);
if (likely(ac->avail)) {
STATS_INC_ALLOCHIT(cachep);
ac->touched = 1;
objp = ac->entry[--ac->avail];
} else {
STATS_INC_ALLOCMISS(cachep);
objp = cache_alloc_refill(cachep, flags);
/*
* the 'ac' may be update