Linux内核-内存-slab分配器

11 篇文章 1 订阅
3 篇文章 0 订阅
         前面分析到Linux使用分区页框分配器满足对连续页框的请求,这适合对大块内存的请求,当处理对小内存的请求时,比如几十或几百个字节,很显然,如果此时为其分配一整个页框将会是一种浪费,所以需要引入一种新的数据结构来描述在同一页框中如何分配小内存区,Linux使用slab分配器来解决这个问题。
        slab分配器把内存区看做对象,这些对象由一组数据结构和几个叫做构造或析构的函数组成。根据请求的频率不同,可以创建一组具有适当大小的专用对象来高效处理频繁请求的内存区,例如进程描述符,打开的文件对象等;对于很少遇到的内存区大小,可以基于一系列几何分布大小(如2的幂次方)的对象的分配模式来处理。

         slab分配器把对象分组放进高速缓存,每个高速缓存都是同种类型对象的一种“储备”,例如,当一个文件被打开时,存放相应“打开文件”对象所需的内存区是从一个叫做filp的slab分配器的高速缓存中得到的。高速缓存包含有多个slab,每个slab由一个或多个连续的页框组成,这些页框中包含已分配和空闲的对象,它们的关系如下图所示:


如上图,各个高速缓存是通过双向链表连接,高速缓存中的每种slab也是通过双向链表连接,每个高速缓存都有一个空闲slab对象的本地高速缓存,请求内存区时就是通过这个本地高速缓存来分配对象,本地高速缓存从slab中的对象获取,一旦分配给本地高速缓存,对于slab来说,这个对象就是非空闲的(有可能还未被使用),每个slab中的对象关系如下图:


紧接着slab描述符之后是一个对象描述符的数组,数组中的第一个对象描述符描述slab中的第一个对象,依次类推。对象描述符只不过是一个无符号整数,只有在对象空闲时才有意义,它包含的是下一个空闲对象在slab中的下标,因此实现了slab内部空闲对象的一个简单链表,空闲对象链表的最后一个元素的对象描述符用BUFCTL_END(0xffff)标记。

1. 数据结构

1.1 高速缓存描述符kmem_cache_t:

struct kmem_cache_s {
/* 1) per-cpu data, touched during every alloc/free */
	/**
	 * 每CPU指针数组,指向包含空闲对象的本地高速缓存。
	 */
	struct array_cache	*array[NR_CPUS];
	/**
	 * 要转移进本地高速缓存或从本地高速缓存中转移出的大批对象的数量。
	 */
	unsigned int		batchcount;
	/**
	 * 本地高速缓存中空闲对象的最大数目。这个参数可调。
	 */
	unsigned int		limit;
/* 2) touched by every alloc & free from the backend */
	/**
	 * 包含三个链表,为什么要单独放到一个描述符中呢?
	 */
	struct kmem_list3	lists;
	/* NUMA: kmem_3list_t	*nodelists[MAX_NUMNODES] */
	/**
	 * 高速缓存中包含的对象的大小。
	 */
	unsigned int		objsize;
	/**
	 * 描述高速缓存永久属性的一组标志。
	 */
	unsigned int	 	flags;	/* constant flags */
	/**
	 * 在一个单独slab中的对象的个数。高速缓存中的所有slab具有相同的大小。
	 */
	unsigned int		num;	/* # of objs per slab */
	/**
	 * 整个slab高速缓存中空闲对象的上限。
	 */
	unsigned int		free_limit; /* upper limit of objects in the lists */
	/**
	 * 高速缓存自旋锁。
	 */
	spinlock_t		spinlock;

/* 3) cache_grow/shrink */
	/* order of pgs per slab (2^n) */
	/**
	 * 一个单独slab中包含的连续页框数目的对数。
	 */
	unsigned int		gfporder;

	/* force GFP flags, e.g. GFP_DMA */
	/**
	 * 分配页框时传递给伙伴系统函数的一组标志。
	 */
	unsigned int		gfpflags;

	/**
	 * slab使用的颜色个数。用于slab着色。
	 */
	size_t			colour;		/* cache colouring range */
	/**
	 * slab中的基本对齐偏移。
	 */
	unsigned int		colour_off;	/* colour offset */
	/**
	 * 下一个被分配的slab使用的颜色。就是对齐因子。
	 */
	unsigned int		colour_next;	/* cache colouring */
	/**
	 * 指向包含slab描述符的普通slab高速缓存。如果使用了内部slab描述符,则这个字段为NULL。
	 */
	kmem_cache_t		*slabp_cache;
	/**
	 * 单个slab的大小。
	 */
	unsigned int		slab_size;
	/**
	 * 高速缓存动态属性标志。
	 */
	unsigned int		dflags;		/* dynamic flags */

	/* constructor func */
	/**
	 * 高速缓存相关的构造方法的指针。
	 */
	void (*ctor)(void *, kmem_cache_t *, unsigned long);

	/* de-constructor func */
	/**
	 * 高速缓存相关的析构方法的指针。
	 */
	void (*dtor)(void *, kmem_cache_t *, unsigned long);

/* 4) cache creation/removal */
	/**
	 * 高速缓存名称。
	 */
	const char		*name;
	/**
	 * 高速缓存链表。
	 */
	struct list_head	next;

/* 5) statistics */
#if STATS
	/**
	 * 统计信息
	 */
	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;
	atomic_t		allochit;
	atomic_t		allocmiss;
	atomic_t		freehit;
	atomic_t		freemiss;
#endif
#if DEBUG
	/**
	 * 调试信息
	 */
	int			dbghead;
	int			reallen;
#endif
};

其中kmem_list3结构如下:

struct kmem_list3 {
	/**
	 * 空闲和非空闲对象的slab描述符双向循环链表。
	 */
	struct list_head	slabs_partial;	/* partial list first, better asm code */
	/**
	 * 不包含空闲对象的slab描述符双向循环链表。
	 */
	struct list_head	slabs_full;
	/**
	 * 只包含空闲对象的slab描述符双向循环链表。
	 */
	struct list_head	slabs_free;
	unsigned long	free_objects;
	/**
	 * slab分配器的页回收算法使用。
	 */
	int		free_touched;
	/**
	 * slab分配器的页回收算法使用。
	 */
	unsigned long	next_reap;
	/**
	 * 所有CPU共享的一个本地高速缓存的指针。它使得将空闲对象从一个本地高速缓存移动到另外一个高速缓存的任务更容易。
	 * 它的初始大小是batchcount字段的8倍。
	 */
	struct array_cache	*shared;
};

1.2 slab描述符:

struct slab {
	/**
	 * slab高速缓存描述符的三个双向循环链表中的一个。
	 */
	struct list_head	list;
	/**
	 * slab中第一个对象的偏移。
	 * 同一个高速缓存的不同slab有不同的coloroff值。这样可以避免硬件缓存行的不利影响。
	 */
	unsigned long		colouroff;
	/**
	 * slab中第一个对象的地址。
	 */
	void			*s_mem;		/* including colour offset */
	/**
	 * 当前正在使用的slab中的对象个数。
	 */
	unsigned int		inuse;		/* num of objs active in slab */
	/**
	 * slab中下一个空闲对象的下标。如果没有剩下空闲对象则为BUFCT_END
	 */
	kmem_bufctl_t		free;
};

1.3 对象描述符:

/**
 * 高速缓存对象描述符。将slab中的空闲对象链接起来。
 */
typedef unsigned short kmem_bufctl_t;

2. 源码分析

2.1 创建新的slab

/*
 * slab分配器调用此接口从页框分配器中获得一组连续的空闲页框。
 * cachep-需要额外页框的高速缓存的高速缓存描述符。请求页框的个数由cachep->gfporder中的order决定。
 * flags-说明如何请求页框。这组标志与存放在高速缓存描述符的gfpflags字段中专用高速缓存分配标志相结合。
 */
static void *kmem_getpages(kmem_cache_t *cachep, int flags, int nodeid)
{
	struct page *page;
	void *addr;
	int i;

	flags |= cachep->gfpflags;
	/*
	 * 从分区页框分配器得到页框
	 */
	if (likely(nodeid == -1)) {
		page = alloc_pages(flags, cachep->gfporder);
	} else {
		page = alloc_pages_node(nodeid, flags, cachep->gfporder);
	}
	if (!page)
		return NULL;
	addr = page_address(page);

	i = (1 << cachep->gfporder);
	if (cachep->flags & SLAB_RECLAIM_ACCOUNT)
		atomic_add(i, &slab_reclaim_pages);
	add_page_state(nr_slab, i);
	/*
	 * 设置页框字段表明其为slab所用
	while (i--) {
		SetPageSlab(page);
		page++;
	}
	return addr;
}

2.2 释放slab

/**
 * 释放分配给slab的页框。
 * addr-从该地址开始释放页框。
 * cachep-slab是由cachep标识的高速缓存中的slab.
 */
static void kmem_freepages(kmem_cache_t *cachep, void *addr)
{
	unsigned long i = (1<<cachep->gfporder);
	struct page *page = virt_to_page(addr);
	const unsigned long nr_freed = i;

	while (i--) {
		if (!TestClearPageSlab(page))
			BUG();
		page++;
	}
	sub_page_state(nr_slab, nr_freed);
	/**
	 * 如果当前进程正在执行回存回收。就适当增加reclaimed_slab字段。
	 * 于是刚被释放的页就能通过回收算法被记录下来。
	 */
	if (current->reclaim_state)
		current->reclaim_state->reclaimed_slab += nr_freed;
	free_pages((unsigned long)addr, cachep->gfporder);
	/**
	 * 如果SLAB_RECLAIM_ACCOUNT被置位,slab_reclaim_pages则被适当的减少。
	 */
	if (cachep->flags & SLAB_RECLAIM_ACCOUNT) 
		atomic_sub(1<<cachep->gfporder, &slab_reclaim_pages);
}


2.3 分配slab对象

/**
 * 获得新的slab对象
 * cachep-指向高速缓存描述符。新空闲对象必须从该高速缓存描述符获得。
 * flag-表示传递给分区页框分配器函数的标志。
 */
void * kmem_cache_alloc (kmem_cache_t *cachep, int flags)
{
	return __cache_alloc(cachep, flags);
}

static inline void * __cache_alloc (kmem_cache_t *cachep, int flags)
{
	unsigned long save_flags;
	void* objp;
	struct array_cache *ac;

	cache_alloc_debugcheck_before(cachep, flags);

	local_irq_save(save_flags);
	/**
	 * 首先试图从本地高速缓存获得一个空闲对象。
	 */
	ac = ac_data(cachep);
	/**
	 * 如果本地高速缓存有空闲对象,那么avail字段就包含最后被释放的对象的项在本地高速缓存中的下标。
	 */
	if (likely(ac->avail)) {
		STATS_INC_ALLOCHIT(cachep);
		ac->touched = 1;
		/**
		 * 因为本地高速缓存数组正好存放在ac描述符的后面。
		 * 所以(void**)(ac+1)[--ac->avail]获得空闲对象的地址,并递减ac->avail的值。
		 */
		objp = ac_entry(ac)[--ac->avail];
	} else {/* 本地高速缓存中没有空闲对象。 */
		STATS_INC_ALLOCMISS(cachep);
		/**
		 * cache_alloc_refill重新填充本地高速缓存并获得一个空闲对象。
		 */
		objp = cache_alloc_refill(cachep, flags);
	}
	local_irq_restore(save_flags);
	objp = cache_alloc_debugcheck_after(cachep, flags, objp, __builtin_return_address(0));
	return objp;
}

cache_alloc_refill函数如下:

/**
 * 重新填充本地高速缓存并获得一个空闲对象
 */
static void* cache_alloc_refill(kmem_cache_t* cachep, int flags)
{
	int batchcount;
	struct kmem_list3 *l3;
	struct array_cache *ac;

	check_irq_off();
	/**
	 * ac = cachep->array[smp_processor_id()];
	 * 将本地高速缓存描述符的地址存放在ac局部变量中。
	 */
	ac = ac_data(cachep);
retry:
	batchcount = ac->batchcount;
	if (!ac->touched && batchcount > BATCHREFILL_LIMIT) {
		/* if there was little recent activity on this
		 * cache, then perform only a partial refill.
		 * Otherwise we could generate refill bouncing.
		 */
		batchcount = BATCHREFILL_LIMIT;
	}
	l3 = list3_data(cachep);

	BUG_ON(ac->avail > 0);
	/**
	 * 获取spinlock
	 */
	spin_lock(&cachep->spinlock);
	/**
	 * 如果slab高速缓存包含共享本地高速缓存
	 */
	if (l3->shared) {
		struct array_cache *shared_array = l3->shared;
		/**
		 * 并且该共享本地高速缓存包含一些空闲对象
		 */
		if (shared_array->avail) {
			if (batchcount > shared_array->avail)
				batchcount = shared_array->avail;
			/**
			 * 从共享本地高速缓存中上移batchcount个指针来重新填充CPU的本地高速缓存
			 */
			shared_array->avail -= batchcount;
			ac->avail = batchcount;
			memcpy(ac_entry(ac), &ac_entry(shared_array)[shared_array->avail],
					sizeof(void*)*batchcount);
			shared_array->touched = 1;
			goto alloc_done;
		}
	}
	/**
	 * 函数试图填充本地高速缓存,填充值为高速缓存的slab中包含的多达ac->batchcount个空闲对象的指针
	 */
	while (batchcount > 0) {
		struct list_head *entry;
		struct slab *slabp;
		/* Get slab alloc is to come from. */
		entry = l3->slabs_partial.next;
		/**
		 * 查看高速缓存描述符的slabs_partial和slabs_free链表
		 */
		if (entry == &l3->slabs_partial) {
			l3->free_touched = 1;
			entry = l3->slabs_free.next;
			if (entry == &l3->slabs_free)
				goto must_grow;
		}

		/**
		 * 获得slab描述符的地址slabp,该slab描述符的相应slab或者部分被填充或者为空。
		 */
		slabp = list_entry(entry, struct slab, list);
		check_slabp(cachep, slabp);
		check_spinlock_acquired(cachep);
		/**
		 * 对于slab中的每个空闲对象
		 */
		while (slabp->inuse < cachep->num && batchcount--) {
			kmem_bufctl_t next;
			STATS_INC_ALLOCED(cachep);
			STATS_INC_ACTIVE(cachep);
			STATS_SET_HIGH(cachep);

			/* get obj pointer */
			/**
			 * 将对象地址插入本地高速缓存。
			 */
			ac_entry(ac)[ac->avail++] = slabp->s_mem + slabp->free*cachep->objsize;

			/**
			 * 函数增加slab描述符的inuse字段
			 */
			slabp->inuse++;
			next = slab_bufctl(slabp)[slabp->free];
#if DEBUG
			slab_bufctl(slabp)[slabp->free] = BUFCTL_FREE;
#endif
				/**
				 * 更新free字段,使得它指向slab中下一个空闲对象的下标。
				 */
		       	slabp->free = next;
		}
		check_slabp(cachep, slabp);

		/* move slabp to correct slabp list: */
		/**
		 * 如果有必要,将清空的slab插入到适当的链表上,可以是slabs_full链表,也可以是slabs_partial链表
		 */
		list_del(&slabp->list);
		if (slabp->free == BUFCTL_END)
			list_add(&slabp->list, &l3->slabs_full);
		else
			list_add(&slabp->list, &l3->slabs_partial);
	}

must_grow:
	/**
	 * 被加到本地高速缓存的指针个数被存放在ac->avail字段。
	 * 函数递减同样数量的free_objects来说明这些对象不再空闲
	 */
	l3->free_objects -= ac->avail;
alloc_done:
	/**
	 * 释放spinlock
	 */
	spin_unlock(&cachep->spinlock);

	/**
	 * 没有发生任何高速缓存再填充的情况
	 */
	if (unlikely(!ac->avail)) {
		int x;
		/**
		 * 调用cache_grow获得一个新的slab,从而获得了新的空闲对象
		 */
		x = cache_grow(cachep, flags, -1);
		
		// cache_grow can reenable interrupts, then ac could change.
		ac = ac_data(cachep);
		/**
		 * cache_grow失败了,返回NULL
		 */
		if (!x && ac->avail == 0)	// no objects in sight? abort
			return NULL;

		/**
		 * cache_grow成功了,返回再试。
		 */
		if (!ac->avail)		// objects refilled by interrupt?
			goto retry;
	}
	/**
	 * 如果ac->avail>0(一些高速缓存再填充的情况发生了)
	 */
	ac->touched = 1;
	/**
	 * 返回最后插入到本地高速缓存的空闲对象指针
	 */
	return ac_entry(ac)[--ac->avail];
}

3.4 释放slab对象

**
 * 释放一个曾经由slab分配器分配给某个内核函数的对象
 * cachep-高速缓存描述符的地址。
 * objp-要释放的对象的地址。
 */
void kmem_cache_free (kmem_cache_t *cachep, void *objp)
{
	unsigned long flags;

	local_irq_save(flags);
	__cache_free(cachep, objp);
	local_irq_restore(flags);
}

static inline void __cache_free (kmem_cache_t *cachep, void* objp)
{
	struct array_cache *ac = ac_data(cachep);

	check_irq_off();
	objp = cache_free_debugcheck(cachep, objp, __builtin_return_address(0));

	/**
	 * 首先检查本地高速缓存是否有空间给指向一个空闲对象的额外指针。
	 */
	if (likely(ac->avail < ac->limit)) {
		STATS_INC_FREEHIT(cachep);
		/**
		 * 本地高速缓存有空闲指针,则该指针被加到本地高速缓存后返回。
		 */
		ac_entry(ac)[ac->avail++] = objp;
		return;
	} else {
		STATS_INC_FREEMISS(cachep);
		/**
		 * 调用cache_flusharray,清空本地高速缓存
		 */
		cache_flusharray(cachep, ac);
		/**
		 * 然后将指针加到本地高速缓存。
		 */
		ac_entry(ac)[ac->avail++] = objp;
	}
}

cache_flusharray如下:
static void cache_flusharray (kmem_cache_t* cachep, struct array_cache *ac)
{
	int batchcount;

	batchcount = ac->batchcount;
#if DEBUG
	BUG_ON(!batchcount || batchcount > ac->avail);
#endif
	check_irq_off();
	/**
	 * 获得自旋锁
	 */
	spin_lock(&cachep->spinlock);
	/**
	 * 如果包含一个共享本地高速缓存
	 */
	if (cachep->lists.shared) {
		struct array_cache *shared_array = cachep->lists.shared;
		int max = shared_array->limit-shared_array->avail;
		/**
		 * 该共享缓存还没有满
		 */
		if (max) {
			if (batchcount > max)
				batchcount = max;
			/**
			 * 将batchcount个指针放到共享高速缓存中。
			 */
			memcpy(&ac_entry(shared_array)[shared_array->avail],
					&ac_entry(ac)[0],
					sizeof(void*)*batchcount);
			/**
			 * 上移batchcount个指针来重新填充共享本地高速缓存
			 */
			shared_array->avail += batchcount;
			goto free_done;
		}
	}

	/**
	 * 将当前包含在本地高速缓存中的ac->batchcount个对象归还给slab分配器。
	 */
	free_block(cachep, &ac_entry(ac)[0], batchcount);
free_done:
#if STATS
	{
		int i = 0;
		struct list_head *p;

		p = list3_data(cachep)->slabs_free.next;
		while (p != &(list3_data(cachep)->slabs_free)) {
			struct slab *slabp;

			slabp = list_entry(p, struct slab, list);
			BUG_ON(slabp->inuse);

			i++;
			p = p->next;
		}
		STATS_SET_FREEABLE(cachep, i);
	}
#endif
	/**
	 * 释放锁
	 */
	spin_unlock(&cachep->spinlock);
	/**
	 * 通过减去被移到共享本地高速缓存或被释放到slab分配器的对象的个数来更新本地高速缓存描述的avail字段
	 */
	ac->avail -= batchcount;
	/**
	 * 移动本地高速缓存数组起始处的那个本地高速缓存中的所有指针。
	 * 因为已经把第一个对象指针从本地高速缓存上删除,因此剩余的指针必须上移。
	 */
	memmove(&ac_entry(ac)[0], &ac_entry(ac)[batchcount],
			sizeof(void*)*ac->avail);
}

上面为专用对象的分配和释放函数,对于通用对象的管理函数如下:
/**
 * 获得普通高速缓存中的对象。
 */
static inline void *kmalloc(size_t size, int flags)
{
	if (__builtin_constant_p(size)) {
		int i = 0;
		/**
		 * 找size对应的几何分布大小值。
		 */
#define CACHE(x) \
		if (size <= x) \
			goto found; \
		else \
			i++;
#include "kmalloc_sizes.h"
		/**
		 * 运行到此,说明要分配的对象太大,不能分配这么大的对象。
		 */
#undef CACHE
		{
			extern void __you_cannot_kmalloc_that_much(void);
			__you_cannot_kmalloc_that_much();
		}
		/**
		 * 请求分配的size对应的高速缓存描述符索引号为i
		 * 根据GFP_DMA,在不同的高速缓存描述符中分配对象。
		 */
found:
		return kmem_cache_alloc((flags & GFP_DMA) ?
			malloc_sizes[i].cs_dmacachep :
			malloc_sizes[i].cs_cachep, flags);
	}
	return __kmalloc(size, flags);
}

void * __kmalloc (size_t size, int flags)
{
	struct cache_sizes *csizep = malloc_sizes;

	for (; csizep->cs_size; csizep++) {
		if (size > csizep->cs_size)
			continue;
#if DEBUG
		/* This happens if someone tries to call
		 * kmem_cache_create(), or kmalloc(), before
		 * the generic caches are initialized.
		 */
		BUG_ON(csizep->cs_cachep == NULL);
#endif
		return __cache_alloc(flags & GFP_DMA ?
			 csizep->cs_dmacachep : csizep->cs_cachep, flags);
	}
	return NULL;
}

释放函数如下:
/**
 * 释放由kmalloc接口分配的内存
 */
void kfree (const void *objp)
{
	kmem_cache_t *c;
	unsigned long flags;

	if (!objp)
		return;
	local_irq_save(flags);
	kfree_debugcheck(objp);
	/**
	 * 通过((kmem_cache_t *)(pg)->lru.next),可确定合适的高速缓存描述符。
	 */
	c = GET_PAGE_CACHE(virt_to_page(objp));
	/**
	 * __cache_free释放高速缓存中的对象。
	 */
	__cache_free(c, (void*)objp);
	local_irq_restore(flags);
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值