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()开始进行分析。

 

  1. static __always_inline void *slab_alloc(struct kmem_cache *s,  
  2.         gfp_t gfpflags, int node, unsigned long addr)  
  3. {  
  4.     void **object;  
  5.     struct kmem_cache_cpu *c;  
  6.     unsigned long flags;  
  7.     unsigned int objsize;  
  8.   
  9.     gfpflags &= gfp_allowed_mask;  
  10.   
  11.     lockdep_trace_alloc(gfpflags);  
  12.     might_sleep_if(gfpflags & __GFP_WAIT);  
  13.   
  14.     if (should_failslab(s->objsize, gfpflags))  
  15.         return NULL;  
  16.   
  17.     local_irq_save(flags);  
  18.     /*获取本地CPU的slab信息结构*/  
  19.     c = get_cpu_slab(s, smp_processor_id());  
  20.     objsize = c->objsize;  
  21.     /*如果本地CPU的freelist为空,也就是说没有空闲对象了,或者节点不匹配 
  22.       则通过慢速途径进行分配*/  
  23.     if (unlikely(!c-> freelist || !node_match(c, node)))  
  24.   
  25.         object = __slab_alloc(s, gfpflags, node, addr, c);  
  26.   
  27.     else {/*常规分配,在freelist不为空的情况下*/  
  28.         object = c->freelist;//将freelist指向的对象赋给object   
  29.         c->freelist = object[c->offset];//通过对象的空闲指针找到下一个空闲对象并保存在freelist中   
  30.         stat(c, ALLOC_FASTPATH);  
  31.     }  
  32.     local_irq_restore(flags);  
  33.   
  34.     /*如果设置了清零标识并且分配成功,则将object的内存区域置零*/  
  35.     if (unlikely((gfpflags & __GFP_ZERO) && object))  
  36.         memset(object, 0, objsize);  
  37.   
  38.     kmemcheck_slab_alloc(s, gfpflags, object, c->objsize);  
  39.     kmemleak_alloc_recursive(object, objsize, 1, s->flags, gfpflags);  
  40.   
  41.     return object;  
  42. }  
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**再取内容)!总而言之,这种实现方法是最简洁雅观的。

 

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

  1. static void *__slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node,  
  2.               unsigned long addr, struct kmem_cache_cpu *c)  
  3. {  
  4.     void **object;  
  5.     struct page *new;  
  6.   
  7.     /* We handle __GFP_ZERO in the caller */  
  8.     gfpflags &= ~__GFP_ZERO;  
  9.   
  10.     if (!c->page)/*本地CPU的slab结构中没有存储可分配的内存,则要为其寻找新的slab*/  
  11.         goto new_slab;  
  12.   
  13.     slab_lock(c->page);  
  14.     if (unlikely(!node_match(c, node)))//节点不匹配   
  15.         goto another_slab;  
  16.   
  17.     stat(c, ALLOC_REFILL);  
  18.   
  19. load_freelist:  
  20.     object = c->page->freelist;//获取一个对象   
  21.     if (unlikely(!object))  
  22.         goto another_slab;  
  23.     if (unlikely(SLABDEBUG && PageSlubDebug(c->page)))  
  24.         goto debug;  
  25.   
  26.     c->freelist = object[c->offset]; //更新freelist   
  27.     c->page->inuse = c->page->objects;  
  28.     /*一旦slab从partial slab链表转移到本地CPU,则page_freelist不再负责维护空闲对象了, 
  29.       因此设为NULL*/  
  30.     c->page->freelist = NULL;  
  31.     c->node = page_to_nid(c->page);  
  32. unlock_out:  
  33.     slab_unlock(c->page);  
  34.     stat(c, ALLOC_SLOWPATH);  
  35.     return object; //返回对象   
  36.   
  37. another_slab:  
  38.     deactivate_slab(s, c);//将本地CPU的slab移回partial slab链表   
  39.   
  40. new_slab:  
  41.     /*从partial slab链表中寻找一个slab*/  
  42.     new = get_partial(s, gfpflags, node);  
  43.     if (new) {/*获取成功则直接将该slab交给本地CPU*/  
  44.         c->page = new;  
  45.         stat(c, ALLOC_FROM_PARTIAL);  
  46.         goto load_freelist;  
  47.     }  
  48.   
  49.     if (gfpflags & __GFP_WAIT)  
  50.         local_irq_enable();  
  51.   
  52.     /*如果partial slab链表中已无法获取slab则创建新的slab*/  
  53.     new = new_slab(s, gfpflags, node);  
  54.   
  55.     if (gfpflags & __GFP_WAIT)  
  56.         local_irq_disable();  
  57.   
  58.     if (new) {//创建成功   
  59.         c = get_cpu_slab(s, smp_processor_id());//获取本地CPU的slab信息结构   
  60.         stat(c, ALLOC_SLAB);  
  61.         if (c->page)//本地CPU中存在slab,则移除该slab   
  62.             flush_slab(s, c);  
  63.         slab_lock(new);  
  64.         __SetPageSlubFrozen(new);//冻结slab,表明slab处于本地CPU中   
  65.         c->page = new;//本地CPU中的slab设置为新创建的slab   
  66.         goto load_freelist;  
  67.     }  
  68.     if (!(gfpflags & __GFP_NOWARN) && printk_ratelimit())  
  69.         slab_out_of_memory(s, gfpflags, node);  
  70.     return NULL;  
  71. debug:  
  72.     if (!alloc_debug_processing(s, c->page, object, addr))  
  73.         goto another_slab;  
  74.   
  75.     c->page->inuse++;  
  76.     c->page->freelist = object[c->offset];  
  77.     c->node = -1;  
  78.     goto unlock_out;  
  79. }  
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的处理

  1. <SPAN style="FONT-SIZE: 12px">static void deactivate_slab(struct kmem_cache *s, struct kmem_cache_cpu *c)  
  2. {  
  3.     struct page *page = c->page;  
  4.     int tail = 1;  
  5.   
  6.     if (page->freelist)  
  7.         stat(c, DEACTIVATE_REMOTE_FREES);  
  8.     /* 
  9.      * Merge cpu freelist into slab freelist. Typically we get here 
  10.      * because both freelists are empty. So this is unlikely 
  11.      * to occur. 
  12.      */  
  13.      /*将本地CPU由freelist维护的空闲对象转交给page的freelist维护,配合后面的解冻操作 
  14.        这样就可以将对象从本地CPU中转移到partial slab链表中*/  
  15.     while (unlikely(c->freelist)) {  
  16.         void **object;  
  17.   
  18.         tail = 0;   /* Hot objects. Put the slab first */  
  19.   
  20.         /* Retrieve object from cpu_freelist */  
  21.         /*从cpu_freelist中取出对象*/  
  22.         object = c->freelist;  
  23.         c->freelist = c->freelist[c->offset];  
  24.   
  25.         /* And put onto the regular freelist */  
  26.         /*将对象转移到page_freelist中*/  
  27.         object[c->offset] = page->freelist;  
  28.         page->freelist = object;  
  29.         page->inuse--;  
  30.     }  
  31.     c->page = NULL;  
  32.     /*将slab解冻,移回partial slab链表*/  
  33.     unfreeze_slab(s, page, tail);  
  34. }  
  35. </SPAN>  
<span style="font-size:12px;">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);
}
</span>


 

  1. static void unfreeze_slab(struct kmem_cache *s, struct page *page, int tail)  
  2. {  
  3.     struct kmem_cache_node *n = get_node(s, page_to_nid(page));  
  4.     struct kmem_cache_cpu *c = get_cpu_slab(s, smp_processor_id());  
  5.   
  6.     __ClearPageSlubFrozen(page);  
  7.     if (page->inuse) {//slab中有对象被分配出去   
  8.   
  9.         if (page->freelist) {//page_freelist不为空   
  10.             /*将page添加到partial slab链表*/  
  11.             add_partial(n, page, tail);  
  12.             stat(c, tail ? DEACTIVATE_TO_TAIL : DEACTIVATE_TO_HEAD);  
  13.         } else {//否则添加到full slab链表   
  14.             stat(c, DEACTIVATE_FULL);  
  15.             if (SLABDEBUG && PageSlubDebug(page) &&  
  16.                         (s->flags & SLAB_STORE_USER))  
  17.                 add_full(n, page);  
  18.         }  
  19.         slab_unlock(page);  
  20.     } else {//slab中的对象都是空闲的   
  21.         stat(c, DEACTIVATE_EMPTY);  
  22.         /*如果partial链表中的slab数小于最小slab数,则依然将该slab添加到partial链表中, 
  23.          而不是释放该slab,这样可以减小伙伴系统的工作负担*/  
  24.         if (n->nr_partial < s->min_partial) {  
  25.             /* 
  26.              * Adding an empty slab to the partial slabs in order 
  27.              * to avoid page allocator overhead. This slab needs 
  28.              * to come after the other slabs with objects in 
  29.              * so that the others get filled first. That way the 
  30.              * size of the partial list stays small. 
  31.              * 
  32.              * kmem_cache_shrink can reclaim any empty slabs from 
  33.              * the partial list. 
  34.              */  
  35.             add_partial(n, page, 1);  
  36.             slab_unlock(page);  
  37.         } else {  
  38.             slab_unlock(page);  
  39.             stat(get_cpu_slab(s, raw_smp_processor_id()), FREE_SLAB);  
  40.             discard_slab(s, page);//销毁该slab   
  41.         }  
  42.     }  
  43. }  
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! 这个过程下节再做分析!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值