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分配器相关的数据结构抽象出来,可以用下图来表示: