Slub代码流程分析

Slub代码流程分析:

slub的代码晦涩难懂,在看书或者相关资料时看似简单,再去对照代码分析时会发现被打回原形,就像数学老师推导公式和自己去推导公式一样。因此,需要静下来心来仔细研读,看完原理之后分析源码,一遍不理解,两遍,三遍,每一遍都会对slub有更深的理解,再多看多想,进行咀嚼消化,自问自答,等所有的自问都能自答时也就形成自己的理解了,也就开始懂了。


slab_alloc

享受阅读代码带来的快乐,尤其是每一次的豁然开朗。

  1. 从per-cpu的cpu_slab->freelist上取object,如果成功直接返回,否则转2。

  2. cpu_slab->page->freelist赋值给cpu_slab->freelist,并设置cpu_slab->page->freelist为NULL,然后从cpu_slab->freelist取object,如果成功直接返回,若失败转下一步。
    展开来说
    2.1 先查看cpu_slab->page(当前cpu正在使用的slab)是否为空,如果为空转3,不为空则转2.2,
    2.2 若cpu正在使用的slab不属于当前节点或slab设置了PFMEMALLOC标志,但是申请内存时没有设置ALLOC_NO_WATERMARK,这两种 情况会deactivate_slab,也就是将当前cpu的slab清空,然后转3。否则重新判断cpu_slab->freelist是否有空闲object(因为可能由于cpu迁移和被中断打断),如果有空闲的object则直接取一个返回。如果freelist为空且cpu_slab->page不为空(走到这cpu_slab->page肯定不为空)且这个slab是frozen的,调用 getfreelist 获取freelist,也就是将cpu_slab->page->freelist摘下来给freelist,然后将cpu_slab->page->freelist置为NULL(这是一个常规动作,摘了cpu_slab->page->freelist的链表就得要清空它),重加载load_freelist并返回给用户。若getfreelist返回的是NULL,则说明这个cpu正在使用的slab(即cpu_slab->page)已经用完了,那么就要将其deactivate_slab,将当前slab移出当前cpu挂入node的full链表。然后转3。

  3. 从cpu的cpu_slab->partial部分空闲链上取一个slab(partial上的slab均为frozen)下来给cpu_slab->page,然后重新走2.2去取object。若cpu_slab->partial没有slab则转下一步4。

  4. 从kmem_cache_node的node->partial链上取,若node->partial上没有slab转5,否则继续,get_partial_node会遍历node->partial上的slab并摘取若干个slab给cpu_slab,条件在put_cpu_partial(s, page, 0);判断,只要不超过cpu_slab的objects阈值kmem_cache->cpu_partial即可,先是对slab进行frozen(cpu->partial上的slab均为frozen的),然后将当前cpu->freelist = cpu->page_freelistcpu->page->freelist=NULL,重新load_freelist。

  5. 从buddy系统申请pages(如果申请失败会将order),根据kmem_cache的成员oo获取order,然后调用alloc_pages申请2^order个pages。若申请成功则将pages切割成objects并用链表串起来(注意kasan也就是在这个时候kasan_poison_slab对slab进行poison),并进行初始化(根据SLUB_DEBUG init_object->setup_object初始化left_red_padred_zonealloc/free track,例如对user的内容设置为0x6b6b6b...a5,而red_zone设置为oxbb),设置page->inuse = page->objects,并将slab冻结frozen(在cpu_slab->partialcpu_slab->page上的slab都是frozen的)。在进入new_slab_objects之前必定cpu->page为NULL且cpu->freelist为NULL,那么从buddy申请之后还要判断一下cpu_slab->page是否有slab,这期间可能会free内存挂接到cpu_slab->page->freelist,故要flush_slab。然后load_freelistcpu_slab->page->freelsitcpu_slab->freelist,设置cpu_slab->page->freelist为NULL。


slab_free:

享受分析代码带来的快乐,尤其是每一次的豁然开朗。

1,先获取object所在的page,如果page没有带PageSlab标记则直接走__free_pages释放给buddy。
2,如果object所在的slab是当前cpu正在使用的slab,那么就直接将object放入cpu_slab->freelist。否则走慢路径转3,先将object放回cpu_slab->page->freelist,根据这个slab的状况来决定如何处理。
① slab未冻结且释放该object后slab为全部空闲empty slab,即释放的这个object是该slab唯一被使用的object,是一个partai slab。
② slab未冻结且释放该object后slab为只有1个object的部分空闲partial slab,即释放当前object前已经没有可以用的object了,是一个full slab。
③ slab未冻结且释放该object后slab大于1个object小于page->objects-1(即在slab未冻结情况下排除①②两种情况)。
④ slab冻结。(cpu的所有slab都是冻结的,如果未冻结说明slab不属于任何一个cpu)

对于情况①的处理:
当前这个slab会变成完全空闲(该slab在node->partial链表上或者在full链表上),会获取node,然后看node->partial是否大于等于kmem_cache->min_partial,如果是则说明node的空闲slab比较多,应该还一部分给buddy,故而会先将该slab从partial链表上移除,然后直接将该slab释放给buddy系统。

对于情况②的处理:
当前这个slab不属于任何一个CPU,需要对其frozen冻结,然后remove_full(slab可能挂node->full也可能是游离状态,具体与配置项有关)将该slab从node->full链表上摘除,并将put_cpu_partial放入到当前cpu_slab->partialput_cpu_partial这个流程会判断cpu_slab->partial的object数量(注意不是slab数量,直接读取第一个slab的page->pobjects)超过kmem_cache的 cpu_slab->partial阈值则会将其清空,详见该函数put_cpu_partial

对于情况③④的处理:那么直接将object放入该slab的page->freelist即可,更新cpu_slab->page->pobjects(object空闲总数)、cpu_slab->page->pages(slab数量)·后直接返回。

综上:只有①②两种情况才会特殊处理一下,因为①这种情况说明释放的内存还挺多,那我们看看是否有多余的内存释放给buddy。②这种情况说明使用的内存比较频繁,看看把释放的object所属slab给cpu_slab->partial


部分函数解析

享受分析代码带来的快乐,尤其是每一次的豁然开朗。

cpu_slab->freelist和cpu_slab->page->freelist的关系?
他们上面挂接的object都属于同一个slab,例如若cpu1的object传给了cpu0,最后在cpu0上释放cpu1的object时,cpu0会帮cpu1将object挂到cpu1的cpu_slab->page->freelist上,也就是该cpu_slab->freelist和cpu_slab->page->freelist的object都是cpu1的slab的object。同时用while循环来保证并发问题。

static void __slab_free(struct kmem_cache *s, struct page *page,
			void *head, void *tail, int cnt,
			unsigned long addr)

{
	void *prior;
	int was_frozen;
	struct page new;
	unsigned long counters;
	struct kmem_cache_node *n = NULL;
	unsigned long uninitialized_var(flags);

	stat(s, FREE_SLOWPATH);

	if (kmem_cache_debug(s) &&
	    !(n = free_debug_processing(s, page, head, tail, cnt,
					addr, &flags)))
		return;

	do {
		if (unlikely(n)) {
			spin_unlock_irqrestore(&n->list_lock, flags);
			n = NULL;
		}
		prior = page->freelist;
		counters = page->counters;
		set_freepointer(s, tail, prior); 1,将page->freelist挂接在tail object上
		new.counters = counters;
		was_frozen = new.frozen;
		new.inuse -= cnt; 
		这里为什么要减掉cnt?因为初始化是全部在用,当申请object时是不会减的,而释放时会减
		之后等到!new.inuse == true时,说明整个slab都是空闲的,而当整个slab都用完时则
		new.inuse就是名副其实的全部在用了
		
		if ((!new.inuse || !prior) && !was_frozen) {

			if (kmem_cache_has_cpu_partial(s) && !prior) {

				/*
				 * Slab was on no list before and will be
				 * partially empty
				 * We can defer the list move and instead
				 * freeze it.
				 */
				new.frozen = 1;

			} else { /* Needs to be taken off a list */

				n = get_node(s, page_to_nid(page));
				/*
				 * Speculatively acquire the list_lock.
				 * If the cmpxchg does not succeed then we may
				 * drop the list_lock without any processing.
				 *
				 * Otherwise the list_lock will synchronize with
				 * other processors updating the list of slabs.
				 */
				spin_lock_irqsave(&n->list_lock, flags);

			}
		}

	} while (!cmpxchg_double_slab(s, page,
		prior, counters,
		head, new.counters,
		"__slab_free"));  
		2,page->freelist=head;就是将head->..->tail这一串object都挂入到page->freelist上,
		例如在cpu0上释放cpu1的object时,cpu0会帮cpu1将这一串object都挂入到cpu1的page->freelist上。
		...
}

deactivate_slab:主要功能就是将slab从CPU上移除释放给node或直接还给buddy。
在__slab_alloc函数会调用这个,有如下两种情况:
① 如果CPU正在使用的slab不属于当前节点或者slab设置PFMEMALLOC但是申请内存时没有设置ALLOC_NO_WATERMARK,就会将slab移出当前CPU。
② 如果cpu_slab->freelist没有可用内存了且cpu_slab->page->freelist也没有空闲内存,也就是当前这个CPU正在使用的slab没有内存了,那么就需要将该slab从当前CPU移除,最后add_full挂入到node的full list(这需要开启CONFIG_SLUB_DEBUG)。

针对①②两种情况都需要将slab移除当前cpu,说明当前cpu的slab已经耗尽了,需要腾出位置来给新的有空闲object的slab。

static void deactivate_slab(struct kmem_cache *s, struct page *page,
				void *freelist)
{
	enum slab_modes { M_NONE, M_PARTIAL, M_FULL, M_FREE };
	struct kmem_cache_node *n = get_node(s, page_to_nid(page));
	int lock = 0;
	enum slab_modes l = M_NONE, m = M_NONE;
	void *nextfree;
	int tail = DEACTIVATE_TO_HEAD;
	struct page new;
	struct page old;

	if (page->freelist) {
		stat(s, DEACTIVATE_REMOTE_FREES);
		tail = DEACTIVATE_TO_TAIL;
	}

	/*
	 * Stage one: Free all available per cpu objects back
	 * to the page freelist while it is still frozen. Leave the
	 * last one.
	 *
	 * There is no need to take the list->lock because the page
	 * is still frozen.
	 */while循环是保留freelist上最后一个可用的object,将其他object都挂到page->freelist上
	while (freelist && (nextfree = get_freepointer(s, freelist))) { 
		void *prior;
		unsigned long counters;

		do {
			prior = page->freelist;
			counters = page->counters;
			set_freepointer(s, freelist, prior); 将freelist指向
			new.counters = counters;
			new.inuse--; 
			因为这里deactive就相当于free,故而要减1,
			下面即将判断new.inuse的值,表明是否slab已经完全空闲,则可以释放给node或buddy了
			否则就挂入到node的partial链表上。
			
			VM_BUG_ON(!new.frozen); 因为是CPU的slab故这个slab肯定是frozen

		} while (!__cmpxchg_double_slab(s, page,
			prior, counters,
			freelist, new.counters,
			"drain percpu freelist")); 
			page->freelist=freelist,将freelist上object加到page->freelist头部。

		freelist = nextfree;
	}

	/*
	 * Stage two: Ensure that the page is unfrozen while the
	 * list presence reflects the actual number of objects
	 * during unfreeze.
	 *
	 * We setup the list membership and then perform a cmpxchg
	 * with the count. If there is a mismatch then the page
	 * is not unfrozen but the page is on the wrong list.
	 *
	 * Then we restart the process which may have to remove
	 * the page from the list that we just put it on again
	 * because the number of objects in the slab may have
	 * changed.
	 */
redo:

	old.freelist = page->freelist;
	old.counters = page->counters;
	VM_BUG_ON(!old.frozen);

	/* Determine target state of the slab */
	new.counters = old.counters;
	if (freelist) {
		new.inuse--;
		set_freepointer(s, freelist, old.freelist);前面残留的那个object的next指向page->freelist来
		new.freelist = freelist;
		将所有的object都一股脑由page->freelist指向,此时所有空闲的object都在page->freelist上了
		为什么搞这么一通?
			我得理解是因为每次cpu_slab->freelist为NULL时都是直接
			cpu_slab->freelist=cpu_slab->page->freelist
			这样能一次性就把可用object全部给cpu_slab->freelist了,这里的整合就是为了在freelist为null
			时一把就把page->freelist全部freelist,也能处理好page.inuse。比如get_freelist。
	} else
		new.freelist = old.freelist;

	new.frozen = 0;

	if (!new.inuse && n->nr_partial >= s->min_partial)
		m = M_FREE; 将freelist的object全部移除到page->freelist上
	else if (new.freelist) {
		m = M_PARTIAL;
		if (!lock) {
			lock = 1;
			/*
			 * Taking the spinlock removes the possiblity
			 * that acquire_slab() will see a slab page that
			 * is frozen
			 */
			spin_lock(&n->list_lock);
		}
	} else {
		m = M_FULL;
		if (kmem_cache_debug(s) && !lock) {
			lock = 1;
			/*
			 * This also ensures that the scanning of full
			 * slabs from diagnostic functions will not see
			 * any frozen slabs.
			 */
			spin_lock(&n->list_lock);
		}
	}

	if (l != m) {

		if (l == M_PARTIAL)——这里是为了redo而设置的,chg失败才会执行这里

			remove_partial(n, page);

		else if (l == M_FULL)

			remove_full(s, n, page);

		if (m == M_PARTIAL) {——这里是另外一个if,不要跟上面搞混了

			add_partial(n, page, tail);
			stat(s, tail);

		} else if (m == M_FULL) {

			stat(s, DEACTIVATE_FULL);
			add_full(s, n, page);

		}
	}

	l = m;
	if (!__cmpxchg_double_slab(s, page,
				old.freelist, old.counters,
				new.freelist, new.counters,
				"unfreezing slab"))
		goto redo; 如果这次cmpxchg失败了还要redo,那么需要先删除再插入,在cmpxchg,保证一致性

	if (lock)
		spin_unlock(&n->list_lock);

	if (m == M_FREE) {
		stat(s, DEACTIVATE_EMPTY);
		discard_slab(s, page);
		stat(s, FREE_SLAB);
	}
}

put_cpu_partial:主要功能是将②类型的slab放入到当前cpu,如果当前cpu->partial
先查看一下cpu_slab->partial->page->pobjects,其表示cpu partial链表中所有slab可分配obj的总数(第一个slab page的pobjects和pages分别表示链表中空闲object总数和slab总数)。当释放一个obj给full slab(已经耗尽的slab)时,该slab会添加到cpu partial链表,因此,每次添加的时候都会判断当前的pobjects是否大于kmem_cache->cpu_partial阈值(用于限制CPU slab的空闲object数量),如果大于,会将cpu_slab->partial链表中所有的slab移送到node->partial,然后再将刚刚释放obj的slab插入到cpu_slab->partial链表。如果不大于,则更新pobjects和pages成员,并将slab插入到cpu_slab->partial链表。

static void put_cpu_partial(struct kmem_cache *s, struct page *page, int drain)
{
#ifdef CONFIG_SLUB_CPU_PARTIAL
	struct page *oldpage;
	int pages;
	int pobjects;

	preempt_disable();
	do {
		pages = 0;
		pobjects = 0;
		oldpage = this_cpu_read(s->cpu_slab->partial);

		if (oldpage) {
			pobjects = oldpage->pobjects;
			pages = oldpage->pages;
			if (drain && pobjects > s->cpu_partial) {
				unsigned long flags;
				/*
				 * partial array is full. Move the existing
				 * set to the per node partial list.
				 */
				local_irq_save(flags);
				unfreeze_partials(s, this_cpu_ptr(s->cpu_slab));
				local_irq_restore(flags);
				oldpage = NULL;
				pobjects = 0;
				pages = 0;
				stat(s, CPU_PARTIAL_DRAIN);
			}
		}

		pages++;
		pobjects += page->objects - page->inuse;

		page->pages = pages; 第一个slab的page的pages表示整个partial链表的slab数量
		page->pobjects = pobjects;第一个slab的page的pobjects表示整个partial链表的空闲object数量
		page->next = oldpage;将传入slab加入到当前CPU partial链表的头部

	} while (this_cpu_cmpxchg(s->cpu_slab->partial, oldpage, page)
								!= oldpage);
	if (unlikely(!s->cpu_partial)) {
		unsigned long flags;

		local_irq_save(flags);
		unfreeze_partials(s, this_cpu_ptr(s->cpu_slab));
		local_irq_restore(flags);
	}
	preempt_enable();
#endif
}

·get_freelist:主要是就将cpu_slab->page->freelist返回,然后cpu_slab->page->freelist置位NULL,更新counter(含objects、inuse、frozen),如果cpu_slab->page->freelist给了cpu_slab->freelist就认为inuse为全部在用状态,如果部分在page->freelist则inuse就需要减掉这部分;

static inline void *get_freelist(struct kmem_cache *s, struct page *page)
{
	struct page new;
	unsigned long counters;
	void *freelist;

	do {
		freelist = page->freelist;
		counters = page->counters;
		
struct page new没有初始化,这里只有new.counter=counters,下面怎么就new.inuse -= cnt?
原因是struct page的counter是一个union,包含了inuse(当前slab正在使用的object数量)、
objects(表示slab中所有object的数量,allocate_slab赋初值,在check_slab会判断page->objects
是否超过该slab能容纳的最大object)、frozen(slab是否冻结)。
	
		new.counters = counters;
		VM_BUG_ON(!new.frozen);

		new.inuse = page->objects;
		new.frozen = freelist != NULL;

	} while (!__cmpxchg_double_slab(s, page,
		freelist, counters,
		NULL, new.counters,
		"get_freelist"));  标准操作freelist=page->freelist; page->freelist = NULL;更新counter;

	return freelist;
}

unfreeze_partials:若cpu_slab->partial的空闲object数量高于cache->cpu_partial时,会将当前cpu_slab的所有slab都解冻并添加到node->partial链表上去。

discard_slab:主要功能是将slab的pages还给buddy。

add_partial/remove_partial/remove_full:主要功能就是从struct page的struct list_head lru的链表中添加删除。


由于本人水平有限,无法保证对slub的理解都准确无误,所以读者发现其中有什么错误,请勿见怪,若方便,可以来信讨论,邮箱ldys2014@foxmail.com。

参考资料

1,http://www.wowotech.net/memory_management/426.html
2,https://www.cnblogs.com/adera/p/11718765.html
3,https://blog.csdn.net/jasonactions/article/details/109332167
4,https://blog.csdn.net/monkeyzh123/article/details/119840691

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值