kernel 3.10内核源码分析--slab原理及相关代码
1、基本原理
我们知道,Linux保护模式下,采用分页机制,内核中物理内存使用buddy system(伙伴系统)进行管理,管理的内存单元大小为一页,也就是说使用buddy system分配内存最少需要分配一页大小。那如果需要分配小于一页的内存该怎么办呢?
另一方面,内核中经常需要大量的数据结构(比如struct task_strcut),这些数据结构的频繁分配和释放对性能影响较大。
Slab正是用于解决上述的两个问题, Slab 分配器源于 Solaris 2.4 的分配算法,工作于buddy system之上,用于管理特定大小对象的缓存,提高小块内存或特定对象内存分配效率。
Slab的两个用途如前面所述:1、缓存和管理内核中经常使用的数据结构对象,内核中使用slab提供的专用的接口,可以实现数据结构对象的快速分配,大大减少相关开销,提升效率。2、缓存和管理小块内存,也称通用缓存,用于kmalloc的底层实现和支撑。小块内存即小于一页大小的内存,以2^n字节对齐,比如512B、128B、64B、32B等。
2、slab管理结构图
3、缓冲区(kmem_cache)
Slab分配器为每种内核对象建立单独的缓冲区(kmem_cache),每个缓冲区包含多个 slab ,每个 slab包含一组连续的物理内存页,这些内存页被划分成固定数目的对象。根据对象大小的不同,缺省情况下一个 slab 最多可以由 1024 个物理内存页框构成。充分利用硬件特性,需要对每个对象进行一定的对齐处理(比如cache line对齐),所以slab 中分配给对象的内存可能大于用户要求的对象实际大小,可能会有一定的内存浪费。
内核使用 kmem_cache 数据结构管理缓冲区。每个缓冲区中,对每个NUMA node都有三个slab链表:
Full 链表,该链表中的所有slab中的对象都已经被完全分配,没有空闲对象。
Partial 链表,该链表中的slab中还有空闲对象,从slab中分配对象时,优先从此链表中分配。当 slab 的最后一个已分配对象被释放时,该 slab 将从 Partial 链表移入 free链表;当 slab 的最后一个空闲对象被分配时,该 slab 将从Partial 链表移入Full 链表里。
Free链表, 该链表中的slab中全是空闲对象,当partial链表中slab用尽时,从这里分配,并将相应的slab从free链表移入partial链表。
当缓冲区中空闲对象总数不足时,则按需从伙伴系统中分配更多的page,用于slab;当空闲对象比较富余,free链表中的的部分 slab 可能被定期回收。
由于 kmem_cache 自身也是一种内核对象,所以需要一个专门的缓冲区。所有缓冲区的 kmem_cache 控制结构被组织成以 cache_chain 为队列头的一个双向循环队列,同时 cache_cache 全局变量指向kmem_cache 对象缓冲区的 kmem_cache 对象。
kmem_cache结构定义如下:
点击(此处)折叠或打开
-
/*用于存放slab的缓存(kmem_cache)描述符*/
-
struct kmem_cache {
-
/* 1) Cache tunables. Protected by cache_chain_mutex */
-
/*当per-CPU缓存列表为空时,从slab中获取对象的数目;或者cache_grow时一次分配的对象数目*/
-
unsigned int batchcount;
-
/*per-CPU缓存列表(array?)中保存的最大对象数目*/
-
unsigned int limit;
-
/*是否存在共享CPU高速缓存*/
-
unsigned int shared;
-
/*slab中管理对象的长度(包括对齐填充字节)*/
-
unsigned int size;
-
/*为提升对象索引效率,使用Newton-Raphson方法使用的参数。*/
-
u32 reciprocal_buffer_size;
-
/* 2) touched by every alloc & free from the backend */
-
/*定义kmem_cache的性质,当前只使用了一个标志:CFLAG_OFF_SLAB,表示slab描述符位于slab数据区之外*/
-
unsigned int flags; /* constant flags */
-
/*每个kmem_cache(?)中可容纳的最大对象数目*/
-
unsigned int num; /* # of objs per slab */
-
-
/* 3) cache_grow/shrink */
-
/* order of pgs per slab (2^n) */
-
/*cache_grow或shrink时,一次性申请或释放的page order*/
-
unsigned int gfporder;
-
-
/* force GFP flags, e.g. GFP_DMA */
-
/*从buddy system中分配page时,使用的分配标志*/
-
gfp_t allocflags;
-
/*着色使用。最大的颜色数目*/
-
size_t colour; /* cache colouring range */
-
/*着色使用。着色偏移*/
-
unsigned int colour_off; /* colour offset */
-
/*当slab描述符(头部管理数据)存储在slab外部时,slabp_cache指向用于分配slab描述符的"通用缓存"(即用于分配kmalloc数据的slab缓存)*/
-
struct kmem_cache *slabp_cache;
-
/*单个slab头部管理数据的大小,包括slab描述符自身和kmem_bufctl_t数组的大小*/
-
unsigned int slab_size;
-
-
/* constructor func */
-
/*构造函数*/
-
void (*ctor)(void *obj);
-
-
/* 4) cache creation/removal */
-
/*kmem_cache名称,在/proc/slabinfo中可以看到*/
-
const char *name;
-
/*将所有的kmem_cache链入到全局链表中:cache_chain*/
-
struct list_head list;
-
/*引用计数*/
-
int refcount;
-
/*缓存中对象的长度,跟前面的size有何区别?*/
-
int object_size;
-
/*对齐使用*/
-
int align;
-
-
/* 5) statistics */
-
/*统计信息,打开slab调试开关时使用*/
-
#ifdef CONFIG_DEBUG_SLAB
-
unsigned long num_active;
-
unsigned long num_allocations;
-
unsigned long high_mark;
-
unsigned long grown;
-
unsigned long reaped;
-
unsigned long errors;
-
unsigned long max_freeable;
-
unsigned long node_allocs;
-
unsigned long node_frees;
-
unsigned long node_overflow;
-
atomic_t allochit;
-
atomic_t allocmiss;
-
atomic_t freehit;
-
atomic_t freemiss;
-
-
/*
-
* If debugging is enabled, then the allocator can add additional
-
* fields and/or padding to every object. size contains the total
-
* object size including these internal fields, the following two
-
* variables contain the offset to the user object and its size.
-
*/
-
int obj_offset;
-
#endif /* CONFIG_DEBUG_SLAB */
-
#ifdef CONFIG_MEMCG_KMEM
-
struct memcg_cache_params *memcg_params;
-
#endif
-
-
/* 6) per-cpu/per-node data, touched during every alloc/free */
-
/*
-
* We put array[] at the end of kmem_cache, because we want to size
-
* this array to nr_cpu_ids slots instead of NR_CPUS
-
* (see kmem_cache_init())
-
* We still use [NR_CPUS] and not [1] or [0] because cache_cache
-
* is statically defined, so we reserve the max number of cpus.
-
*
-
* We also need to guarantee that the list is able to accomodate a
-
* pointer for each node since "nodelists" uses the remainder of
-
* available pointers.
-
*/
-
/*用于管理slab链表(full、partial、free)的表头,每个node对应一个*/
-
struct kmem_cache_node **node;
-
/*per-CPU缓存列表(结构体末尾entry[]数组用于存放被释放的slab对象),当slab对象被释放时,先释放到该列表中*/
-
struct array_cache *array[NR_CPUS + MAX_NUMNODES];
-
/*
-
* Do not add fields after array[]
-
*/
- }
4、slab描述符
Slab使用 struct slab 数据结构(slab描述符)来描述其状态及进行相关管理。Slab描述符位于每个slab区域的起始,在为slab分配内存时,会先从buddy system中分配预定义数量的page作为slab区域,为充分利用硬件高速缓存,使用了着色机制,在slab区域的首部保留一定字节(通常根据cache line)长度的区域作为着色偏移,而slab描述符就位于此偏移之后(当slab管理数据位于slab内时)。
slab数据结构定义如下:
点击(此处)折叠或打开
-
struct slab {
-
union {
-
struct {
-
/*链入kmem_cache中的 slab_full 或者slab_patial 或者 slab_free链表中*/
-
struct list_head list;
-
/*着色偏移(包括了slab描述符和kmem_bufctl_t数组的长度),slab起始地址+colouroff=s_mem(object区起始地址)*/
-
unsigned long colouroff;
-
/*
-
* object区起始地址=slab起始地址+colouroff,即实际slab缓存对象所处的起始地址,所有对象在slab区中
-
* 连续分布,但有做cacheline对齐处理
-
*/
-
void *s_mem; /* including colour offset */
-
unsigned int inuse; /* num of objs active in slab */
-
/*
-
* kmem_bufctl_t数组中第一个空闲object在kmem_bufctl_t数组中的索引号,使用此索引,内核无需使用复杂
-
* 的结构体(比如SunOS中使用的链表),即可遍历所有空闲的slab对象。
-
*/
-
kmem_bufctl_t free;
-
/*用于在kmem_cache中索引指定node对应的slab链表*/
-
unsigned short nodeid;
-
};
-
struct slab_rcu __slab_cover_slab_rcu;
- }
Slab描述符和kmem_bufctl_t对象数组一起组成了slab的管理数据。Slab管理数据可以直接存放于slab区域内,也可以单独放置。单独放置即放到用kmalloc分配的不同的slab区域中。内核如何选择,取决于slab对象的长度和已用对象的数量,简单算法如下:如果slab对象不超过 1/8 个物理内存页框的大小,那么slab管理数据直接存放于slab区域首部(着色偏移之后);否则的话,存放在slab区域外部,位于由 kmalloc 分配的通用对象缓冲区中。
在kmem_bufctl_t对象数组之后,就是存放真正的slab对象(object)的区域,由slab->s_mem指向。slab->coloroff即为slab区域首部到slab->s_mem的偏移。slab->free中存放了该slab中第一个空闲对象的索引。
5、空闲对象管理
slab中的对象有 2 种状态:已用或空闲。Slab中使用了一个静态数组来管理空闲对象,该数组元素为kmem_bufctl_t对象(定义为无符号整数),每个数组元素对应一个slab对象,数组元素中存放下一个空闲对象的索引,该slab中的第一个空闲对象的索引保存在slab->free中,而该数组最后一个元素中总是几个结束标记:BUFCTL_END,如此,slab中利用此巧妙而简洁的方式有效的管理了slab中的空闲对象。相比SunOS中使用链表来管理slab空闲对象,Linux中的设计更巧妙和简洁、效率更高。
点击(此处)折叠或打开
7、slab着色
7、per-CPU slab对象缓存
为更好的利用CPU高速缓存,slab实现中,为每个CPU分配了一个slab对象的per-CPU缓存。当slab对象被释放时,会首先放入该缓存,当该缓存中的对象数目超限时,会将其移入相应的slab链表中;当分配slab时,也会首先冲该缓存中查找,如果找到直接返回,如果未找到,才从partial链表中开始查找。当该缓存为空时,会从slab 中批量分配对象到该缓存。使用该per-CPU缓存,能进一步提升slab的分配和释放效率。
该缓存由kmem_cache->array[]管理,kmem_cache->array定义为array_cache结构(数组缓存)。 array_cache结构定义如下:
点击(此处)折叠或打开
该结构中的entry数组用于存放slab对象指针,用于指向最近释放的slab对象。
点击(此处)折叠或打开
点击(此处)折叠或打开
点击(此处)折叠或打开
点击(此处)折叠或打开
点击(此处)折叠或打开
点击(此处)折叠或打开
kmem_bufctl_t定义如下:
8、代码分析
主要分析slab对象的分配流程:kmem_cache_alloc()
kmem_cache_alloc()->slab_alloc()
kmem_cache_alloc()->slab_alloc()->__do_cache_alloc()
kmem_cache_alloc()->slab_alloc()->__do_cache_alloc()->____cache_allc()
kmem_cache_alloc()->slab_alloc()->__do_cache_alloc()->____cache_allc()->cache_alloc_refill()
kmem_cache_alloc()->slab_alloc()->__do_cache_alloc()->____cache_allc()->cache_alloc_refill()->cache_grow()