ARM64内存管理十:slub回收

前沿

往篇回顾

在上一篇中,我们主要分析了申请slub内存的过程,入口函数是kmem_cache_alloc,主要内容如下:

  • kmem_cache刚刚建立,还没有任何对象可供分配,此时只能从伙伴系统分配一个slab;

  • 如果正在使用的slab有free obj,那么就直接分配即可,这种是最简单快捷的;

  • 随着正在使用的slab中obj的一个个分配出去,最终会无obj可分配,此时per cpu partial链表中有可用slab用于分配,那么就会从per cpu partial链表中取下一个slab用于分配obj;

  • 如果per cpu partial链表也为空,此时发现per node partial链表中有可用slab用于分配,那么就会从per node partial链表中取下一个slab用于分配obj;

  • 最后还是不行只能从伙伴系统再申请一个slab;

本篇主要内容

前面分析了Slub分配算法的缓存区创建及对象分配,现继续分配算法的对象回收,入口函数kmem_cache_free

代码分析

kmem_cache_free

void kmem_cache_free(struct kmem_cache *s, void *x)
{
	/* 用于获取回收对象的kmem_cache */
	s = cache_from_obj(s, x);
	if (!s)
		return;
	/* 用于将对象回收 */
	slab_free(s, virt_to_head_page(x), x, _RET_IP_);
	/* 对对象的回收做轨迹跟踪 */
	trace_kmem_cache_free(_RET_IP_, x);
}

slab_free

static __always_inline void slab_free(struct kmem_cache *s,
			struct page *page, void *x, unsigned long addr)
{
	void **object = (void *)x;
	struct kmem_cache_cpu *c;
	unsigned long tid;
	/* 释放处理钩子调用处理,主要是用于去注册kmemleak中的对象? */
	slab_free_hook(s, x);

redo:
	/*
	 * 如果目前执行代码的CPU,要释放的缓冲区所属的CPU不是同一个(通过tid判断);
	 * 则不停循环等待抢占;等待切换到同一个CPU,否则this_cpu_cmpxchg_double会失败;
	 */
	do {
		tid = this_cpu_read(s->cpu_slab->tid);
		c = raw_cpu_ptr(s->cpu_slab);
	} while (IS_ENABLED(CONFIG_PREEMPT) &&
		 unlikely(tid != READ_ONCE(c->tid)));

	/* Same with comment on barrier() in slab_alloc_node() */
	barrier();
	/* 如果当前释放的对象与本地CPU的缓存区相匹配,设置该对象尾随的空闲对象指针数据 */
	if (likely(page == c->page)) {
		set_freepointer(s, object, c->freelist);
		/* 快速归还对象? */
		if (unlikely(!this_cpu_cmpxchg_double(
				s->cpu_slab->freelist, s->cpu_slab->tid,
				c->freelist, tid,
				object, next_tid(tid)))) {
			
			note_cmpxchg_failure("slab_free", s, tid);
			goto redo;
		}
		stat(s, FREE_FASTPATH);
	} else
	/* 否则通过慢速通道释放对象 */
		__slab_free(s, page, x, addr);

}

__slab_free

static void __slab_free(struct kmem_cache *s, struct page *page,
			void *x, unsigned long addr)
{
	void *prior;
	void **object = (void *)x;
	int was_frozen;
	struct page new;
	unsigned long counters;
	struct kmem_cache_node *n = NULL;
	unsigned long uninitialized_var(flags);

	stat(s, FREE_SLOWPATH);
	/* 
	 *判断是否开启调试,如果开启
	 * 1. 通过free_debug_processing进行调试检测
	 * 2. 获取经检验过的合法的kmem_cache_node节点缓冲区管理结构
	*/
	if (kmem_cache_debug(s) &&
		!(n = free_debug_processing(s, page, x, addr, &flags)))
		return;

	do {
		if (unlikely(n)) {
			/* 释放在free_debug_processing中加的锁 */
			spin_unlock_irqrestore(&n->list_lock, flags);
			n = NULL;
		}
		/* 获取缓冲区的信息以及设置对象末尾的空闲对象指针,同时更新缓冲区中对象使用数 */
		prior = page->freelist;
		counters = page->counters;
		set_freepointer(s, object, prior);/* 设置对象末尾空闲对象指针? */
		new.counters = counters;
		was_frozen = new.frozen;
		new.inuse--;
		/* 
		 * 如果缓冲区中被使用的对象为0或者空闲队列为空,且缓冲区未处于冻结态(即缓冲区未处于每CPU对象缓存中):
		 * 该释放的对象是缓冲区中最后一个被使用的对象,对象释放之后的缓冲区是可以被释放回伙伴管理算法的 
		 */
		if ((!new.inuse || !prior) && !was_frozen) {

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

				/*
				 * 每CPU存在partial半满队列同时空闲队列不为空
				 * 那么该缓冲区将会设置frozen标识,用于后期将其放置到每CPU的partial队列中
				 */
				new.frozen = 1;

			} else { /* 该缓冲区将会从链表中移出 */
				/* 获取节点缓冲区管理结构 */
				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,
		object, new.counters,
		"__slab_free"));/* 通过cmpxchg_double_slab()将对象释放 */

	if (likely(!n)) {

		/*
		 * 如果刚冻结该缓冲区,则把该缓冲区put_cpu_partial()挂入到每CPU的partial队列中
		 */
		if (new.frozen && !was_frozen) {
			put_cpu_partial(s, page, 1);
			stat(s, CPU_PARTIAL_FREE);
		}
		/*
		 * 该缓冲区本来就是冻结的
		 */
		if (was_frozen)
			stat(s, FREE_FROZEN);
		return;
	}
	/* 
	 * 如果缓冲区无对象被使用,且节点的半满slab缓冲区数量超过了最小临界点,则该页面将需要被释放掉
	 * 跳转至slab_empty执行缓冲区释放操作 
	 */
	if (unlikely(!new.inuse && n->nr_partial >= s->min_partial))
		goto slab_empty;

	/*
	 * 如果本来缓冲区链表都被使用,释放后处于半满状态
	 * 其将从full链表中remove_full()移出,并add_partial()添加至半满partial队列中
	 */
	if (!kmem_cache_has_cpu_partial(s) && unlikely(!prior)) {
		if (kmem_cache_debug(s))
			remove_full(s, n, page);
		add_partial(n, page, DEACTIVATE_TO_TAIL);
		stat(s, FREE_ADD_PARTIAL);
	}
	/* 释放中断锁并恢复中断环境 */
	spin_unlock_irqrestore(&n->list_lock, flags);
	return;

slab_empty:
	if (prior) {
		/* 如果缓冲区非空,则从partial链表中删除 */
		remove_partial(n, page);
		stat(s, FREE_REMOVE_PARTIAL);
	} else {
		/* Slab must be on the full list */
		remove_full(s, n, page);
	}

	spin_unlock_irqrestore(&n->list_lock, flags);
	stat(s, FREE_SLAB);
	discard_slab(s, page);
}
static noinline struct kmem_cache_node *free_debug_processing(
	struct kmem_cache *s, struct page *page, void *object,
	unsigned long addr, unsigned long *flags)
{
	struct kmem_cache_node *n = get_node(s, page_to_nid(page));

	spin_lock_irqsave(&n->list_lock, *flags);
	slab_lock(page);
	/* 检查slab的kmem_cache与page中的slab信息是否匹配,如果不匹配,可能发生了破坏或者数据不符 */
	if (!check_slab(s, page))
		goto fail;
	/* 检查对象地址的合法性,表示地址确切地为某对象的首地址,而非对象的中间位置 */
	if (!check_valid_pointer(s, page, object)) {
		slab_err(s, page, "Invalid object pointer 0x%p", object);
		goto fail;
	}
	/* on_freelist()检测该对象是否已经被释放,避免造成重复释放置 */
	if (on_freelist(s, page, object)) {
		object_err(s, page, object, "Object already free");
		goto fail;
	}
	/* check_object()主要是根据内存标识SLAB_RED_ZONE及SLAB_POISON的设置,对对象空间进行完整性检测 */
	if (!check_object(s, page, object, SLUB_RED_ACTIVE))
		goto out;
	/* 确保用户传入的kmem_cache与页面所属的kmem_cache类型是匹配的,否则将记录错误日志 */
	if (unlikely(s != page->slab_cache)) {
		if (!PageSlab(page)) {
			slab_err(s, page, "Attempt to free object(0x%p) "
				"outside of slab", object);
		} else if (!page->slab_cache) {
			pr_err("SLUB <none>: no slab for object 0x%p.\n",
			       object);
			dump_stack();
		} else
			object_err(s, page, object,
					"page slab pointer corrupt.");
		goto fail;
	}
	/* 如果设置了SLAB_STORE_USER标识,将记录对象释放的track信息 */
	if (s->flags & SLAB_STORE_USER)
		set_track(s, object, TRACK_FREE, addr);
	/* trace()记录对象的轨迹信息,同时还init_object()将重新初始化对象 */
	trace(s, page, object, 0);
	init_object(s, object, SLUB_RED_INACTIVE);
out:
	slab_unlock(page);
	/*
	 * Keep node_lock to preserve integrity
	 * until the object is actually freed
	 */
	return n;

fail:
	slab_unlock(page);
	spin_unlock_irqrestore(&n->list_lock, *flags);
	slab_fix(s, "Object at 0x%p not freed", object);
	return NULL;
}

discard_slab

static void discard_slab(struct kmem_cache *s, struct page *page)
{
	/* 更新统计信息 */
	dec_slabs_node(s, page_to_nid(page), page->objects);
	/* 释放缓冲区 */
	free_slab(s, page);
}

__free_slab

static void __free_slab(struct kmem_cache *s, struct page *page)
{
	/* 获取页面阶数转而获得释放的页面数 */
	int order = compound_order(page);
	int pages = 1 << order;
	/* 对该slab缓冲区进行一次检测,主要是检测是否有内存破坏以记录相关信息 */
	if (kmem_cache_debug(s)) {
		void *p;

		slab_pad_check(s, page);
		for_each_object(p, s, page_address(page),
						page->objects)
			check_object(s, page, p, SLUB_RED_INACTIVE);
	}
	/* 释放影子内存 */
	kmemcheck_free_shadow(page, compound_order(page));
	/* 修改内存页面的状态 */
	mod_zone_page_state(page_zone(page),
		(s->flags & SLAB_RECLAIM_ACCOUNT) ?
		NR_SLAB_RECLAIMABLE : NR_SLAB_UNRECLAIMABLE,
		-pages);
	/* 清除页面的slab信息 */
	__ClearPageSlabPfmemalloc(page);
	__ClearPageSlab(page);

	page_mapcount_reset(page);
	if (current->reclaim_state)
		current->reclaim_state->reclaimed_slab += pages;
	/* 将内存释放回伙伴系统,在伙伴系统内存释放中再分析 */
	__free_pages(page, order);
	/* 释放memcg中的页面处理 */
	memcg_uncharge_slab(s, order);
}

参考资料

slub释放