linux内核源码分析之slab

Posted 为了维护世界和平_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux内核源码分析之slab相关的知识,希望对你有一定的参考价值。

目录

结构体分析

结构体之间关系

静态初始化

创建缓存


结构体分析

kmem_cache 每个缓存由kmem_cache结构的一个实例表示。

struct kmem_cache 
	//是每个CPU一个array_cache类型的变量
	struct array_cache __percpu *cpu_cache;

/* 1) Cache tunables. Protected by slab_mutex */
	unsigned int batchcount;//指定了在per-CPU列表为空的情况下,从缓存的slab中获取对象的数目。它还表示在缓存增长时分配的对象数目
	unsigned int limit;//指定per-CPU列表中保存的对象的最大数目
	unsigned int shared;

	unsigned int size;//cache大小
	struct reciprocal_value reciprocal_buffer_size;
/* 2) touched by every alloc & free from the backend */

	slab_flags_t flags;		/* slab 标志constant flags */
	unsigned int num;		/* 对象个数 # of objs per slab */

/* 3) cache_grow/shrink */
	/* order of pgs per slab (2^n) */
	unsigned int gfporder;//分配内存页面的order

	/* 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;//slab名字
	struct list_head list;//所有slab链接
	int refcount;//引用计数
	int object_size;//对象大小
	int align;//对齐大小

	int obj_offset;
	//指向管理kmemcache的上层结构
	struct kmem_cache_node *node[MAX_NUMNODES];
;

array_cache 缓存

struct array_cache 
	unsigned int avail;//当前可用对象的数目
	unsigned int limit;
	unsigned int batchcount;
	unsigned int touched;//从缓存移除一个对象时,将touched设置为1,而缓存收缩时,则将touched设置0
	void *entry[];	//对象地址
;

avail保存了当前可用对象的数目。在从缓存移除一个对象时,将touched设置1,而缓存收缩时,将touched设置为0

kmem_cache_node节点

struct kmem_cache_node 
	spinlock_t list_lock;//自旋锁

#ifdef CONFIG_SLAB
	struct list_head slabs_partial;	/*对象被使用一部分的slab描述符的链表 */
	struct list_head slabs_full;//对象被占用的slab描述符链表
	struct list_head slabs_free;//只包含空闲对象的slab描述符链表
	unsigned long total_slabs;	/* 一共多少kmem_cache结构*/
	unsigned long free_slabs;	/* 空闲kmem_cache结构 */
	unsigned long free_objects;/*空闲的对象*/
	unsigned int free_limit;
	unsigned int colour_next;	/* Per-node cache coloring */
	struct array_cache *shared;	/*CPU共享本地缓存 shared per node */
	struct alien_cache **alien;	/* on other nodes */
	unsigned long next_reap;	/* 由slab的页回收算法使用 updated without locking */
	int free_touched;		/*  由slab的页回收算法使用 updated without locking */
#endif
;

三种缓存 空闲,满,部分空闲。

结构体之间关系

静态初始化

        伙伴系统已经完全启用,初始化slab数据结构,内核需要若干小于一整页的内存块,适合用kmalloc分配,但在slab启用后才能使用kmalloc。

        kmem_cache_init函数用于初始化slab,在伙伴系统启用之后调用

/* internal cache of cache description objs */
static struct kmem_cache kmem_cache_boot = 
	.batchcount = 1,
	.limit = BOOT_CPUCACHE_ENTRIES,
	.shared = 1,
	.size = sizeof(struct kmem_cache),
	.name = "kmem_cache",
;


void __init kmem_cache_init(void)

	int i;
	//指向静态定义的kmem_cache_boot
	kmem_cache = &kmem_cache_boot;

	if (!IS_ENABLED(CONFIG_NUMA) || num_possible_nodes() == 1)
		use_alien_caches = 0;

	for (i = 0; i < NUM_INIT_LISTS; i++)
		kmem_cache_node_init(&init_kmem_cache_node[i]);

	if (!slab_max_order_set && totalram_pages() > (32 << 20) >> PAGE_SHIFT)
		slab_max_order = SLAB_MAX_ORDER_HI;


   //建立保存kmem_cache结构的kmem_cache
	/* 1) create the kmem_cache struct kmem_cache size depends on nr_node_ids & nr_cpu_ids*/
	create_boot_cache(kmem_cache, "kmem_cache",
		offsetof(struct kmem_cache, node) +
				  nr_node_ids * sizeof(struct kmem_cache_node *),
				  SLAB_HWCACHE_ALIGN, 0, 0);

    //加入全局slab_caches
	list_add(&kmem_cache->list, &slab_caches);
	memcg_link_cache(kmem_cache, NULL);
	slab_state = PARTIAL;

	kmalloc_caches[KMALLOC_NORMAL][INDEX_NODE] = create_kmalloc_cache(
				kmalloc_info[INDEX_NODE].name[KMALLOC_NORMAL],
				kmalloc_info[INDEX_NODE].size,
				ARCH_KMALLOC_FLAGS, 0,
				kmalloc_info[INDEX_NODE].size);
	slab_state = PARTIAL_NODE;
	setup_kmalloc_cache_index_table();

	slab_early_init = 0;

	
		int nid;
        //加入全局slab_cache链表中
		for_each_online_node(nid) 
			init_list(kmem_cache, &init_kmem_cache_node[CACHE_CACHE + nid], nid);

			init_list(kmalloc_caches[KMALLOC_NORMAL][INDEX_NODE],
					  &init_kmem_cache_node[SIZE_NODE + nid], nid);
		
	
    //建立kmalloc函数使用的kmem_cache
	create_kmalloc_caches(ARCH_KMALLOC_FLAGS);

        kmem_cache_init创建系统中第一个slab缓存,以便为kmem_cache的实例提供内存,静态数据结构。初始化时建立了第一个 kmem_cache 结构之后,init_list 函数负责一个个分配内存空间。

/*
 * swap the static kmem_cache_node with kmalloced memory
 */
static void __init init_list(struct kmem_cache *cachep, struct kmem_cache_node *list,
				int nodeid)

	struct kmem_cache_node *ptr;
	//分配新的kmem_cache_node结构的空间
	ptr = kmalloc_node(sizeof(struct kmem_cache_node), GFP_NOWAIT, nodeid);
	BUG_ON(!ptr);
	//复制初始时的静态kmem_cache_node结构
	memcpy(ptr, list, sizeof(struct kmem_cache_node));
	spin_lock_init(&ptr->list_lock);

	MAKE_ALL_LISTS(cachep, ptr, nodeid);
	//设置kmem_cache_node的地址
	cachep->node[nodeid] = ptr;

kmalloc_caches是kmalloc_cache的集合

初始化过程,遍历枚举


#define KMALLOC_SHIFT_HIGH	((MAX_ORDER + PAGE_SHIFT - 1) <= 25 ? \\
				(MAX_ORDER + PAGE_SHIFT - 1) : 25)


enum kmalloc_cache_type 
	KMALLOC_NORMAL = 0,
	KMALLOC_RECLAIM,
#ifdef CONFIG_ZONE_DMA
	KMALLOC_DMA,
#endif
	NR_KMALLOC_TYPES
;

extern struct kmem_cache *
kmalloc_caches[NR_KMALLOC_TYPES][KMALLOC_SHIFT_HIGH + 1];


void __init create_kmalloc_caches(slab_flags_t flags)

	int i;
	enum kmalloc_cache_type type;

	for (type = KMALLOC_NORMAL; type <= KMALLOC_RECLAIM; type++) 
		for (i = KMALLOC_SHIFT_LOW; i <= KMALLOC_SHIFT_HIGH; i++) 
			if (!kmalloc_caches[type][i])
				new_kmalloc_cache(i, type, flags);

			if (KMALLOC_MIN_SIZE <= 32 && i == 6 &&
					!kmalloc_caches[type][1])
				new_kmalloc_cache(1, type, flags);
			if (KMALLOC_MIN_SIZE <= 64 && i == 7 &&
					!kmalloc_caches[type][2])
				new_kmalloc_cache(2, type, flags);
		
	

	/* Kmalloc array is now usable */
	slab_state = UP;
#ifdef CONFIG_ZONE_DMA
	for (i = 0; i <= KMALLOC_SHIFT_HIGH; i++) 
		struct kmem_cache *s = kmalloc_caches[KMALLOC_NORMAL][i];

		if (s) 
			kmalloc_caches[KMALLOC_DMA][i] = create_kmalloc_cache(
				kmalloc_info[i].name[KMALLOC_DMA],
				kmalloc_info[i].size,
				SLAB_CACHE_DMA | flags, 0, 0);
		
	
#endif

创建缓存

static size_t calculate_slab_order(struct kmem_cache *cachep,
				size_t size, slab_flags_t flags)

	size_t left_over = 0;
	int gfporder;

	//每次order+1. 从第一页帧开始,每次倍增slab长度
	//1) 8*left_over 小于slab长度,即浪费空间小于1/8
	//2)gfp_order大于或等于slab_max_order
	for (gfporder = 0; gfporder <= KMALLOC_MAX_ORDER; gfporder++) 
		unsigned int num;
		size_t remainder;
		//找到一个slab布局,size对象长度,gfp_order页阶,num slab上对象数目
		num = cache_estimate(gfporder, size, flags, &remainder);
		if (!num)
			continue;

		if (num > SLAB_OBJ_MAX_NUM)
			break;

		//slab头在管理数据存储在slab之外
		if (flags & CFLGS_OFF_SLAB) 
			struct kmem_cache *freelist_cache;
			size_t freelist_size;

			freelist_size = num * sizeof(freelist_idx_t);
			freelist_cache = kmalloc_slab(freelist_size, 0u);
			if (!freelist_cache)
				continue;


			if (OFF_SLAB(freelist_cache))
				continue;

			if (freelist_cache->size > cachep->size / 2)
				continue;
		
		cachep->num = num;
		cachep->gfporder = gfporder;
		left_over = remainder;

		if (flags & SLAB_RECLAIM_ACCOUNT)
			break;

		if (gfporder >= slab_max_order)
			break;

		if (left_over * 8 <= (PAGE_SIZE << gfporder))
			break;
	
	return left_over;

  如果下述条件之一成立,即结束循环

  1. 每次order+1. 从第一页帧开始,每次倍增slab长度
  2. 8*left_over 小于slab长度,即浪费空间小于1/8
  3. gfp_order大于或等于slab_max_order        

        内核试图通过calculate_slab_order实现的迭代过程,找到理想的slab长度。基于给定对象长度, cache_estimate针对特定的页数,来计算对象数目、浪费的空间、着色所需的空间。该函数会循环调用,直至内核对结果满意为止。

//产生per-CPU缓存
static int __ref setup_cpu_cache(struct kmem_cache *cachep, gfp_t gfp)

	if (slab_state >= FULL)
		return enable_cpucache(cachep, gfp);

	cachep->cpu_cache = alloc_kmem_cache_cpus(cachep, 1, 1);
	if (!cachep->cpu_cache)
		return 1;

...
	return 0;
static int enable_cpucache(struct kmem_cache *cachep, gfp_t gfp)

...
	if (cachep->size > 131072)
		limit = 1;
	else if (cachep->size > PAGE_SIZE)
		limit = 8;
	else if (cachep->size > 1024)
		limit = 24;
	else if (cachep->size > 256)
		limit = 54;
	else
		limit = 120;

	shared = 0;
	if (cachep->size <= PAGE_SIZE && num_possible_cpus() > 1)
		shared = 8;

	batchcount = (limit + 1) / 2;//缓存中对象数的一半
skip_setup://初始化数据结构
	err = do_tune_cpucache(cachep, limit, batchcount, shared, gfp);
end:
	if (err)
		pr_err("enable_cpucache failed for %s, error %d\\n",
		       cachep->name, -err);
	return err;

        为各个处理器分配所需的内存:一个array_cache的实例和一个指针数组,数组项数目
在上述的计算中给出;并初始化数据结构,这些任务委托给do_tune_cpucache。
 

参考

《深入Linux内核架构》

https://course.0voice.com/v1/course/intro?courseId=2&agentId=0


以上是关于linux内核源码分析之slab的主要内容,如果未能解决你的问题,请参考以下文章

linux内核源码分析之slab

linux内核源码分析之slab

linux内核分析———SLAB原理及实现

深入浅出分析Linux内核slab性能优化的核心思想

多核心Linux内核路径优化的不二法门之-slab与伙伴系统

Memcached源码分析之slabs.c