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

news/2024/5/3 10:38:10/文章来源:https://blog.csdn.net/Breeze_CAT/article/details/130015764

文章目录

    • 背景
      • 省流
      • 前情回顾
      • 描述方法约定
    • kfree 操作总览
      • 简介
      • 逻辑图预览
        • 释放逻辑
        • slab page各个状态转化
      • 调用栈
    • 详细分析
      • kfree
      • slab_free
      • __slab_free
      • put_cpu_partial
        • unfreeze_partials
      • discard_slab->free_slab
    • 内存释放逻辑总结
    • slab page状态转换关系图

背景

省流

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

前情回顾

关于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 等检测内存越界等的操作,也不过多分析。

逻辑图预览

释放逻辑

在这里插入图片描述

slab page各个状态转化

在这里插入图片描述

调用栈

  • 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 就是headstruct 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_slabtid = 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]获取freelistset_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的freelistif (unlikely(n)) {//循环了多次才会走到这,加锁操作spin_unlock_irqrestore(&n->list_lock, flags);n = NULL;}prior = page->freelist;//[1.1]获取当前page的freelistcounters = page->counters;//获取当前page的counters,这是一个联合体,包括inuse、frozen等set_freepointer(s, tail, prior);//将freelist 接到要释放内存块的后面*tail=priornew.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->partialremove_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_PARTIALstruct page *oldpage;int pages; //[1] 代表这个slab 后面大概还有多少个pageint 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数量清0pages = 0;stat(s, CPU_PARTIAL_DRAIN);}}pages++;//[3]更新pages和pobjects数量pobjects += page->objects - page->inuse;page->pages = pages; //设置当前slab 的pages 和pobjectspage->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_PARTIALstruct kmem_cache_node *n = NULL, *n2 = NULL;struct page *page, *discard_page = NULL;while ((page = slub_percpu_partial(c))) {//[1] 遍历cpu_slab->partial中的pagestruct page new;struct page old;slub_set_percpu_partial(c, page);//取出头部的,更新新头部n2 = get_node(s, page_to_nid(page));//获取page所在nodeif (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 pagestat(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 对象,如果在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的状态转换关系图:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_283295.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

21.SSM框架-SpringMVC

目录 一、SpringMVC。 &#xff08;1&#xff09;SpringMVC快速入门。 &#xff08;2&#xff09;SpringMVC的数据响应方式。 &#xff08;1&#xff09;页面跳转。 &#xff08;2&#xff09;回写数据。 &#xff08;3&#xff09;获取请求参数。 &#xff08;4&#xf…

SpringSecurity实战解析

文章目录一、Security认证和原理1、认证基本流程1.1 表单认证概述1.2 基本流程分析1.3 权限访问流程2、请求间共享认证信息2.1 概述2.2 获取认证用户信息3、认证的几种方式4、注解权限4.1 概述4.2 Secured注解使用方式4.3 jsr250Enabled4.4 prePostEnabled 规范(重要)5、自定义…

【数据结构初阶】二叉树OJ题

⭐博客主页&#xff1a;️CS semi主页 ⭐欢迎关注&#xff1a;点赞收藏留言 ⭐系列专栏&#xff1a;数据结构初阶 ⭐代码仓库&#xff1a;Data Structure 家人们更新不易&#xff0c;你们的点赞和关注对我而言十分重要&#xff0c;友友们麻烦多多点赞&#xff0b;关注&#xff…

Flume笔记

Flume 概念 高可用、高可靠&#xff0c;分布式海量日志采集、聚合和传输的系统。 主要作用&#xff1a;实时读取服务器本地磁盘的数据&#xff0c;将数据写入到HDFS 组成 Agent&#xff0c;JVM进程&#xff0c;以事件的形式将数据从源头送到目的地 Agent分为Source、Chann…

李宏毅2021春季机器学习课程视频笔记5-模型训练不起来问题(当梯度很小的时候问题)

求解最小Loss的失败&#xff0c;不能得到最优的值&#xff0c;找不到Loss足够小的值。 1.Loss关于参数的梯度为0&#xff0c;不能继续更新参数。&#xff08;local minima 或者 saddle point&#xff09;如何知道走到了哪个点&#xff1f; 利用泰勒展开&#xff1a; Critical P…

免费ChatGPT接入-国内怎么玩chatGPT

免费ChatGPT中文版 OpenAI 的 GPT 模型目前并不提供中文版的免费使用&#xff0c;但是有许多机器学习平台和第三方服务提供商也提供了基于 GPT 技术的中文版模型和 API。下面是一些常见的免费中文版 ChatGPT&#xff1a; Hugging Face&#xff1a;Hugging Face 是一个开源社区…

Mysql主备一致性保证

大家知道 bin log 既可以用来归档&#xff0c;又可以用来做主备同步。有人可能会问&#xff0c;为什么备库执行了 bin log 就可以跟主库保持一致了呢&#xff1f;bin log的内容是什么样的呢&#xff1f;今天我们就来聊聊它。 在最开始&#xff0c;Mysql 是以容易学习和方便的高…

JDK1.8下载与安装完整教程

目录 一、获取安装资源 1、百度网盘共享 2、官方网站下载(百度网盘文件下载下来有问题情况下) 2.1、搜索jdk官方网站 2.2、进到官网下拉找到Java8&#xff0c;选择Windows 2.3、下载安装程序(下载要登录&#xff0c;没有账号就注册就行) 二、正式安装 1、先在D盘(不在C…

【模型复现】Network in Network,将1*1卷积引入网络设计,运用全局平均池化替代全连接层。模块化设计网络

《Network In Network》是一篇比较老的文章了&#xff08;2014年ICLR的一篇paper&#xff09;&#xff0c;是当时比较厉害的一篇论文&#xff0c;同时在现在看来也是一篇非常经典并且影响深远的论文&#xff0c;后续很多创新都有这篇文章的影子。[1312.4400] Network In Networ…

蓝桥杯刷题冲刺 | 倒计时1天

作者&#xff1a;指针不指南吗 专栏&#xff1a;蓝桥杯倒计时冲刺 &#x1f43e;蓝桥杯加油&#xff0c;大家一定可以&#x1f43e; 文章目录我是菜菜&#xff0c;最近容易我犯的错误总结 一些tips 各位蓝桥杯加油加油 当输入输出数据不超过 1e6 时&#xff0c;scanf printf 和…

elasticsearch基础6——head插件安装和web页面查询操作使用、ik分词器

文章目录一、基本了解1.1 插件分类1.2 插件管理命令二、分析插件2.1 es中的分析插件2.1.1 官方核心分析插件2.1.2 社区提供分析插件2.2 API扩展插件三、Head 插件3.1 安装3.2 web页面使用3.2.1 概览页3.2.1.1 unassigned问题解决3.2.2 索引页3.2.3 数据浏览页3.2.4 基本查询页3…

微服务+springcloud+springcloud alibaba学习笔记(1/9)

1.微服务简介 什么是微服务呢&#xff1f; 就是将一个大的应用&#xff0c;拆分成多个小的模块&#xff0c;每个模块都有自己的功能和职责&#xff0c;每个模块可以 进行交互&#xff0c;这就是微服务 简而言之&#xff0c;微服务架构的风格&#xff0c;就是将单一程序开发成…

项目管理案例分析有哪些?

项目管控中遇到的问题有哪些&#xff1f;这些问题是如何解决的&#xff1f; 在项目管理领域&#xff0c;案例分析是一种常见的方法来学习和理解项目管理实践&#xff0c;下面就来介绍几个成功案例&#xff0c;希望能给大家带来一些参考。 1、第六空间&#xff1a;快速响应个性…

1669_MIT 6.828 xv6代码的获取以及编译启动

全部学习汇总&#xff1a; GreyZhang/g_unix: some basic learning about unix operating system. (github.com) 6.828的学习的资料从开始基本信息的讲解&#xff0c;逐步往unix的一个特殊版本xv6过度了。这样&#xff0c;先得熟悉一下这个OS的基本代码以及环境。 在课程中其实…

最短路径算法及Python实现

最短路径问题 在图论中&#xff0c;最短路径问题是指在一个有向或无向的加权图中找到从一个起点到一个终点的最短路径。这个问题是计算机科学中的一个经典问题&#xff0c;也是许多实际问题的基础&#xff0c;例如路线规划、通信网络设计和交通流量优化等。在这个问题中&#…

Downloader工具配置参数并烧录到flash中

1 Downloader工具介绍 Downloader工具可以用来烧录固件到设备中&#xff0c;固件格式默认为*dcf。该工具还可以用来在线调试EQ或者进行系统设置。 2 配置参数 2.1 作用 当有一个dcf文件时&#xff0c;配合不同的配置文件*.setting&#xff0c;在不进行编译的情况下&#xff…

【毕业设计】ESP32通过MQTT协议连接服务器(二)

文章目录0 前期教程1 前言2 配置SSL证书3 配置用户名和密码4 配置客户端id&#xff08;client_id&#xff09;5 conf文件理解6 websocket配置7 其他资料0 前期教程 【毕业设计】ESP32通过MQTT协议连接服务器&#xff08;一&#xff09; 1 前言 上一篇教程简单讲述了怎么在虚拟…

【调试】ftrace(三)trace-cmd和kernelshark

之前使用ftrace的时候需要一系列的配置&#xff0c;使用起来有点繁琐&#xff0c;这里推荐一个ftrace的一个前端工具&#xff0c;它就是trace-cmd trace-cmd安装教程 安装trace-cmd及其依赖库 git clone https://git.kernel.org/pub/scm/libs/libtrace/libtraceevent.git/ c…

【Ruby学习笔记】19.Ruby 连接 Mysql - MySql2

Ruby 连接 Mysql - MySql2 前面一章节我们介绍了 Ruby DBI 的使用。这章节我们技术 Ruby 连接 Mysql 更高效的驱动 mysql2&#xff0c;目前也推荐使用这种方式连接 MySql。 安装 mysql2 驱动&#xff1a; gem install mysql2你需要使用 –with-mysql-config 配置 mysql_conf…

【DevOps】GitOps 初识(下) - 让DevOps变得更好

实践GitOps的五大难题 上一篇文章中&#xff0c;我们介绍了GitOps能为我们带来许多的好处&#xff0c;然而&#xff0c;任何新的探索都将不会是一帆风顺的。在开始之前&#xff0c;如果能了解实践GitOps通常会遇到的挑战&#xff0c;并对此作出合适的应对&#xff0c;可能会使…