SLUB内存管理的4个主要接口函数介绍(4)

article/2025/11/9 11:35:26

slub内存管理的4个主要接口函数如下(参考kernel-4.19):

//slab缓存的创建
struct kmem_cache *kmem_cache_create(const char *name, size_t size, size_t align, unsigned long flags, void (*ctor)(void *));
//slab object的分配
void *kmem_cache_alloc(struct kmem_cache *cachep, int flags);
//slab object的释放
void kmem_cache_free(struct kmem_cache *cachep, void *objp);
//slab缓存的释放
void kmem_cache_destroy(struct kmem_cache *);

本篇主要介绍slab 缓存释放的函数kmem_cache_destroy,相比前面三个函数,destory比较简单

一、函数调用关系图

二、kmem_cache_destroy函数

void kmem_cache_destroy(struct kmem_cache *s)
{int err;
//如果s为NULL,直接退出if (unlikely(!s))return;
//如果使能了CONFIG_MEMCG,先预处理memcg中跟这个kmem_cache相关的一些数据flush_memcg_workqueue(s);//get_online_cpus是对cpu_online_map的加锁,其与末尾的put_online_cpus()是配对使用的;get_online_mems类似,不过是对mem操作get_online_cpus();get_online_mems();//获取slab_mutex互斥锁,用于全局资源保护mutex_lock(&slab_mutex);//kmem_cache的引用计数减1,如果不为0,说明其他kmem_cache别名挂靠,有依赖,不能释放,走out_unlock;否则删除该kmem_caches->refcount--;if (s->refcount)goto out_unlock;//如果引用计数为0,先销毁s名下所属的memcg对应的children kmem cache,如果成功的话err等于零,最后通过shutdown_cache销毁s本身err = shutdown_memcg_caches(s);if (!err)
//调用shutdown_cache销毁s,成功返回0err = shutdown_cache(s);//否则,打印error信息,dump_stack,退出if (err) {pr_err("kmem_cache_destroy %s: Slab cache still has objects\n",s->name);dump_stack();}//销毁kmem_cache *s成功,释放销毁前所加的锁
out_unlock:mutex_unlock(&slab_mutex);put_online_mems();put_online_cpus();
}
EXPORT_SYMBOL(kmem_cache_destroy);

2.1 shutdown_memcg_caches函数

         该函数要销毁s名下所属的memcg对应的children kmem cache,实际上也是调用shutdown_cache来实现的,分别销毁online memory cgroups和offline memory cgroups上面的kmem cache,当然对于还有obj在使用的kmem cache最后会放回到s->memcg_params.children里面,同时返回-EBUSY。此时,kmem_cache_destroy走pr_err和dump_stack流程,不会这个kmem_cache *s给释放。

static int shutdown_memcg_caches(struct kmem_cache *s)
{struct memcg_cache_array *arr;struct kmem_cache *c, *c2;LIST_HEAD(busy);int i;
//通过s->memcg_params.root_cache得出判断BUG_ON(!is_root_cache(s));/** First, shutdown active caches, i.e. caches that belong to online* memory cgroups.*/
//第一步,销毁active cache,如,属于online memory cgroups的caches
//函数rcu_dereference_protected,根据kmemcg ID 子缓存索引表(memcg_caches)得到指向受RCU protected的指针arr,该指针指向将要被释放的kemem_cache,arr = rcu_dereference_protected(s->memcg_params.memcg_caches,lockdep_is_held(&slab_mutex));
//循环遍历这个数组arrfor_each_memcg_cache_index(i) {c = arr->entries[i];if (!c)continue;
//如果shutdown_cache返回非0,表示这个cache上仍然有obj在被使用,将这个cache从原有的list上删除,并添加到临时list(busy)上面,避免下面second会重复遍历if (shutdown_cache(c))/** The cache still has objects. Move it to a temporary* list so as not to try to destroy it for a second* time while iterating over inactive caches below.*/list_move(&c->memcg_params.children_node, &busy);
//如果shutdown_cache返回0,表示这个cache上没有obj在被使用,那么可以直接被销毁
//同时清除memcg_caches 数组中指向它的指针,这样即使root cache还活着,它也永远不会被访问else/** The cache is empty and will be destroyed soon. Clear* the pointer to it in the memcg_caches array so that* it will never be accessed even if the root cache* stays alive.*/arr->entries[i] = NULL;}/** Second, shutdown all caches left from memory cgroups that are now* offline.*/
//第二步,循环遍历所有inactive cache(offline memory cgroups的caches),调用shutdown_cache销毁kmem_cachelist_for_each_entry_safe(c, c2, &s->memcg_params.children,memcg_params.children_node)shutdown_cache(c);
//根据第一步,有个临时list(busy)保存obj仍然在被使用的kmem cache,这里将其放回到s->memcg_params.children list中list_splice(&busy, &s->memcg_params.children);/** A cache being destroyed must be empty. In particular, this means* that all per memcg caches attached to it must be empty too.*/
//如果s名下的all per memcg caches有继续在使用的,那么s->memcg_params.children肯定不为NULL,返回-EBUSY,否则返回0if (!list_empty(&s->memcg_params.children))return -EBUSY;return 0;
}

2.2 shutdown_cache函数

         该函数首先释放被kasan隔离延迟释放的obj,kmem_cache_free->slab_free->slab_free_freelist_hook函数做了隔离,然后就是开始释放kmem cache使用的resource,包括kmem_cache_cpu和kmem_cache_node管理的空间。

static int shutdown_cache(struct kmem_cache *s)
{/* free asan quarantined objects */
//1、释放前面被kasan隔离延迟释放的obj,kmem_cache_free->slab_free->slab_free_freelist_hook函数做了隔离kasan_cache_shutdown(s);//2、释放所有被kmem_cache使用的resource,如果执行成功,返回0;返回1,说明有异常,销毁失败if (__kmem_cache_shutdown(s) != 0)return -EBUSY;memcg_unlink_cache(s);
//将这个kmem_cache从系统全局slab_caches链表中删除list_del(&s->list);//如果有rcu的话,通过slab_caches_to_rcu_destroy_work来释放if (s->flags & SLAB_TYPESAFE_BY_RCU) {
#ifdef SLAB_SUPPORTS_SYSFS
//如果使能了sysfs,从hierarchy中取消链接struct kobjectsysfs_slab_unlink(s);
#endif
//将这个kmem_cache添加到slab_caches_to_rcu_destroy尾部,然后调用slab_caches_to_rcu_destroy_work来释放list_add_tail(&s->list, &slab_caches_to_rcu_destroy);schedule_work(&slab_caches_to_rcu_destroy_work);} else {
#ifdef SLAB_SUPPORTS_SYSFSsysfs_slab_unlink(s);
//如果使能了sysfs,需要将kobject的引用计数减1sysfs_slab_release(s);
#else
//前面释放完后,将s->cpu_slab和s->node置为NULL,如果使能了CONFIG_SLAB_FREELIST_RANDOM,也会将s->random_seq置为NULL,实际上就是释放name占用的资源slab_kmem_cache_release(s);
#endif}return 0;
}

2.2.1 kasan_cache_shutdown函数

//1、释放前面被kasan隔离延迟释放的obj
void kasan_cache_shutdown(struct kmem_cache *cache)
{if (!__kmem_cache_empty(cache))
//如果该kmem_cache的所有node,有一个不为空的,就会执行,确保该kmem_cache中被隔离的obj也要释放quarantine_remove_cache(cache);
}bool __kmem_cache_empty(struct kmem_cache *s)
{int node;struct kmem_cache_node *n;
//遍历s的所有node,如果n->nr_partial不为0或者n->nr_slabs不为0,则返回FALSE,意味着不为emptyfor_each_kmem_cache_node(s, node, n)if (n->nr_partial || slabs_node(s, node))return false;
//否则,返回truereturn true;
}/* Free all quarantined objects belonging to cache. */
void quarantine_remove_cache(struct kmem_cache *cache)
{unsigned long flags, i;struct qlist_head to_free = QLIST_INIT;/** Must be careful to not miss any objects that are being moved from* per-cpu list to the global quarantine in quarantine_put(),* nor objects being freed in quarantine_reduce(). on_each_cpu()* achieves the first goal, while synchronize_srcu() achieves the* second.*/
//隔离的obj放在per-cpu队列和remove_cache_srcu全局队列中,这里on_each_cpu先释放per-cpu队列上的obj(访问per-cpu是原子操作,可以不用上锁)on_each_cpu(per_cpu_remove_cache, cache, 1);//上锁quarantine_lock,因为要访问remove_cache_srcu了,通过qlist_move_cache和qlist_free_all完成这个全局队列的释放,
//然后,通过synchronize_srcu同步这个全局队列信息spin_lock_irqsave(&quarantine_lock, flags);for (i = 0; i < QUARANTINE_BATCHES; i++) {if (qlist_empty(&global_quarantine[i]))continue;qlist_move_cache(&global_quarantine[i], &to_free, cache);/* Scanning whole quarantine can take a while. */spin_unlock_irqrestore(&quarantine_lock, flags);cond_resched();spin_lock_irqsave(&quarantine_lock, flags);}spin_unlock_irqrestore(&quarantine_lock, flags);qlist_free_all(&to_free, cache);synchronize_srcu(&remove_cache_srcu);
}

2.2.2 __kmem_cache_shutdown函数

//2、释放所有被kmem_cache使用的resource
/** Release all resources used by a slab cache.*/
int __kmem_cache_shutdown(struct kmem_cache *s)
{int node;struct kmem_cache_node *n;
//2.1 首先,释放本地CPU的缓存区,即kmem_cache_cpu管理的缓存区空间flush_all(s);//2.2 然后,循环遍历s的所有node,将node partial上管理的空间通过free_partial释放,
//如果n->nr_partial不为0或者n->nr_slabs不为0,则返回1/* Attempt to free all objects */for_each_kmem_cache_node(s, node, n) {free_partial(s, n);if (n->nr_partial || slabs_node(s, node))return 1;}//如果循环遍历s的所有node都为empty,继续对sysfs的slab做移除操作sysfs_slab_remove(s);return 0;
}//2.1 首先,释放本地CPU的缓存区,即kmem_cache_cpu管理的缓存区空间
static void flush_all(struct kmem_cache *s)
{
/*
看似封装了on_each_cpu_cond()函数,实际上该函数并不执行任何与资源释放的操作,
其主要是遍历各个CPU,然后执行作为入参传入的函数has_cpu_slab(),
以判断各个CPU上的资源是否存在,存在将会通过flush_cpu_slab()对该CPU上的资源进行释放处理
*/on_each_cpu_cond(has_cpu_slab, flush_cpu_slab, s, 1, GFP_ATOMIC);
}//主要是用于判断per CPU是否占有缓存区
static bool has_cpu_slab(int cpu, void *info)
{struct kmem_cache *s = info;struct kmem_cache_cpu *c = per_cpu_ptr(s->cpu_slab, cpu);
//slub_percpu_partial(c)得到c->partial,如果c->page或者c->partial不为空,则认为per CPU占有了缓存区,返回truereturn c->page || slub_percpu_partial(c);
}//主要用于将per CPU的缓存区进行释放
static void flush_cpu_slab(void *d)
{struct kmem_cache *s = d;
//smp_processor_id()得到current CPU ID__flush_cpu_slab(s, smp_processor_id());
}/** Flush cpu slab.** Called from IPI handler with interrupts disabled.*/
static inline void __flush_cpu_slab(struct kmem_cache *s, int cpu)
{
//先获取per CPU的kmem_cache_cpustruct kmem_cache_cpu *c = per_cpu_ptr(s->cpu_slab, cpu);if (likely(c)) {
//如果c->page不为空,调用flush_slab释放c->page和c->freelist指向的空间if (c->page)flush_slab(s, c);
//如果c->partial不为空,调用unfreeze_partials释放c->partial管理的空间,
//关于unfreeze_partials函数,在SLUB内存管理的4个主要接口函数介绍(3)中有介绍unfreeze_partials(s, c);}
}//2.2 将node partial上管理的空间通过free_partial释放
/** Attempt to free all partial slabs on a node.* This is called from __kmem_cache_shutdown(). We must take list_lock* because sysfs file might still access partial list after the shutdowning.*/
static void free_partial(struct kmem_cache *s, struct kmem_cache_node *n)
{LIST_HEAD(discard);struct page *page, *h;BUG_ON(irqs_disabled());spin_lock_irq(&n->list_lock);
//遍历访问n->partial上的所有pagelist_for_each_entry_safe(page, h, &n->partial, lru) {
//如果page->inuse等于0,说明这个page上全是空闲obj,remove_partial将其从该node partial中删除
//关于remove_partial函数,在SLUB内存管理的4个主要接口函数介绍(3)中有介绍
//list_add将这个page,添加到discard链表中if (!page->inuse) {remove_partial(n, page);list_add(&page->lru, &discard);
//否则,这个page上是部分空闲,有部分obj还在使用,会报error信息} else {list_slab_objects(s, page,"Objects remaining in %s on __kmem_cache_shutdown()");}}spin_unlock_irq(&n->list_lock);
//遍历这个discard链表上的page,调用discard_slab释放page,还给buddy system
//关于discard_slab函数,在SLUB内存管理的4个主要接口函数介绍(3)中有介绍list_for_each_entry_safe(page, h, &discard, lru)discard_slab(s, page);
}

参考资料

【原创】(十一)Linux内存管理slub分配器

图解slub

mm-slab对象的回收

【Linux内存源码分析】SLUB分配算法(6)

ARM64内存管理十一:slub销毁

内存管理API之kmem_cache_destroy


http://chatgpt.dhexx.cn/article/Uru49cws.shtml

相关文章

Linux内核:内存管理——SLUB分配器

SLUB和SLAB的区别 首先为什么要说slub分配器&#xff0c;内核里小内存分配一共有三种&#xff0c;SLAB/SLUB/SLOB&#xff0c;slub分配器是slab分配器的进化版&#xff0c;而slob是一种精简的小内存分配算法&#xff0c;主要用于嵌入式系统。慢慢的slab分配器或许会被slub取代&…

SLUB的引入及举例说明

我们都知道Buddy分配器是按照页的单位分配的&#xff08;Buddy系统分配器实现&#xff09;&#xff0c;如果我们需要分配几十个字节&#xff0c;几百个字节的时候&#xff0c;就需要用到SLAB分配器。 SLAB分配器专门是针对小内存分配而设计的&#xff0c;比如我们驱动中常见的…

电信光猫改桥接模式

如果只是改桥接 可以试试下面这两个地址&#xff1a;http://192.168.1.1/bridge_route.gchhttp://192.168.1.1:8080/bridge_route.gch 转载于:https://www.cnblogs.com/Devopser/p/11257535.html

电信光猫改桥接还在苦苦破解超级密码吗?

电信光猫路由改桥接&#xff0c;不同的地区有不通的方法。比较幸运的地区和终端&#xff0c;有通用的超级密码。但是不幸的地区&#xff0c;就需要通过破解这个超级密码。我就属于比较不幸的地区&#xff0c;遇到不幸的终端&#xff1a;天翼网关TEWA-708G。然后按照网上大神的破…

获取电信光猫TEWA-600超级管理密码,修改电信光猫为桥接模式

文章转载&#xff1a;玩转盒子 前些年各地运营商响应国家政策&#xff0c;光进铜退小区宽带由网线入户改成光缆入户&#xff0c;这样必须用光猫把光信号转换成电信号。我用的是中国电信的宽带&#xff0c;2017年我们小区也进行了网络改造&#xff0c;网线入户变成了光缆入户。…

友华PT921G光猫破解获取超级密码和更改桥接模式

获取超级密码 1.登陆光猫管理地址192.168.1.1 2.打开新的窗口输入&#xff1a;http://192.168.1.1/romfile.cfg ,就能下载到配置文件 3.用记事本打开romfile.cfg&#xff0c;点击编辑–>查找–>输入telecomadmin->点击查找下一个 4.查找到username“telecomadmin”,而…

为了改桥接,我决定破解中兴F450G V2光猫

还记得我之前买了个猫棒来替换光猫么&#xff1f;用了一个来月&#xff0c;发现这玩意真的不稳定&#xff0c;短则几分钟长则一两天它必定自己重启一次&#xff0c;导致我的网络时不时就会断线。这玩意不好使&#xff0c;我也没有别的光猫&#xff0c;只好找电信装维师傅给我改…

大话设计模式-桥接模式

使用场景&#xff1a;桥接模式的核心意图就是将这些实现独立出来&#xff0c;让它们各自地变化。这就使得每种实现的变化不会影响其他实现&#xff0c;从而达到应对变化的目的。 多用聚合&#xff0c;少用继承 1. 手机软件抽象类、通讯录类、游戏类 package com.hj.designPat…

电信光猫/烽火HG6543c1光猫超级密码获取改桥接模式( 中国电信浙江公司定制天翼网关3.0)

第一步&#xff1a;开telnet 浏览器网址栏输入&#xff1a;192.168.1.1:8080/cgi-bin/telnetenable.cgi?telnetenable1 第二步&#xff1a;使用pytty软件登入光猫 &#xff08;Putty软件下载&#xff1a;https://download.csdn.net/download/qq_34885669/12098009&#xff0…

保姆级-光猫改桥接-路由拨号-openwrt端口转发-阿里云DNS域名解析访问家中设备

准备&#xff1a; 1.公网ip&#xff08;江苏省电信&#xff0c;电话1分钟解决&#xff09; 2.域名(最好备案了) 3.路由器(我的是红米AC2100刷openwrt&#xff0c;重点路由器要有动态dns服务的功能&#xff0c;端口转发功能什么路由器都有) 往期教程 路由器固件刷写 红米AC210…

单臂路由 光猫桥接 一根网线复用

由于弱电箱太小&#xff0c; 二级路由拨号只能放客厅里。 这就导致其他房间无法上网了。 下面是单臂路由设置方法&#xff0c; 光猫桥接的端口下的设备&#xff0c;同样可以上网。 充分利用光猫端口。 前提&#xff1a; 光猫需要超级密码 二级路由固件为 openwrt ​​​​​

光猫桥接模式路由器拨号成功 端口映射失败的原因

路由器拨号成功&#xff0c;光猫必须是桥接模式。 光猫从路由模式设置为桥接模式的操作过程: 在浏览器地址栏中输入光猫的地址192.168.1.1&#xff0c; 进入光猫的登陆界面&#xff0c;输入超级用户名和密码&#xff0c; 用户&#xff1a;CMCCAdmin(或telecomadmin &#xff0…

电信网关改造无线打印服务器,电信天翼网关路由改桥接流程

大家好&#xff0c;好多网友要求我发一份网关路由改桥接的流程&#xff0c;由于平日工作太忙&#xff0c;更新点有慢&#xff0c;以后会多多分享平日工作中碰到的一些关于网络的各种问题。 路由改桥接首先准备以下两样。 1.网关一台(不管是四口还是二口都可以) 2.光猫超级密码&…

DDNS之光猫改桥接

相信有很多小伙伴在折腾远程开机的时候&#xff0c;都遇到了这样一个问题&#xff1a; 在成功实现了局域网唤醒之后&#xff0c;无论怎么设置DDNS、端口映射都没办法实现广域网唤醒。 究竟是什么一个原因导致的呢&#xff1f;亿元程序员今天就给大家讲解其中一个原因&#xff…

电信中兴f452光猫路由改桥接最简单的方式,亲自体验成功。

家里的光猫是中兴f452&#xff0c;默认是路由模式&#xff0c;改为桥接由路由器拨号&#xff0c;过程如下&#xff0c;验证成功。前提是去电信营业厅申请内网IP改公网IP&#xff0c;申请通过后再进行如下操作。 1、网上的方法很多都是用超级用户密码登陆&#xff0c;也就是用户…

宽带开启ipv6的方法(包含光猫改桥接和光猫改桥接后访问其管理界面以及路由器拨号)

本文将介绍移动宽带如何开启ipv6 你为什么需要ipv6&#xff1f; 关于v4 v6地址个数的差别不再赘述&#xff0c;主要介绍对于家庭用户的实际意义 开启ipv6之后&#xff0c;种子下载文件时&#xff0c;可以从只有ipv6的设备获取资源&#xff0c;增加了资源个数&#xff0c;赚钱宝…

光猫桥接后宽带降速问题解决

文章目录 一、背景二、解决方案三、升级款改动四、排查经历 一、背景 光猫桥接使用路由器拨号后宽带降速到原来的一半。 条件&#xff1a; 1、新办的电信宽带2、光猫桥接&#xff0c;路由器拨号3、光猫10Gbps接口&#xff0c;路由器1Gbps接口 现象&#xff1a; 光猫路由器…

路由器连接光猫用桥接模式好还是用路由模式好?路由器桥接模式vs路由模式

许多家庭网络中的上网设备即有光猫又有路由器&#xff0c;那么光猫和路由器在家庭网络中各起什么作用呢&#xff1f;一般如何搭配使用可以满足资源利用最大化呢&#xff1f;怎么样搭配能让网络利用效率最高呢&#xff1f; 下面来解答以上问题&#xff0c;首先说说光猫和路由器…

光猫桥接服务器无响应,解决光猫改为桥接后无法再次访问的问题

换了一个千兆光猫&#xff0c;型号是PT632。最近在研究IPv6&#xff0c;不停的折腾光猫的WAN口连接模式(Route和Bridge)。 大概的设备结构&#xff1a;光猫(PT632)→路由器(网件R8000)→下端设备 发现一个问题&#xff1a; 光猫使用Route模式(路由器模式)时&#xff0c;光猫进行…

光猫改桥接之后连接路由器上网的设备不能访问光猫的解决方法

因为群晖需要外网访问&#xff0c;将自购的路由器改为拨号上网&#xff0c;光猫设置为桥接模式。 但这种情况下&#xff0c;因为路由的网段 (我的是 192.168.3.x) 和光猫的网段 (电信&#xff0c;默认 192.168.1.x) 不同&#xff0c;此时不能通过 192.168.1.1 网址来连接光猫&a…