Linux Slub分配器(四)--分配对象

水平有限,描述不当之处还请之处,转载请注明出处http://blog.csdn.net/vanbreaker/article/details/7700482

对象的分配过程体现了内存管理器对内存对象的组织方式,相较Slab分配器,Slub在组织对象的方式上给人的感觉就是简洁精悍。Slub没有用任何的管理区数组来组织这些对象,而是巧妙的将对象之间联系的桥梁嵌入在对象自身之中,因为请求分配对象的程序并不关心在对象分配之前,内存中的内容是什么,而这座桥梁就是空闲对象指针void*,它用以指明下一个空闲对象的地址。void*在对象内存中的偏移为offset,需要注意的一点是,struct kmem_cache和struct kmem_cache_cpu这两个结构中都存在offset变量,两个offset都是表示空闲对象指针的偏移,只不过前者是以字节为单位而后者是以字长为单位!

另外,Slab和Slub都引入了本地CPU缓存的概念,但是Slab分配器中的本地CPU缓存中的对象都是以batchcount为大小,从slab中转移填充(或释放)的,这增加了操作上的复杂性,而Slub则是直接引入一个slab作为本地CPU缓存的管理单位,这样更为简洁。在Slub分配器中,如果一个slab位于本地CPU缓存上的话则称其处于冻结状态(frozen),如果处于slab 链表中中,则称其处于解冻状态(unfrozen)。

下面就来看具体的代码,函数void *kmem_cache_alloc(struct kmem_cache *s, gfp_t gfpflags)用于在指定的缓存中分配一个对象,它实质上是对函数slab_alloc()的一个封装,我们从slab_alloc()开始进行分析。

static __always_inline void *slab_alloc(struct kmem_cache *s,
		gfp_t gfpflags, int node, unsigned long addr)
{
	void **object;
	struct kmem_cache_cpu *c;
	unsigned long flags;
	unsigned int objsize;

	gfpflags &= gfp_allowed_mask;

	lockdep_trace_alloc(gfpflags);
	might_sleep_if(gfpflags & __GFP_WAIT);

	if (should_failslab(s->objsize, gfpflags))
		return NULL;

	local_irq_save(flags);
	/*获取本地CPU的slab信息结构*/
	c = get_cpu_slab(s, smp_processor_id());
	objsize = c->objsize;
	/*如果本地CPU的freelist为空,也就是说没有空闲对象了,或者节点不匹配
	  则通过慢速途径进行分配*/
	if (unlikely(!c-> freelist || !node_match(c, node)))

		object = __slab_alloc(s, gfpflags, node, addr, c);

	else {/*常规分配,在freelist不为空的情况下*/
		object = c->freelist;//将freelist指向的对象赋给object
		c->freelist = object[c->offset];//通过对象的空闲指针找到下一个空闲对象并保存在freelist中
		stat(c, ALLOC_FASTPATH);
	}
	local_irq_restore(flags);

	/*如果设置了清零标识并且分配成功,则将object的内存区域置零*/
	if (unlikely((gfpflags & __GFP_ZERO) && object))
		memset(object, 0, objsize);

	kmemcheck_slab_alloc(s, gfpflags, object, c->objsize);
	kmemleak_alloc_recursive(object, objsize, 1, s->flags, gfpflags);

	return object;
}

我们捡主要的操作进行分析。

首先要从本地CPU高速缓存中分配,那么当然得获取这个结构保存在变量c中。然后出现了两种情况:

1.c->freelist指向NULL,表示本地CPU高速缓存中已经没有可用的空闲对象了,另外一个不想见到的情况就是节点不匹配,出现这两种情况中的任意一种都要通过__slab_alloc()走一条慢速分配的路径;

2.没有异常情况,可以直接从本地CPU高速缓存中进行分配,我们先拿这个来分析。

我们可以看到分配的操作非常的简洁,

object = c->freelist;

c->freelist = object[c->offset];

第一句话很容易理解,freelist指向了一个最热的对象,这一句话就是将这个对象的地址保存在object中。

第二句话的意义也很明显,就是将下一个空闲对象的地址保存在freelist中,以便下次分配使用。但这是如何实现的呢?注意object的定义是void **,这里似乎有点复杂,为什么要定义成一个二级指针?这是为了能够以字长为单位来访问到偏移为offset处的内存值,因为void *指针占用的存储空间是一个字长!可以将object理解成一个void*指针数组的起始地址(数组名),这时在数组内做偏移的运算时就是以void *为单位的,取出来的数值也是以void*为单位的,也就是说都是基于字长的。假如object定义成void*指针,那么虽然仍然可以找到下一个对象的地址,但是却不方便将这个字长的地址值给取出来(必须得将结果强制转换成void**再取内容)!总而言之,这种实现方法是最简洁雅观的。

接下来我们再看慢速分配路径究竟做了什么。

static void *__slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node,
			  unsigned long addr, struct kmem_cache_cpu *c)
{
	void **object;
	struct page *new;

	/* We handle __GFP_ZERO in the caller */
	gfpflags &= ~__GFP_ZERO;

	if (!c->page)/*本地CPU的slab结构中没有存储可分配的内存,则要为其寻找新的slab*/
		goto new_slab;

	slab_lock(c->page);
	if (unlikely(!node_match(c, node)))//节点不匹配
		goto another_slab;

	stat(c, ALLOC_REFILL);

load_freelist:
	object = c->page->freelist;//获取一个对象
	if (unlikely(!object))
		goto another_slab;
	if (unlikely(SLABDEBUG && PageSlubDebug(c->page)))
		goto debug;

	c->freelist = object[c->offset]; //更新freelist
	c->page->inuse = c->page->objects;
	/*一旦slab从partial slab链表转移到本地CPU,则page_freelist不再负责维护空闲对象了,
	  因此设为NULL*/
	c->page->freelist = NULL;
	c->node = page_to_nid(c->page);
unlock_out:
	slab_unlock(c->page);
	stat(c, ALLOC_SLOWPATH);
	return object; //返回对象

another_slab:
	deactivate_slab(s, c);//将本地CPU的slab移回partial slab链表

new_slab:
	/*从partial slab链表中寻找一个slab*/
	new = get_partial(s, gfpflags, node);
	if (new) {/*获取成功则直接将该slab交给本地CPU*/
		c->page = new;
		stat(c, ALLOC_FROM_PARTIAL);
		goto load_freelist;
	}

	if (gfpflags & __GFP_WAIT)
		local_irq_enable();

	/*如果partial slab链表中已无法获取slab则创建新的slab*/
	new = new_slab(s, gfpflags, node);

	if (gfpflags & __GFP_WAIT)
		local_irq_disable();

	if (new) {//创建成功
		c = get_cpu_slab(s, smp_processor_id());//获取本地CPU的slab信息结构
		stat(c, ALLOC_SLAB);
		if (c->page)//本地CPU中存在slab,则移除该slab
			flush_slab(s, c);
		slab_lock(new);
		__SetPageSlubFrozen(new);//冻结slab,表明slab处于本地CPU中
		c->page = new;//本地CPU中的slab设置为新创建的slab
		goto load_freelist;
	}
	if (!(gfpflags & __GFP_NOWARN) && printk_ratelimit())
		slab_out_of_memory(s, gfpflags, node);
	return NULL;
debug:
	if (!alloc_debug_processing(s, c->page, object, addr))
		goto another_slab;

	c->page->inuse++;
	c->page->freelist = object[c->offset];
	c->node = -1;
	goto unlock_out;
}


对于一般的情况(本地CPU缓存中无空闲对象),主要的处理过程就是load_freelist-->another_slab->new_slab

load_freelist:其主要操作是在获取到新的slab之后,它的工作我们可以理解成slab交出它对slab的管理权,这个管理权的体现就是freelist.将c->page->freelist维护的空闲对象交给c->freelist来维护,这也是slab由解冻状态转移到冻结状态的一个体现

another_slab:将当前本地CPU缓存中的slab移回对应的struct kmem_cache_node的slab链表中

new_slab:寻找新的slab,首先要到slab链表中寻找,如果没有的话则通过new_slab()函数创建新的slab,该函数在后面 再做分析

我们来看another_slab的处理

static void deactivate_slab(struct kmem_cache *s, struct kmem_cache_cpu *c)
{
	struct page *page = c->page;
	int tail = 1;

	if (page->freelist)
		stat(c, DEACTIVATE_REMOTE_FREES);
	/*
	 * Merge cpu freelist into slab freelist. Typically we get here
	 * because both freelists are empty. So this is unlikely
	 * to occur.
	 */
	 /*将本地CPU由freelist维护的空闲对象转交给page的freelist维护,配合后面的解冻操作
	   这样就可以将对象从本地CPU中转移到partial slab链表中*/
	while (unlikely(c->freelist)) {
		void **object;

		tail = 0;	/* Hot objects. Put the slab first */

		/* Retrieve object from cpu_freelist */
		/*从cpu_freelist中取出对象*/
		object = c->freelist;
		c->freelist = c->freelist[c->offset];

		/* And put onto the regular freelist */
		/*将对象转移到page_freelist中*/
		object[c->offset] = page->freelist;
		page->freelist = object;
		page->inuse--;
	}
	c->page = NULL;
	/*将slab解冻,移回partial slab链表*/
	unfreeze_slab(s, page, tail);
}


static void unfreeze_slab(struct kmem_cache *s, struct page *page, int tail)
{
	struct kmem_cache_node *n = get_node(s, page_to_nid(page));
	struct kmem_cache_cpu *c = get_cpu_slab(s, smp_processor_id());

	__ClearPageSlubFrozen(page);
	if (page->inuse) {//slab中有对象被分配出去

		if (page->freelist) {//page_freelist不为空
			/*将page添加到partial slab链表*/
			add_partial(n, page, tail);
			stat(c, tail ? DEACTIVATE_TO_TAIL : DEACTIVATE_TO_HEAD);
		} else {//否则添加到full slab链表
			stat(c, DEACTIVATE_FULL);
			if (SLABDEBUG && PageSlubDebug(page) &&
						(s->flags & SLAB_STORE_USER))
				add_full(n, page);
		}
		slab_unlock(page);
	} else {//slab中的对象都是空闲的
		stat(c, DEACTIVATE_EMPTY);
		/*如果partial链表中的slab数小于最小slab数,则依然将该slab添加到partial链表中,
		 而不是释放该slab,这样可以减小伙伴系统的工作负担*/
		if (n->nr_partial < s->min_partial) {
			/*
			 * Adding an empty slab to the partial slabs in order
			 * to avoid page allocator overhead. This slab needs
			 * to come after the other slabs with objects in
			 * so that the others get filled first. That way the
			 * size of the partial list stays small.
			 *
			 * kmem_cache_shrink can reclaim any empty slabs from
			 * the partial list.
			 */
			add_partial(n, page, 1);
			slab_unlock(page);
		} else {
			slab_unlock(page);
			stat(get_cpu_slab(s, raw_smp_processor_id()), FREE_SLAB);
			discard_slab(s, page);//销毁该slab
		}
	}
}


Slub这种处理方法在某些地方也稍微增加了一些复杂性,由于slab从本地CPU缓存移回到了slab链表中,此时该slab还有对象分配出去了没有回收,而partial slab链表中的某个slab或者一个新创建的slab引入到了本地CPU缓存中,这样在每次释放对象时,都要考虑当前本地CPU缓存中的slab究竟是不是自己本该归属的slab! 这个过程下节再做分析!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值