[linux kernel]slub内存管理分析(5) kfree

这篇具有很好参考价值的文章主要介绍了[linux kernel]slub内存管理分析(5) kfree。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

背景

省流

如果对代码细节不感兴趣,可以直接跳转底部内存释放逻辑总结。

前情回顾

关于slab几个结构体的关系和初始化和内存分配的逻辑请见:

[linux kernel]slub内存管理分析(0) 导读

[linux kernel]slub内存管理分析(1) 结构体

[linux kernel]slub内存管理分析(2) 初始化

[linux kernel]slub内存管理分析(2.5) slab重用

[linux kernel]slub内存管理分析(3) kmalloc

[linux kernel]slub内存管理分析(4) 细节操作以及安全加固

描述方法约定

PS:为了方便描述,这里我们将一个用来切割分配内存的page 称为一个slab page,而struct kmem_cache我们这里称为slab管理结构,它管理的真个slab 体系成为slab cachestruct kmem_cache_node这里就叫node。单个堆块称为object或者堆块内存对象

kfree 操作总览

简介

kfree 是用来回收kmalloc 分配的内存的函数,这里详细分析流程。

kfree之中同样有很多设计cpu抢占、NUMA架构node相关的逻辑,这里不详细分析了,因为我们分析的目的是为了搞内核安全,而写漏洞利用的时候可以通过将程序绑定在一个cpu 来避免涉及这些逻辑的代码发生。

还有一些和Kasan、slub_debug 等检测内存越界等的操作,也不过多分析。

逻辑图预览

释放逻辑

[linux kernel]slub内存管理分析(5) kfree

slab page各个状态转化

[linux kernel]slub内存管理分析(5) kfree

调用栈

  • kfree
    • __free_pages 释放页面(大块)
    • slab_free 释放slab 的内存
      • slab_free_freelist_hook
      • do_slab_free 入口,和cpu_slab快速释放
        • memcg_slab_free_hook memcg相关计数操作
        • __slab_free 慢速释放
          • kfence_freekmem_cache_debugfree_debug_processing 检查
          • 释放逻辑
          • put_cpu_partial
            • unfreeze_partials
          • discard_slab slab为空则销毁该slab page
            • free_slab->__free_slab

详细分析

kfree

kfree总入口:

linux\mm\slub.c : kfree

void kfree(const void *x)
{
	struct page *page;
	void *object = (void *)x;

	trace_kfree(_RET_IP_, x);

	if (unlikely(ZERO_OR_NULL_PTR(x)))
		return;

	page = virt_to_head_page(x);//[1]根据地址找到对应的page结构体
	if (unlikely(!PageSlab(page))) {//[2]如果该page 不是slab,那么就是大块内存了,调用free_page释放
		unsigned int order = compound_order(page);

		BUG_ON(!PageCompound(page));
		kfree_hook(object);
		mod_lruvec_page_state(page, NR_SLAB_UNRECLAIMABLE_B,
				      -(PAGE_SIZE << order));
		__free_pages(page, order);
		return;
	}
	slab_free(page->slab_cache, page, object, NULL, 1, _RET_IP_);//[3]释放slab 分配的内存块
}

[1] 调用kfree 的都是要释放的内存地址,所以首先要根据内存虚拟地址找到它所在页的页结构体。

[2] 判断该页是不是slab page,如果不是,说明这个要释放的内存不是slab切割分配的,而是一个大内存块,当初是直接分配的页,所以这里页调用__free_pages来释放内存页。

[3] 否则说明这个内存块是slab page分配的内存块,进入slab 的释放流程slab_free。参数是通过page 获得page 的slab管理结构体kmem_cache

slab_free

释放slab 内存的入口slab_free

linux\mm\slub.c : slab_free

static __always_inline void slab_free(struct kmem_cache *s, struct page *page,
				      void *head, void *tail, int cnt,
				      unsigned long addr)
{
	if (slab_free_freelist_hook(s, &head, &tail))
		do_slab_free(s, page, head, tail, cnt, addr);//[1]调用do_slab_free
}

[1] slab_free_freelist_hook 是些kasan 的东西没啥用,这里直接调用了do_slab_free,传入参数有headtail,由于slab_free设计上可以释放多个内存块,所以这里headtail 和释放多个内存块相关,如果只释放一个的话,不影响。

linux\mm\slub.c : do_slab_free

static __always_inline void do_slab_free(struct kmem_cache *s,
				struct page *page, void *head, void *tail,
				int cnt, unsigned long addr)
{
	void *tail_obj = tail ? : head;//[1]获得尾部,这里tail 传入是0, 所以tail_obj 就是head
	struct kmem_cache_cpu *c;
	unsigned long tid;

	memcg_slab_free_hook(s, &head, 1);//[2]memcg 相关
redo:
	/*
	 * Determine the currently cpus per cpu slab.
	 * The cpu may change afterward. However that does not matter since
	 * data is retrieved via this pointer. If we are on the same cpu
	 * during the cmpxchg then the free will succeed.
	 */
	do {//[3]获取cpu_slab
		tid = this_cpu_read(s->cpu_slab->tid);
		c = raw_cpu_ptr(s->cpu_slab);
	} while (IS_ENABLED(CONFIG_PREEMPTION) &&
		 unlikely(tid != READ_ONCE(c->tid)));

	/* Same with comment on barrier() in slab_alloc_node() */
	barrier();//同步一下

	if (likely(page == c->page)) {//[4]释放的对象正好属于当前cpu_slab正在使用的slab则快速释放
		void **freelist = READ_ONCE(c->freelist);//[4.1]获取freelist

		set_freepointer(s, tail_obj, freelist);//[4.2]将新释放内存块插入freelist 开头

		if (unlikely(!this_cpu_cmpxchg_double(//[4.3]this_cpu_cmpxchg_double()原子指令操作存放
				s->cpu_slab->freelist, s->cpu_slab->tid,
				freelist, tid,
				head, next_tid(tid)))) {

			note_cmpxchg_failure("slab_free", s, tid);//失败记录
			goto redo;
		}
		stat(s, FREE_FASTPATH);
	} else
		__slab_free(s, page, head, tail_obj, cnt, addr);//[5]否则说明释放的内存不是当前cpu_slab立马能释放的

}

[1] 获取一下尾部,由于我们传入的tail 指针为0,则获取head指针,也就是要释放的内存地址。换句话说,当只释放一个内存块的时候,head=tail

[2] memcg相关,这里不详细分析。

[3] 跟kmalloc 的时候一样,获取一下当前使用cpu的cpu_slab

[4] 如果要释放的内存块的所在page正好是当前cpu_slab管理的slab,那么可以快速释放。

​ [4.1] 获取cpu_slabfreelist

​ [4.2] 调用set_freepointer将要释放的内存块放到freelist 链表的开头,在前一章分析过set_freepointer了。

​ [4.3] 跟kmalloc时候相同的原子操作,这里等价于,也就是更新一下cpu_slab->freelist

freelist = s->cpu_slab->freelist;
s->cpu_slab->freelist=head;
tid=s->cpu_slab->tid;
s->cpu_slab->tid=next_tid(tid)

[5] 到这里说明释放的内存不是当前cpu_slab立马能释放的,那么调用__slab_free慢速分配。

__slab_free

linux\mm\slub.c : __slab_free

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 flags;

	stat(s, FREE_SLOWPATH);

	if (kfence_free(head))
		return;

	if (kmem_cache_debug(s) && //这两个里面就是各种检测,实际没有对free环节有影响的操作
	    !free_debug_processing(s, page, head, tail, cnt, addr))
		return; 

	do {//[1] 尝试释放内存块到所属page的freelist
		if (unlikely(n)) {//循环了多次才会走到这,加锁操作
			spin_unlock_irqrestore(&n->list_lock, flags);
			n = NULL;
		}
		prior = page->freelist;//[1.1]获取当前page的freelist
		counters = page->counters;//获取当前page的counters,这是一个联合体,包括inuse、frozen等
		set_freepointer(s, tail, prior);//将freelist 接到要释放内存块的后面*tail=prior
		new.counters = counters;
		was_frozen = new.frozen;
		new.inuse -= cnt;//正在使用内存块减掉要释放的数量
		if ((!new.inuse || !prior) && !was_frozen) {//[2]释放之后全空或释放之前为满并且不在cpu_slab中
            //释放前为满有2种情况:游离中,在cpu_slab->page中,但走到这里确定不在cpu_slab中
            //调试模式时可能在node->full中,默认不开启,不考虑

			if (kmem_cache_has_cpu_partial(s) && !prior) {//[2.1]释放前为满(不管你释放后空不空)
                //并且开启了cpu_slab->partial
				//走到这可以确定slab page为游离状态
				/*
				 * Slab was on no list before and will be
				 * partially empty
				 * We can defer the list move and instead
				 * freeze it.
				 */
				new.frozen = 1;//准备放入cpu_slab中

			} else { /* Needs to be taken off a list *///[2.2]释放之后就为空slab了

				n = get_node(s, page_to_nid(page));//获取page所在node
				/*
				 * 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, //[1.2]成功之前一直尝试
		prior, counters,
		head, new.counters,
		"__slab_free"));
    /* if (page->freelist == prior && page->counters == counters)
     * page->freelist = head
     * page->counters = new.counters 完成释放
     */

	if (likely(!n)) {//[3]n为空,说明这里释放之前为满,或释放之前之后都半满

		if (likely(was_frozen)) {//[3.1]原本就在cpu_slab中,啥也不用操作
			/* 走到这里说明该slab page在当前cpu_slab->partial中或其他cpu_slab的page或partial中
			直接释放到page->freelist即可(已经释放完毕),其他cpu我们管不到
			 * The list lock was not taken therefore no list
			 * activity can be necessary.
			 */
			stat(s, FREE_FROZEN);
		} else if (new.frozen) {//[3.2]释放之前是满的也就是游离状态,放入cpu_slab->partial中
			/*
			 * If we just froze the page then put it onto the
			 * per cpu partial list.
			 */
			put_cpu_partial(s, page, 1);//放到cpu_slab->partial中,这里有一个bug,他没有考虑node->full的情况
			stat(s, CPU_PARTIAL_FREE);
		}

		return;//[3.3]释放之前在node->partial中,并且释放之后也不为空,直接返回啥也不用干
	}

	if (unlikely(!new.inuse && n->nr_partial >= s->min_partial))//[4]释放之后为空 node 中的partial 满足最小要求
		goto slab_empty;//去释放这个page

	/*
	 * Objects left in the slab. If it was not on the partial list before
	 * then add it.
	 */
	if (!kmem_cache_has_cpu_partial(s) && unlikely(!prior)) {//[5]释放前为full,但没开启cpu_slab->partial
		remove_full(s, n, page);//从full 中移除,如果是游离状态不在链表中会直接返回
		add_partial(n, page, DEACTIVATE_TO_TAIL); //添加到partial中
		stat(s, FREE_ADD_PARTIAL);
	}
	spin_unlock_irqrestore(&n->list_lock, flags);
	return;

slab_empty:
	if (prior) {//[4.1]释放之前不是full,在partial中
		/*
		 * Slab on the partial list.
		 */
		remove_partial(n, page);//从partial 删除
		stat(s, FREE_REMOVE_PARTIAL);
	} else {//[4.2]释放之前在full 中
		/* Slab must be on the full list */
		remove_full(s, n, page);//从full 中删除
	}

	spin_unlock_irqrestore(&n->list_lock, flags);
	stat(s, FREE_SLAB);
	discard_slab(s, page);//[4]slab为空,则直接删除整个空slab
}

[1] 尝试释放内存块到所属pagefreelist,由于涉及到锁的操作/原子操作,这里会一直循环尝试直到成功为止

​ [1.1] 获取一下释放堆块之前pagefreelistcounters 等信息,counters是一个联合体,同时包括pagefrozen(代表page 是在cpu_slab中还是在node中)、inuse(代表正在使用的内存数量)等。然后将page->freelist 拼到准备释放的内存块后面,也就是新释放的放到freelist表头,但目前还没更新到page,然后计算新inuse等。

​ [1.2] 进行原子操作尝试完成分配,cmpxchg_double_slab 函数完成的操作等价于如下操作,即将新freelist 头更新到page中,再更新counters

if (page->freelist == prior && page->counters == counters)
{
     page->freelist = head;
     page->counters = new.counters;
}

目前已经将堆块释放到page->freelist中了,接下来要对page的种类进行判断。目前一共有6种(有一种是DEBUG场景)可能:

  • 在当前cpu_slab->partial
  • cpu_slab->page
  • 在其他cpu_slab->partial
  • 游离状态(分配满了),不在任何列表之中
  • 在所属nodepartial列表中
  • 如果开启CONFIG_SLUB_DEBUG,则可能会是分配满的状态而在所属node的full列表中,默认不开启

[2] !new.inuse代表释放之后已经没有inuse的内存了,说明释放之后该slab里面全是free 的堆块,也就是释放后整个page都空了;!prior说明释放之前没有freelist,即释放之前该slab 为分配满的slab page;!was_frozen说明释放之前该slab不在cpu_slab之中。也就是说,当该slab 不在是cpu_slab正在使用的slab 时,如果释放后为空slab或释放前为full slab,则说明游离状态、node->full中或node->partial中的page满足该分支条件

​ [2.1] 释放前为full slab,对应游离状态和node->full的slab pagekmem_cache_has_cpu_partial判断是否开启了cpu_slab->partial;若开启则new.frozen设置为1,打算将该slab 放入cpu_slab->partial中,因为释放后就变成了半满状态了,可以使用了。这里为什么放入cpu_slabpartial而不是放入node的partial,是因为cpu_slab永远都是最近在使用的slab。

​ [2.2] 接下来就是slab page在node->partial中且释放之后就为空slab page的情况,获取一下page 所属node。方便后续操作。

[3] n为空,说明之前没有获取node,也就是说不是释放之后就为空slab。满足这个分支的有三种cpu_slab中的情况和游离状态的slab page情况

​ [3.1] 原本就在cpu_slab中(的三种情况),什么也不用改变,因为我们已经将堆块释放到page->freelist中了,如果在本cpu_slab->partial 无论释放完是否为空都无关紧要,因为不需要释放page;其次在其他cpu_slab中的话,不管是在page还是在partial中我们都是无权干涉的,直接释放到page->freelist然后返回即可。

​ [3.2] 走到这里说明**page是申请满的游离状态或node->full中**,则释放后为可用状态,将其放入cpu_slab->partial中然后返回(put_cpu_partial函数逻辑不止于此,下面会详细分析)。这个地方有一个问题就是,他没有考虑如果开启了CONFIG_SLUB_DEBUG的情况下,满的page可能会存在于node->full列表中,如果补充node->full双向链表中把page删掉直接加入到cpu_slab->partial中会破坏node->full双链表,后续插入操作可能造成unlink崩溃,由于默认不开启该CONFIG,再加上page进入node->full需要触发强制下架的时候cpu_slab->page必须是满的,概率非常低,用户也不可控,一般不影响。不过内核最新版已经重写了开启CONFIG_SLUB_DEBUG的逻辑,不存在这样的问题了,我这里的代码是5.13 版本,不影响其他逻辑。

​ [3.3] 最后说明释放之前在node->partial中,并且释放之后也不为空,那么还继续呆在node->partial中就行,直接啥也不用干返回。

[4] 走到这里说明slab page在node->partial中(小概率在node->full中),且释放之后为空且node 中当前partial slab数量比slab 规定的最小partial数量多,可以释放空的slab page,则进入空slab处理流程:

​ [4.1] 释放之前的状态是半满,则调用remove_partialnode->partial 列表中删除,然后调用discard_slab释放该空slab page。

​ [4.2] 释放之前在node->full中,则调用remove_fullfull列表中删除,之所以会有释放之后直接从full 变为空,我猜是考虑到这个函数可以一次释放多个堆块的原因。还有一点就是,正常full状态的page在上面就会加入到cpu_slab->partial或者node->partial中了,无论释放完是否为空,所以是走不到这个分支的,可见这个版本的代码对CONFIG_SLUB_DEBUG的处理还是比较粗糙的,目前已经在后续版本都会更新完善。

[5] 否则说明**cpu_slab没开启partial列表,游离状态的slab page释放后仍需要添加到一个列表中**,则将其加入node->partial 中。

put_cpu_partial

put_cpu_partial 函数不止是将slab page 放到cpu_slab->partial链表中,如果cpu_slab->partial链表中的页面数量已经达到最大值,则还会进行洗牌操作:

mm\slub.c : put_cpu_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; //[1] 代表这个slab 后面大概还有多少个page
	int pobjects;//代表这个slab 后面还有多少个object 可用

	preempt_disable();
	do {
		pages = 0;
		pobjects = 0;
		oldpage = this_cpu_read(s->cpu_slab->partial);//获取cpu->partial列表头部的那个

		if (oldpage) {//[1] 获取之前的pages 和pobjects 值用来之后更新
			pobjects = oldpage->pobjects;
			pages = oldpage->pages;
			if (drain && pobjects > slub_cpu_partial(s)) {//[2] 当前cpu_slab->partial中的页数量已经达到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));//把cpu_slab->partial中现存的slab page都放到node->partial中
				local_irq_restore(flags);
				oldpage = NULL;
				pobjects = 0;//cpu_slab->partial数量清0
				pages = 0;
				stat(s, CPU_PARTIAL_DRAIN);
			}
		}

		pages++;//[3]更新pages和pobjects数量
		pobjects += page->objects - page->inuse;

		page->pages = pages; //设置当前slab 的pages 和pobjects
		page->pobjects = pobjects;
		page->next = oldpage;//加入链表头部

	} while (this_cpu_cmpxchg(s->cpu_slab->partial, oldpage, page)//[4] 最后将当前页放入cpu_slab->partial中
								!= oldpage);
	if (unlikely(!slub_cpu_partial(s))) {
		unsigned long flags;

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

[1] cpu_slab->partial 中每个页面中的pobjectspages 都代表这个页以及后面的单链表中有多少内存块数和页数,这样我们只访问第一个页即可了解全部信息。

[2] 如果cpu_slab->partial中的页数已经达到了slab 规定的cpu_partial 最大值,则要进行unfreeze_partial操作。下面分析该函数。主要逻辑就是将cpu_slab->partial中的slab page都放到node->partial中,然后更新统计信息,当前cpu_slab->partial为空。

[3] 将该slab page加入到cpu_slab->partial链表头部。

[4] 最后将整个链表更新到cpu_slab->partial

unfreeze_partials

unfreeze_partials函数主要是在cpu_slab->partial中slab page数量已经达到最大值的时候将cpu_slab->partial中的slab page都转移到node->partial中,如果遇到空的slab page,则会释放掉。

mm\slub.c : unfreeze_partials

static void unfreeze_partials(struct kmem_cache *s,
		struct kmem_cache_cpu *c)
{
#ifdef CONFIG_SLUB_CPU_PARTIAL
	struct kmem_cache_node *n = NULL, *n2 = NULL;
	struct page *page, *discard_page = NULL;

	while ((page = slub_percpu_partial(c))) {//[1] 遍历cpu_slab->partial中的page
		struct page new;
		struct page old;

		slub_set_percpu_partial(c, page);//取出头部的,更新新头部

		n2 = get_node(s, page_to_nid(page));//获取page所在node
		if (n != n2) {
			if (n)
				spin_unlock(&n->list_lock);

			n = n2;
			spin_lock(&n->list_lock);
		}

		do {

			old.freelist = page->freelist;
			old.counters = page->counters;//[2] 获取page 的基本信息联合体
			VM_BUG_ON(!old.frozen);

			new.counters = old.counters;
			new.freelist = old.freelist;

			new.frozen = 0;//要从cpu_slab->partial中拿出来,肯定要解冻

		} while (!__cmpxchg_double_slab(s, page,
				old.freelist, old.counters,//更新page 信息
				new.freelist, new.counters,
				"unfreezing slab"));

		if (unlikely(!new.inuse && n->nr_partial >= s->min_partial)) {//[3] 如果page 为空,并且node->partial数量满足最小要求
			page->next = discard_page;//把page加入到要释放的page列表
			discard_page = page;
		} else {//[3.1] 否则就加入到node的的partial中
			add_partial(n, page, DEACTIVATE_TO_TAIL);
			stat(s, FREE_ADD_PARTIAL);
		}
	}

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

	while (discard_page) {//[4] 如果释放page 列表不为空
		page = discard_page;
		discard_page = discard_page->next;

		stat(s, DEACTIVATE_EMPTY);
		discard_slab(s, page);//依次调用discard_slab 释放掉slab page
		stat(s, FREE_SLAB);
	}
#endif	/* CONFIG_SLUB_CPU_PARTIAL */
}

[1] 遍历cpu_slab->partial 中的每一个slab page,并从链表中依次取出

[2] 获取page 的基本信息,然后更新基本信息,这里是将frozen 设为0,也就是解冻,从cpu_slab中移除

[3] 如果slab page的inuse 为0,说明该slab page已经为空,并且如果node->partial页满足最小数量要求,则将其加入到待释放的slab page的列表

​ [3.1] 否则将其加入到node->partial

[4] 将所有空的可以释放的page调用discard_slab释放掉

discard_slab->free_slab

linux\mm\slub.c : discard_slab

static void discard_slab(struct kmem_cache *s, struct page *page)
{
	dec_slabs_node(s, page_to_nid(page), page->objects);//更新node中的slab 和object数量,释放了要减少
	free_slab(s, page);//free_slab 释放slab
}

discard_slab中先调用dec_slabs_node进行一些统计,更新一下slab page被释放后node中的slab page数量和堆块数量。

然后调用free_slab 进行正式的slab page释放:

linux\mm\slub.c : free_slab

static void free_slab(struct kmem_cache *s, struct page *page)
{
	if (unlikely(s->flags & SLAB_TYPESAFE_BY_RCU)) {//RCU延迟释放
		call_rcu(&page->rcu_head, rcu_free_slab);
	} else
		__free_slab(s, page);
}

如果开启了SLAB_TYPESAFE_BY_RCU 则使用RCU延迟释放slab page,不太关心,默认不开启。正常流程调用__free_slab释放slab page:

linux\mm\slub.c : __free_slab

static void __free_slab(struct kmem_cache *s, struct page *page)
{
	int order = compound_order(page); //获取slab所属page阶数
	int pages = 1 << order; //page 页数

	if (kmem_cache_debug_flags(s, SLAB_CONSISTENCY_CHECKS)) {//如果开启了debug,则进行一些检测
		void *p;

		slab_pad_check(s, page);//slab 检测
		for_each_object(p, s, page_address(page),
						page->objects)
			check_object(s, page, p, SLUB_RED_INACTIVE);//slab中的object 检测
	}

	__ClearPageSlabPfmemalloc(page);//这两个都是修改page->flags的宏,清楚slab相关标志位
	__ClearPageSlab(page);
	/* In union with page->mapping where page allocator expects NULL */
	page->slab_cache = NULL;//取消page指向slab 的指针
	if (current->reclaim_state)
		current->reclaim_state->reclaimed_slab += pages;//更新数据
	unaccount_slab_page(page, order, s);//如果开启了memcg相关会进行memcg去注册
	__free_pages(page, order); //释放掉page
}

逻辑很简单,因为在之前已经将该slab page从node中unlink 出来了,那么接下来只需要释放该page即可。在__free_slab 中进行一些常规的检查和统计更新,最后调用__free_pages将页面释放。

特殊slab的释放

特殊slab使用kmem_cache_free函数来释放堆块:

static void file_free_rcu(struct rcu_head *head)
{
	struct file *f = container_of(head, struct file, f_u.fu_rcuhead);

	put_cred(f->f_cred);
	kmem_cache_free(filp_cachep, f);//专属slab的释放函数
}

kmem_cache_free 虽然也是直接封装的slab_free ,但释放前会使用cache_from_obj函数校验一下,避免其他堆块的释放到错误的slab cache中:

void kmem_cache_free(struct kmem_cache *s, void *x)
{
	s = cache_from_obj(s, x);//先校验一下是不是这个slab的堆块
	if (!s)
		return;
    //调用slab_free正常free掉x
	slab_free(s, virt_to_head_page(x), x, NULL, 1, _RET_IP_);
	trace_kmem_cache_free(_RET_IP_, x, s->name);
}

校验逻辑无非就是根据堆块找page,根据page找slab_cache,不多赘述了。

内存释放逻辑总结

[linux kernel]slub内存管理分析(5) kfree

内存对象释放主要思路就是,如果是页面对象,则直接伙伴系统释放,如果是slab 对象,如果在cpu_slab中,在cpu_slab中释放,如果不是,则根据释放之前之后的状态(为空、为满、半满),进行不同的操作。

  • 首先找到释放的内存对象所在的page 的page结构体。如果该page不是slab,也就是说内存对象不是通过slab分配的,而是直接分配的页(大块内存),那么直接释放页。

    • 大块内存分配的时候就是从伙伴系统直接分配的页,释放的时候页通过伙伴系统释放页面(__free_pages)
  • 其他内存是通过slab分配,则通过slab释放(slab_free)

    • memcg相关处理(memcg_slab_free_hook)

    • 获取当前cpu_slab,如果要释放的内存对象正好属于当前cpu_slab(可以理解为是否是从当前cpu_slab分配的),则快速释放

      • 获取cpu_slabfreelist,将该内存对象插入freelist头部,刷新cpu_slab相关信息(do_slab_free)
    • 如果要释放的内存对象不属于当前cpu_slab,(当前slab page在cpu_slab->partial、别的cpu_slab->page、别的cpu_slab->partial、游离状态、node->partialnode->full6种情况),需要慢速释放(__slab_free)

      • 先把内存对象释放到slab page的freelist头部,更新slab page相关统计信息
      • 如果该slab page为冻结状态(说明是在cpu_slab中的三种情况)
        • 则直接结束(已经将object 放到page->freelist了,剩下的就不用管了)
      • 如果释放前该slab page是满的(freelist为空),则说明page目前是游离状态(不在任何列表中)或node->full
        • 如果开启cpu->partial,则将该slab page放到cpu_slab->partial中(从node->full中移除)
          • 如果cpu_slab->partial满了,则要将当前cpu_slab->partial中的所有slab page放到node->partial中,然后再将新的slab page放到cpu_slab->partial
        • 否则放入node->partial中(从node->full中移除)
      • 如果释放后为空,则说明目前该slab page一定处在node->partial列表中,因为如果在cpu_slab或者游离状态或node->full中不管释放完是否为空,都会在上面的步骤中处理完毕
        • 如果当前slab 管理的partial页面数量满足最小要求,则将该释放后为空的slab page释放掉(__free_pages)
        • 否则不变,继续呆在node->partial
      • 否则说明该page是本来就呆在node->partial中的半满page,并且释放后还是半满,则什么也不操作。

slab page状态转换关系图

结合kmallockfree的逻辑,可以画出slab page的状态转换关系图:

[linux kernel]slub内存管理分析(5) kfree文章来源地址https://www.toymoban.com/news/detail-407581.html

到了这里,关于[linux kernel]slub内存管理分析(5) kfree的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux-0.11 kernel目录进程管理asm.s详解

    该模块和CPU异常处理相关,在代码结构上asm.s和traps.c强相关。 CPU探测到异常时,主要分为两种处理方式,一种是有错误码,另一种是没有错误码,对应的方法就是 error_code 和 no_error_code 。在下面的函数详解中,将主要以两个函数展开。 no_error_code 对于一些异常而言,CPU在出现

    2024年02月07日
    浏览(60)
  • 【Linux0.11代码分析】07 之 kernel execve() 函数 实现原理

    系列文章如下: 系列文章汇总:《【Linux0.11代码分析】之 系列文章链接汇总(全)》 https://blog.csdn.net/Ciellee/article/details/130510069 . 1.《【Linux0.11代码分析】01 之 代码目录分析》 2.《【Linux0.11代码分析】02 之 bootsect.s 启动流程》 3.《

    2024年02月03日
    浏览(48)
  • 遍历slub分配器申请的object(linux3.16)

    我只是把active_obj的数量基本凑齐了,细节还没有去研究,可能有些地方是错的 之前遇到了一个设备跑了一段直接之后内存降低无法回收的问题。当时通过slabinfo看到某些slab池子里面active_objs和num_objs差距很大。slub分配器无法自己回收 (无法回收的原因有种这个说法,如果你

    2024年01月20日
    浏览(40)
  • 基于VSCode的Linux内核调试环境搭建以及start_kernel跟踪分析

    参考ppt的步骤: 准备工作:1-5 ,配置vscode环境:6 ,跟踪分析:7。 目录 1.安装开发工具  2.下载内核源码  3.配置内核选项 4.编译和运行内核 5.制作内存根文件系统 *gdb调试(可跳过)  7.配置VSCode调试Linux内核  7.跟踪分析 下载出现了“Axel -n 20 :太多重定向”的问题,原因是

    2023年04月17日
    浏览(63)
  • Linux内存管理 | 一、内存管理的由来及思想

    1、前言 《中庸》有:“九层之台,起于垒土” 之说,那么对于我们搞技术的人,同样如此! 对于 Linux 内存管理,你可以说没有留意过,但是它存在于我们日常开发的方方面面,你所打开的文件,你所创建的变量,你所运行的程序,无不以此为基础,它可以说是操作系统的

    2024年02月08日
    浏览(37)
  • Linux内存管理--smaps内存

    swaps文件是Linux的proc文件系统提供的查看系统下运行进程内存使用情况的方法,Linux给每个进程都提供了一个这样的文件,学会查看并分析swaps文件有助于定位和解决诸如内存泄漏、性能瓶颈等内存资源相关问题。 了解smaps文件之前,需要先搞清楚Linux内存管理中的虚拟内存(

    2024年02月09日
    浏览(49)
  • Semantic Kernel 入门系列:?Memory内存

    了解的运作原理之后,就可以开始使用Semantic Kernel来制作应用了。 Semantic Kernel将embedding的功能封装到了Memory中,用来存储上下文信息,就好像电脑的内存一样,而LLM就像是CPU一样,我们所需要做的就是从内存中取出相关的信息交给CPU处理就好了。 使用Memory需要注册 embedding

    2023年04月13日
    浏览(42)
  • 【Linux内核】内存管理——内存回收机制

    转载请注明: https://www.cnblogs.com/Ethan-Code/p/16626560.html 前文提到malloc的内存分配方式,malloc申请的是虚拟内存,只有在程序去访问时,才会触发缺页异常进入内核态,在缺页中断函数中建立物理内存映射。 如果物理内存充足,则直接建立页框与页的映射。当物理内存不足时,内

    2023年04月09日
    浏览(51)
  • 系统内存管理:虚拟内存、内存分段与分页、页表缓存TLB以及Linux内存管理

    虚拟内存是一种操作系统提供的机制,用于将每个进程分配的独立的虚拟地址空间映射到实际的物理内存地址空间上。通过使用虚拟内存,操作系统可以有效地解决多个应用程序直接操作物理内存可能引发的冲突问题。 在使用虚拟内存的情况下,每个进程都有自己的独立的虚

    2024年02月11日
    浏览(45)
  • 【管理运筹学】第 7 章 | 图与网络分析(1,图论背景以及基本概念、术语、矩阵表示)

    【管理运筹学】第 7 章 | 图与网络分析(2,最小支撑树问题) 【管理运筹学】第 7 章 | 图与网络分析(3,最短路问题) 【管理运筹学】第 7 章 | 图与网络分析(4,最大流问题) 【管理运筹学】第 7 章 | 图与网络分析(5,最小费用流问题及最小费用最大流问题) 按照正常

    2024年02月09日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包