Linux内核,slub分配流程
我们根据上面的流程图,依次看下slub是如何分配的
首先从kmem_cache_cpu中分配,如果没有则从kmem_cache_cpu的partial链表分配,如果还没有则从kmem_cache_node中分配,如果kmem_cache_node中也没有,则需要向伙伴系统申请内存。
第一步先看看kmem_cache_cpu是如何实现
/** When changing the layout, make sure freelist and tid are still compatible* with this_cpu_cmpxchg_double() alignment requirements.*/
struct kmem_cache_cpu {union {struct {void **freelist; /* Pointer to next available object */unsigned long tid; /* Globally unique transaction id */};freelist_aba_t freelist_tid; /* 将 freelist 和 tid 封装为原子操作单元 */};struct slab *slab; /* 当前用于分配对象的Slab页 */
#ifdef CONFIG_SLUB_CPU_PARTIALstruct slab *partial; /* 部分分配的冻结Slab链表(仅启用CPU Partial时存在) */
#endiflocal_lock_t lock; /* 保护上述字段的本地CPU锁 */
#ifdef CONFIG_SLUB_STATSunsigned stat[NR_SLUB_STAT_ITEMS]; /* Slab分配统计信息 */
#endif
};
接下来,我们可以详细拆解一下这段代码:
联合体(Union)中的 freelist 和 tid
union {struct {void **freelist; // 指向当前Slab中下一个可用对象的指针unsigned long tid; // 全局唯一的事务ID(Transaction ID)};freelist_aba_t freelist_tid; // 将两者封装为一个原子操作单元
};
目的:
无锁快速路径:在SLUB分配器中,对象的分配和释放通常通过无锁操作(如this_cpu_cmpxchg_double())实现,以规避传统锁的性能开销。
ABA问题防御:tid(事务ID)用于防止ABA问题。每次修改freelist时,tid会递增,确保即使freelist的值在并发操作中“看似未变”(如A→B→A),其tid也已变化,使得原子操作能检测到状态不一致。
联合体的作用:
freelist_tid(类型通常为u64或类似)将freelist和tid在内存中紧密打包,确保它们占据连续且对齐的内存空间,满足双字原子操作(如cmpxchg_double)的硬件对齐要求。
例如,在64位系统中,freelist(8字节)和tid(8字节)组合为一个16字节的单元,对齐到16字节边界,从而允许通过单条指令原子地比较和交换这两个字段。## 对齐要求:
this_cpu_cmpxchg_double()需要操作的两个字段必须满足:
a. 在内存中连续。
b. 对齐到双字(例如,16字节对齐)。
联合体强制freelist和tid共享同一内存区域,确保它们的布局符合上述条件。
slab指针
struct slab *slab; // 当前活跃的Slab页,用于快速分配对象
作用:指向当前CPU正在使用的Slab页,其中包含可分配的对象。
性能优化:通过本地化访问减少NUMA或缓存一致性开销。
partial 指针(条件编译)
#ifdef CONFIG_SLUB_CPU_PARTIAL
struct slab *partial; // 部分空闲的冻结Slab链表
#endif
功能:
当启用CONFIG_SLUB_CPU_PARTIAL时,每个CPU会缓存部分空闲的Slab(称为“冻结”状态),避免频繁向全局链表归还/申请Slab。
在内存压力或特定条件下(如flush_slab),这些Partial Slab会被转移到全局链表(NUMA节点的partial链表)。
local_lock_t lock
local_lock_t lock; // 本地CPU锁,保护kmem_cache_cpu结构中的字段
作用:
在慢速路径(如Slab切换、统计更新)中,防止同一CPU上的不同上下文(如进程与中断)竞争访问kmem_cache_cpu结构。
注意:快速路径(无锁分配/释放)不依赖此锁,仅在慢速路径中使用。
stat 统计数组(条件编译)
#ifdef CONFIG_SLUB_STATS
unsigned stat[NR_SLUB_STAT_ITEMS]; // 统计计数器(如分配次数、缓存命中率等)
#endif
功能:在内核启用CONFIG_SLUB_STATS时,记录Slab分配器的运行时性能指标,用于调优和监控。
现在我们来看看kmem_cache_cpu在慢速分配时候是如何工作(___slab_alloc)
/** Slow path. The lockless freelist is empty or we need to perform* debugging duties.** Processing is still very fast if new objects have been freed to the* regular freelist. In that case we simply take over the regular freelist* as the lockless freelist and zap the regular freelist.** If that is not working then we fall back to the partial lists. We take the* first element of the freelist as the object to allocate now and move the* rest of the freelist to the lockless freelist.** And if we were unable to get a new slab from the partial slab lists then* we need to allocate a new slab. This is the slowest path since it involves* a call to the page allocator and the setup of a new slab.** Version of __slab_alloc to use when we know that preemption is* already disabled (which is the case for bulk allocation).*/
static void *___slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node,unsigned long addr, struct kmem_cache_cpu *c, unsigned int orig_size)
{void *freelist;struct slab *slab;unsigned long flags;struct partial_context pc;stat(s, ALLOC_SLOWPATH);reread_slab:slab = READ_ONCE(c->slab);if (!slab) {/** if the node is not online or has no normal memory, just* ignore the node constraint*/if (unlikely(node != NUMA_NO_NODE &&!node_isset(node, slab_nodes)))node = NUMA_NO_NODE;goto new_slab;}
redo:if (unlikely(!node_match(slab, node))) {/** same as above but node_match() being false already* implies node != NUMA_NO_NODE*/if (!node_isset(node, slab_nodes)) {node = NUMA_NO_NODE;} else {stat(s, ALLOC_NODE_MISMATCH);goto deactivate_slab;}}/** By rights, we should be searching for a slab page that was* PFMEMALLOC but right now, we are losing the pfmemalloc* information when the page leaves the per-cpu allocator*/if (unlikely(!pfmemalloc_match(slab, gfpflags)))goto deactivate_slab;/* must check again c->slab in case we got preempted and it changed */local_lock_irqsave(&s->cpu_slab->lock, flags);if (unlikely(slab != c->slab)) {local_unlock_irqrestore(&s->cpu_slab->lock, flags);goto reread_slab;}freelist = c->freelist;if (freelist)goto load_freelist;freelist = get_freelist(s, slab);if (!freelist) {c->slab = NULL;c->tid = next_tid(c->tid);local_unlock_irqrestore(&s->cpu_slab->lock, flags);stat(s, DEACTIVATE_BYPASS);goto new_slab;}stat(s, ALLOC_REFILL);load_freelist:lockdep_assert_held(this_cpu_ptr(&s->cpu_slab->lock));/** freelist is pointing to the list of objects to be used.* slab is pointing to the slab from which the objects are obtained.* That slab must be frozen for per cpu allocations to work.*/VM_BUG_ON(!c->slab->frozen);c->freelist = get_freepointer(s, freelist);c->tid = next_tid(c->tid);local_unlock_irqrestore(&s->cpu_slab->lock, flags);return freelist;deactivate_slab:local_lock_irqsave(&s->cpu_slab->lock, flags);if (slab != c->slab) {local_unlock_irqrestore(&s->cpu_slab->lock, flags);goto reread_slab;}freelist = c->freelist;c->slab = NULL;c->freelist = NULL;c->tid = next_tid(c->tid);local_unlock_irqrestore(&s->cpu_slab->lock, flags);deactivate_slab(s, slab, freelist);new_slab:if (slub_percpu_partial(c)) {local_lock_irqsave(&s->cpu_slab->lock, flags);if (unlikely(c->slab)) {local_unlock_irqrestore(&s->cpu_slab->lock, flags);goto reread_slab;}if (unlikely(!slub_percpu_partial(c))) {local_unlock_irqrestore(&s->cpu_slab->lock, flags);/* we were preempted and partial list got empty */goto new_objects;}slab = c->slab = slub_percpu_partial(c);slub_set_percpu_partial(c, slab);local_unlock_irqrestore(&s->cpu_slab->lock, flags);stat(s, CPU_PARTIAL_ALLOC);goto redo;}new_objects:pc.flags = gfpflags;pc.slab = &slab;pc.orig_size = orig_size;freelist = get_partial(s, node, &pc);if (freelist)goto check_new_slab;slub_put_cpu_ptr(s->cpu_slab);slab = new_slab(s, gfpflags, node);c = slub_get_cpu_ptr(s->cpu_slab);if (unlikely(!slab)) {slab_out_of_memory(s, gfpflags, node);return NULL;}stat(s, ALLOC_SLAB);if (kmem_cache_debug(s)) {freelist = alloc_single_from_new_slab(s, slab, orig_size);if (unlikely(!freelist))goto new_objects;if (s->flags & SLAB_STORE_USER)set_track(s, freelist, TRACK_ALLOC, addr);return freelist;}/** No other reference to the slab yet so we can* muck around with it freely without cmpxchg*/freelist = slab->freelist;slab->freelist = NULL;slab->inuse = slab->objects;slab->frozen = 1;inc_slabs_node(s, slab_nid(slab), slab->objects);check_new_slab:if (kmem_cache_debug(s)) {/** For debug caches here we had to go through* alloc_single_from_partial() so just store the tracking info* and return the object*/if (s->flags & SLAB_STORE_USER)set_track(s, freelist, TRACK_ALLOC, addr);return freelist;}if (unlikely(!pfmemalloc_match(slab, gfpflags))) {/** For !pfmemalloc_match() case we don't load freelist so that* we don't make further mismatched allocations easier.*/deactivate_slab(s, slab, get_freepointer(s, freelist));return freelist;}retry_load_slab:local_lock_irqsave(&s->cpu_slab->lock, flags);if (unlikely(c->slab)) {void *flush_freelist = c->freelist;struct slab *flush_slab = c->slab;c->slab = NULL;c->freelist = NULL;c->tid = next_tid(c->tid);local_unlock_irqrestore(&s->cpu_slab->lock, flags);deactivate_slab(s, flush_slab, flush_freelist);stat(s, CPUSLAB_FLUSH);goto retry_load_slab;}c->slab = slab;goto load_freelist;
}
`___slab_alloc 是 SLUB 分配器的慢速路径函数,当快速路径(无锁分配)失败时,负责处理复杂场景
___slab_alloc
是 Linux 内核 SLUB 分配器中处理慢速路径的核心函数,主要用于以下场景和逻辑:
核心作用
- 处理快速路径失败
当 CPU 本地缓存(kmem_cache_cpu->freelist
)无可用对象时,通过慢速路径获取新对象。 - NUMA 优化
确保内存分配符合请求的 NUMA 节点,减少跨节点访问延迟。 - 调试与隔离
支持调试功能(如内存跟踪、毒化)和隔离 PFMEMALLOC 内存(用于内存回收的专用页)。 - 并发同步
通过锁和事务 ID(tid
)确保多核环境下的数据一致性,避免 ABA 问题。
核心逻辑流程
-
初始化检查
- 读取当前 CPU 的活跃 Slab(
c->slab
),若为空则跳转至新 Slab 分配(new_slab
)。 - 检查 NUMA 节点是否有效,若无效则忽略节点约束。
- 读取当前 CPU 的活跃 Slab(
-
NUMA 与 PFMEMALLOC 匹配
- 若 Slab 的 NUMA 节点与请求不匹配,停用当前 Slab(
deactivate_slab
)。 - 检查 Slab 的 PFMEMALLOC 标志是否与分配标志(
gfpflags
)匹配,不匹配则停用。
- 若 Slab 的 NUMA 节点与请求不匹配,停用当前 Slab(
-
加锁与状态重验
- 获取本地锁(
local_lock_irqsave
),防止同一 CPU 上的进程与中断竞争。 - 二次验证 Slab 是否被其他上下文修改,若已修改则重新读取(
reread_slab
)。
- 获取本地锁(
-
获取空闲链表(Freelist)
- 若本地
freelist
存在可用对象,直接分配。 - 若本地
freelist
为空,尝试从 Slab 页获取新freelist
(get_freelist
)。 - 若获取失败,标记 Slab 失效(
c->slab = NULL
),触发新 Slab 分配。
- 若本地
-
分配新对象
- 从 CPU 的 Partial 链表获取:若启用
CONFIG_SLUB_CPU_PARTIAL
,优先重用部分空闲 Slab。 - 从节点的 Partial 链表获取:通过
get_partial
批量获取部分空闲对象。 - 分配全新 Slab:调用伙伴系统(
new_slab
)分配新页,初始化并冻结 Slab。
- 从 CPU 的 Partial 链表获取:若启用
-
更新状态
- 递增事务 ID(
c->tid
),确保后续快速路径能检测到状态变化。 - 若启用调试,记录内存分配跟踪信息(
set_track
)。
- 递增事务 ID(
-
异常处理
- 若内存不足(
slab_out_of_memory
),触发 OOM 处理。 - 若并发冲突(如锁内发现 Slab 被修改),回滚并重试。
- 若内存不足(
关键设计
-
锁与无锁混合
- 快速路径无锁:通过原子操作(
this_cpu_cmpxchg_double
)实现高效分配。 - 慢速路径加锁:使用本地锁保护
kmem_cache_cpu
结构,避免并发修改。
- 快速路径无锁:通过原子操作(
-
事务 ID(
tid
)防 ABA- 每次修改
freelist
后递增tid
,确保并发操作能检测到状态变化。
- 每次修改
-
Partial 链表优化
- 缓存部分空闲 Slab,减少全局锁争用和内存碎片化。
-
NUMA 感知
- 优先从请求的 NUMA 节点分配,降低跨节点访问开销。
性能影响
- 快速恢复:通过重用 Partial 链表,减少全新 Slab 分配频率。
- 最小化锁范围:仅对关键操作加锁,缩短锁持有时间。
- 统计与调试:通过
stat()
记录性能事件,支持调优和问题排查。
总结
___slab_alloc
是 SLUB 分配器在复杂场景下(如本地缓存耗尽、NUMA 约束、调试需求)实现内存分配的核心逻辑。其通过精细的状态管理、锁优化和资源重用,平衡了性能与可靠性,确保多核系统中内存分配的高效性和正确性。
(参考链接:https://zhuanlan.zhihu.com/p/382056680#/)
相关文章:

Linux内核,slub分配流程
我们根据上面的流程图,依次看下slub是如何分配的 首先从kmem_cache_cpu中分配,如果没有则从kmem_cache_cpu的partial链表分配,如果还没有则从kmem_cache_node中分配,如果kmem_cache_node中也没有,则需要向伙伴系统申请…...

本地部署DeepSeek-R1(Ollama+Docker+OpenWebUI知识库)
安装Ollama 打开 Ollama官网 https://ollama.com/下载安装 Ollama服务默认只允许本机访问,修改允许其它主机访问 OLLAMA_HOST0.0.0.0 ollama serve也可以添加系统环境变量 都知道模型体积很大,顺便也通过环境变量修改模型存放位置,我这…...
Java 实现快速排序算法:一条快速通道,分而治之
大家好,今天我们来聊聊快速排序(QuickSort)算法,这个经典的排序算法被广泛应用于各种需要高效排序的场景。作为一种分治法(Divide and Conquer)算法,快速排序的效率在平均情况下非常高ÿ…...

20250223下载并制作RTX2080Ti显卡的显存的测试工具mats
20250223下载并制作RTX2080Ti显卡的显存的测试工具mats 2025/2/23 23:23 缘起:我使用X99的主板,使用二手的RTX2080Ti显卡【显存22GB版本,准备学习AI的】 但是半年后发现看大码率的视频容易花屏,最初以为是WIN10经常更换显卡/来回更…...

element-ui的组件使用
1. 安装 Element UI(在文件夹最上面输入cmd进入dos窗口,然后输入安装指令 npm install element-ui --save) 2.在main.js文件全局引入(main.js文件负责 全局注册 ),在该文件注册的所有组件在其他文件都能直接调用,一般…...

医疗AI领域中GPU集群训练的关键技术与实践经验探究(上)
医疗AI领域中GPU集群训练的关键技术与实践经验探究(上) 一、引言 1.1 研究背景与意义 在科技飞速发展的当下,医疗 AI 作为人工智能技术与医疗领域深度融合的产物,正引领着医疗行业的深刻变革。近年来,医疗 AI 在疾病诊断、药物研发、健康管理等诸多方面取得了显著进展,…...
详解Redis淘汰策略
引言 Redis 是一个高性能的内存数据库,广泛应用于缓存系统、消息队列等场景。当 Redis 的内存达到限制时,需要根据一定的策略来淘汰数据,以便腾出空间给新数据。本文将深入解析 Redis 的内存淘汰机制,帮助更好地配置 Redis&#…...

HarmonyOS 5.0应用开发——鸿蒙接入高德地图实现POI搜索
【高心星出品】 文章目录 鸿蒙接入高德地图实现POI搜索运行结果:准备地图编写ArkUI布局来加载HTML地图 鸿蒙接入高德地图实现POI搜索 在当今数字化时代,地图应用已成为移动设备中不可或缺的一部分。随着鸿蒙系统的日益普及,如何在鸿蒙应用中…...

nginx关于配置SSL后启动失败原因分析
在配置SSL后,启动./nginx失败,报错提示如下: nginx: [emerg] the "ssl" parameter requires ngx_http_ssl_module in /usr/local/nginx-1.27.4/conf/nginx.conf:36 这个错误提示表在配置nginx启用SSL时,nginx未启用 ng…...

【自学嵌入式(9)ESP8266网络服务器的使用】
ESP8266网络服务器的使用 ESP8266WiFi 库① WiFiClass② WiFiClient③ WiFiServer④ WiFiUDP ESP8266WiFiMulti 库① WiFiMulti ESP8266WebServer 库① ESP8266WebServer 网络服务器实例在浏览器中控制ESP8266指示灯将开发板引脚状态显示在网页中 在之前的文章中,曾…...

危化品经营单位安全管理人员的职责及注意事项
危化品经营单位安全管理人员肩负着保障经营活动安全的重要责任,以下是其主要职责及注意事项: 职责 1. 安全制度建设与执行:负责组织制定本单位安全生产规章制度、操作规程和生产安全事故应急救援预案,确保这些制度符合国家相关法…...
项目实战--网页五子棋(匹配模块)(5)
上期我们实现了websocket后端的大部分代码,这期我们实现具体的匹配逻辑 1. 定义Mather类 我们新建一个Matcher类用来实现匹配逻辑 Component public class Matcher {//每个匹配队列代表不同的段位,这里约定每一千分为一个段位private ArrayList<Queue<User…...

mysql 迁移到人大金仓数据库
我是在windows上安装了客户端工具 运行数据库迁移工具 打开 在浏览器输入http://localhost:54523/ 账号密码都是kingbase 添加mysql源数据库连接 添加人大金仓目标数据库 添加好的两个数据库连接 新建迁移任务 选择数据库 全选 迁移中 如果整体迁移不过去可以单个单个或者几个…...

uniapp 网络请求封装(uni.request 与 uView-Plus)
一、背景 在开发项目中,需要经常与后端服务器进行交互;为了提高开发效率和代码维护性,以及降低重复性代码,便对网络请求进行封装统一管理。 二、创建环境文件 2.1、根目录新建utils文件夹,utils文件夹内新建env.js文…...
计算机网络与通讯知识总结
计算机网络与通讯知识总结 基础知识总结 1)FTP:文件传输 SSH:远程登录 HTTP:网址访问 2)交换机 定义:一种基于MAC地址实现局域网(LAN)内数据高速转发的网络设备,可为接入设备提供独享通信通道。 - 核心功能: 1.数据链路层(OSI第二层)工作,通过MAC地址…...

DPVS-2:单臂负载均衡测试
上一篇编译安装了DPVS,这一篇开启DPVS的负载均衡测试 : 单臂 FULL NAT模式 拓扑-单臂 单臂模式 DPVS 单独物理机 CLINET,和两个RS都是另一个物理机的虚拟机,它们网卡都绑定在一个桥上br0 , 二层互通。 启动DPVS …...

open webui 部署 以及解决,首屏加载缓慢,nginx反向代理访问404,WebSocket后端服务器链接失败等问题
项目地址:GitHub - open-webui/open-webui: User-friendly AI Interface (Supports Ollama, OpenAI API, ...) 选择了docker部署 如果 Ollama 在您的计算机上,请使用以下命令 docker run -d -p 3000:8080 --add-hosthost.docker.internal:host-gatewa…...
交通物联网:概念、历史、现状与展望
交通物联网:概念、历史、现状与展望 李升伟 李昱均 一、概念 交通物联网(Internet of Vehicles, IoV)是物联网(IoT)在交通领域的延伸,旨在通过信息传感设备,实现车、路、人、云之间的全方位连…...
如何实现应用程序与中间件的类进行隔离
以下是一些可以实现类似阿里巴巴 Pandora 功能的框架和工具,这些项目可以帮助你实现类隔离以及中间件和应用的 JAR 包隔离: 1. Pandora Boot Pandora Boot 是阿里巴巴开源的一个基于 Pandora 的轻量级隔离容器,用于管理第三方包,…...
MySQL 数据库基础
1. MySQL 数据库基础 在这一部分,我们将学习 MySQL 的基本概念和常见的数据库操作,帮助你掌握如何创建数据库、表,并进行数据的增、删、改操作。同时,我们还会探讨一些常见的错误示例及其原因,帮助你避免常见的陷阱。…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...

HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...

多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...