1. 简介
slab分配器负责分配小于页的内存块,例如以Byte为单位的内存块。我们知道伙伴系统的分配单位是页,slab分配器则是在伙伴系统分配的页面上实现自己的算法,专门用于满足小块内存的分配需求。
在内核中,当需要频繁地分配和释放数据结构时,可以将分配好内存的数据结构用空闲链表缓存起来,而不是彻底释放它占用的内存。当再次需要使用数据结构时,直接从空闲链表中抓取,避免了再次分配内存,提高执行效率。
slab分配器把不同的对象分为所谓的高速缓存组,其中每个高速缓存组都存放不同类型的对象。例如,一个高速缓存组用于存放进程描述符(task_struct),另一个高速缓存组用于存放索引节点(struct inode)等。
高速缓存组又被划分为slab。这个slab由一个或多个物理上连续的页组成。每个高速缓存组由多个slab组成。每个slab都包含一些对象成员,这些对象成员就是被缓存的数据结构。
2. slab描述符(高速缓存)
struct kmem_cache 是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 */
slab_flags_t 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;
/* 5) statistics */
#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;
#ifdef CONFIG_DEBUG_SLAB_LEAK
atomic_t store_user_clean;
#endif
/*
* 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, while 'obj_offset'
* and 'object_size' contain the offset to the user object and its
* size.
*/
int obj_offset;
#endif /* CONFIG_DEBUG_SLAB */
#ifdef CONFIG_MEMCG
struct memcg_cache_params memcg_params;
#endif
#ifdef CONFIG_KASAN
struct kasan_cache kasan_info;
#endif
#ifdef CONFIG_SLAB_FREELIST_RANDOM
unsigned int *random_seq;
#endif
unsigned int useroffset; /* Usercopy region offset */
unsigned int usersize; /* Usercopy region size */
struct kmem_cache_node *node[MAX_NUMNODES];
};
- cpu_cache:每cpu的struct array_cache数据结构,表示本地对象缓冲池(本地高速缓存)。
- batchcount:表示当前cpu的本地对象缓冲池array_cache为空时,从共享缓冲池或slabs_partial、slabs_free列表中获取对象的数目。
- limit:当本地对象缓冲池的空闲对象数目大于limit时就会主动释放batchcount个对象,便于内核回收和销毁slab。
- shard:cpu共享高速缓存标志,用于多核系统,实际地址保存在kmem_cache_node中。
- size:对象的长度,这个长度需要加上align对齐字节。
- flags:对象分配掩码。
- num:一个slab中最大可以有多少对象。
- gfporder:一个slab中占用gfporder个页面。
- allocflags:从伙伴系统分配,补足slab时的分配掩码。
- colour:一个slab中有几个不同的cache line。
- colour_off:一个cache color 的长度,和L1 cache line大小相同。
- freelist_cache:空闲对象链表放在slab外部时使用,管理用于slab对象管理结构中freelist成员的缓存,也就是又一个新缓存。
- freelist_size:空闲对象链表的大小,每个对象要占用1Byte来存放空闲对象链表。
- name:slab描述符的名称。
- list:slab描述符的双向链表指针。
- object_size:对象的实际大小。
- align:对齐的长度。
- node:slab节点,在NUMA系统中每个节点有一个struct kmem_cache_node数据结构。
3. 创建slab描述符(高速缓存)
struct kmem_cache *kmem_cache_create(const char *name, unsigned int size, unsigned int align,slab_flags_t flags, void (*ctor)(void *))
- name:高速缓存的名称。
- size:高速缓存中每个元素的大小。
- align:slab内第一个对象的偏移,它用来确保在页内进行特定的对齐。通常情况下,0就可以满足要求,也就是标准对齐。
- flags:用来控制高速缓存的行为。可以为0,表示没有特殊要求。
SLAB_HWCACHE_ALIGN //这个表示命令slab分配器把一个slab内的所有对象按高速缓存行对齐,防止“错误的共享”(两个或多个对象尽管位于不同的内存地址,但映射到相同的高速缓存行)。这个标志可以提高性能,但以增加内存开销为代价,因为对齐越严格,浪费的内存就越多。
SLAB_POISON //这个标志使slab分配器用已知的值(a5a5a5a5)填充slab,这就是所谓的“中毒”现象,有利于对未初始化的内存访问。
SLAB_RED_ZONE //这个标志导致slab分配器在已分配的内存周围插入“红色警界区”以探测缓冲越界。
SLAB_PANIC //这个标志当分配失败时提醒slab分配器。这在要求分配只能成功时非常有用。比如,在系统启动时分配一个VMA结构的高速缓存。
SLAB_CACHE_DMA //这个标志命令slab分配器使用可以执行DMA的内存给每个slab分配空间。只有在分配的对象用于DMA,并且必须驻留在ZONE_DMA区时才需要这个标志。
- ctor:对象的构造函数,只有在新页追加到高速缓存时,构造函数才会被调用。实际上,Linux内核的高速缓存不再使用构造函数,可以将ctor设置为NULL。
如果该函数调用成功就会返回一个指向高速缓存的指针,否则返回NULL。这个函数不能在中断上下文中调用,因为它可能睡眠。
了解了函数原型后,简要梳理一下该函数的内部实现逻辑:
4. 释放slab描述符(高速缓存)
void kmem_cache_destroy(struct kmem_cache *s)
该函数撤销高速缓存,一般在模块的注销代码中调用。同样,它也不能在中断上下文中调用。在调用该函数之前必须确保两个条件:
- 高速缓存中的所有slab都必须为空。只要还有一个对象被分配出去并正在使用的话,不能撤销该高速缓存。
- 在调用该函数的过程中,不能再去访问这个高速缓存。
该函数调用成功返回0,失败返回非0值。
5. 分配缓存对象
创建好高速缓存后,可以用以下函数分配slab对象:
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)
该函数从指定的高速缓存中返回一个指向对象的指针。如果指定的高速缓存中的所有slab中都没有空闲的对象,那么slab分配器必须通过kmem_getpages()函数从伙伴系统中获得新的页,flags的值传递给_get_free_pages()。flags是 GFP_KERNEL 或 GFP_ATOMIC。
以下简要梳理该函数分配对象的过程:
6. 归还缓存对象
想要归还缓存对象时,使用以下函数:
void kmem_cache_free(struct kmem_cache *cachep, void *objp)
当该函数把对象放到本地对象缓存池ac中后,归还过程才结束。如果此时本地对象缓存池的空闲对象数目大于阈值,则会尝试将本地缓存对象池中的batchcount 个空闲对象迁移到共享对象缓存池。假设此时共享对象缓存池中的空闲对象也大于阈值,那么就会释放 batchount 个空闲对象。如果slab中没有了活跃对象(page->active),并且slab节点中所有空闲对象数目大于阈值,则会销毁这个slab。
7. 总结
将内核中slab分配器相关的数据结构抽象出来,可以用下图来表示: