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 分配器中处理慢速路径的核心函数,主要用于以下场景和逻辑:


核心作用

  1. 处理快速路径失败
    当 CPU 本地缓存(kmem_cache_cpu->freelist)无可用对象时,通过慢速路径获取新对象。
  2. NUMA 优化
    确保内存分配符合请求的 NUMA 节点,减少跨节点访问延迟。
  3. 调试与隔离
    支持调试功能(如内存跟踪、毒化)和隔离 PFMEMALLOC 内存(用于内存回收的专用页)。
  4. 并发同步
    通过锁和事务 ID(tid)确保多核环境下的数据一致性,避免 ABA 问题。

核心逻辑流程

  1. 初始化检查

    • 读取当前 CPU 的活跃 Slab(c->slab),若为空则跳转至新 Slab 分配(new_slab)。
    • 检查 NUMA 节点是否有效,若无效则忽略节点约束。
  2. NUMA 与 PFMEMALLOC 匹配

    • 若 Slab 的 NUMA 节点与请求不匹配,停用当前 Slab(deactivate_slab)。
    • 检查 Slab 的 PFMEMALLOC 标志是否与分配标志(gfpflags)匹配,不匹配则停用。
  3. 加锁与状态重验

    • 获取本地锁(local_lock_irqsave),防止同一 CPU 上的进程与中断竞争。
    • 二次验证 Slab 是否被其他上下文修改,若已修改则重新读取(reread_slab)。
  4. 获取空闲链表(Freelist)

    • 若本地 freelist 存在可用对象,直接分配。
    • 若本地 freelist 为空,尝试从 Slab 页获取新 freelistget_freelist)。
    • 若获取失败,标记 Slab 失效(c->slab = NULL),触发新 Slab 分配。
  5. 分配新对象

    • 从 CPU 的 Partial 链表获取:若启用 CONFIG_SLUB_CPU_PARTIAL,优先重用部分空闲 Slab。
    • 从节点的 Partial 链表获取:通过 get_partial 批量获取部分空闲对象。
    • 分配全新 Slab:调用伙伴系统(new_slab)分配新页,初始化并冻结 Slab。
  6. 更新状态

    • 递增事务 ID(c->tid),确保后续快速路径能检测到状态变化。
    • 若启用调试,记录内存分配跟踪信息(set_track)。
  7. 异常处理

    • 若内存不足(slab_out_of_memory),触发 OOM 处理。
    • 若并发冲突(如锁内发现 Slab 被修改),回滚并重试。

关键设计

  1. 锁与无锁混合

    • 快速路径无锁:通过原子操作(this_cpu_cmpxchg_double)实现高效分配。
    • 慢速路径加锁:使用本地锁保护 kmem_cache_cpu 结构,避免并发修改。
  2. 事务 ID(tid)防 ABA

    • 每次修改 freelist 后递增 tid,确保并发操作能检测到状态变化。
  3. Partial 链表优化

    • 缓存部分空闲 Slab,减少全局锁争用和内存碎片化。
  4. NUMA 感知

    • 优先从请求的 NUMA 节点分配,降低跨节点访问开销。

性能影响

  • 快速恢复:通过重用 Partial 链表,减少全新 Slab 分配频率。
  • 最小化锁范围:仅对关键操作加锁,缩短锁持有时间。
  • 统计与调试:通过 stat() 记录性能事件,支持调优和问题排查。

总结

___slab_alloc 是 SLUB 分配器在复杂场景下(如本地缓存耗尽、NUMA 约束、调试需求)实现内存分配的核心逻辑。其通过精细的状态管理、锁优化和资源重用,平衡了性能与可靠性,确保多核系统中内存分配的高效性和正确性。

(参考链接:https://zhuanlan.zhihu.com/p/382056680#/)

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

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

相关文章

使用Windbg调试目标进程排查C++软件异常的一般步骤与要点分享

目录 1、概述 2、将Windbg附加到已经启动起来的目标进程上,或者用Windbg启动目标程序 2.1、将Windbg附加到已经启动起来的目标进程上 2.2、用Windbg启动目标程序 2.3、Windbg关联到目标进程上会中断下来,输入g命令将该中断跳过去 3、分析实例说明 …

51单片机测试题AI作答测试(DeepSeek Kimi)

单片机测试题 DeepSeek Kimi 单项选择题 (10道) 6题8题判断有误 6题判断有误 智谱清言6题靠谱,但仔细斟酌,题目出的貌似有问题,详见 下方。 填空题 (9道) 脉宽调制(Pulse …

模版语法vscode

这里注意&#xff1a;<template></template>里面只能写一个根标签&#xff0c;其他在嵌套&#xff1a; <script > export default {data(){return{tthtml:"<a hrefhttps://itbaizhan.com>百战程序员</a>"}} } </script><tem…

洛谷B3637 最长上升子序

B3637 最长上升子序列 - 洛谷 代码区&#xff1a; #include<bits/stdc.h>using namespace std;int main(){int n;cin >> n;int arry[n],dp[n];for(int i0;i<n;i){cin >>arry[i];dp[i]1;}/*在 i 之前可能存在多个 j 满足 arry[j] < arry[i]&#xff0c…

kotlin 知识点 七 泛型的高级特性

对泛型进行实化 泛型实化这个功能对于绝大多数Java 程序员来讲是非常陌生的&#xff0c;因为Java 中完全没有这个概 念。而如果我们想要深刻地理解泛型实化&#xff0c;就要先解释一下Java 的泛型擦除机制才行。 在JDK 1.5之前&#xff0c;Java 是没有泛型功能的&#xff0c;…

Day 49 卡玛笔记

这是基于代码随想录的每日打卡 1143. 最长公共子序列 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变…

重新求职刷题DAY18

1.513. 找树左下角的值 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节点。 示例 1: 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 输入: root [2,1,3] 输出: 1思路&#xff1a; 这…

因子数个数之和

BDAA实验室的保研机试一道题&#xff0c;有时间限制。 1. 求每个数的因子数再求和&#xff1a;超时 2. 思想转换&#xff1a;统计每个数在 1 到 N 中作为因子出现的次数&#xff0c;从而避免对每个数进行因子分解&#xff0c;将时间复杂度优化到O(N)。&#xff08; 没想到 :( …

『obsidian』obsidian接入DeepSeek模型的完整说明

一、插件概览 &#x1f3c6;Copilot For Obsidian 开发者&#xff1a;loganc yang | 第 5 届 Ob 宝石奖 LLM 类冠军 核心功能&#xff1a;内置多模态大语言模型&#xff0c;支持自定义模型扩展&#xff0c;实现笔记智能交互预置模型&#xff1a;Claude、GPT-4、Gemini 系列特…

基于YOLO11深度学习的苹果叶片病害检测识别系统【python源码+Pyqt5界面+数据集+训练代码】

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…

基于 DeepSeek LLM 本地知识库搭建开源方案(AnythingLLM、Cherry、Ragflow、Dify)认知

写在前面 博文内容涉及 基于 Deepseek LLM 的本地知识库搭建使用 ollama 部署 Deepseek-R1 LLM知识库能力通过 Ragflow、Dify 、AnythingLLM、Cherry 提供理解不足小伙伴帮忙指正 &#x1f603;,生活加油 我站在人潮中央&#xff0c;思考这日日重复的生活。我突然想&#xff0c…

Redis分布式锁故障处理:当Redis不可用时的应对策略

Redis分布式锁故障处理&#xff1a;当Redis不可用时的应对策略 在分布式系统中&#xff0c;Redis因其高性能和丰富的特性常被用于实现分布式锁。但当加锁过程中Redis服务不可用时&#xff0c;系统将面临严重挑战。本文将深入探讨这一问题&#xff0c;并提供多维度解决方案。 目…

番外·卓伊凡参加 [2025年2月HDD·鸿蒙赋能交流会·成都站] 线下活动的心得体会-优雅草卓伊凡

番外卓伊凡参加 [2025年2月HDD鸿蒙赋能交流会成都站] 线下活动的心得体会-优雅草卓伊凡 背景 2025 年 2 月 22 日&#xff0c;HDD・鸿蒙赋能交流会将在北京、长沙、成都、南京、雄安同步开展。此次交流会由 HDG 组织者牵头&#xff0c;携手 HUAWEI DEVELOPER EXPERTS&#xf…

vue-fastapi-admin 部署心得

vue-fastapi-admin 部署心得 这两天需要搭建一个后台管理系统&#xff0c;找来找去 vue-fastapi-admin 这个开源后台管理框架刚好和我的技术栈所契合。于是就浅浅的研究了一下。 主要是记录如何基于原项目提供的Dockerfile进行调整&#xff0c;那项目文件放在容器外部&#xf…

永洪科技旗下BI产品,成功入选“金融信创优秀解决方案“

3月28日至29日&#xff0c;金融信创生态实验室在京举办金融信创解决方案研讨会&#xff0c;发布第三期金融信创优秀解决方案、实验室推荐解决方案&#xff0c;启动解决方案的分享活动。 永洪科技凭借旗下的敏捷BI数据分析平台&#xff0c;成功入选“金融信创优秀解决方案&…

Android之APP更新(通过接口更新)

文章目录 前言一、效果图二、实现步骤1.AndroidManifest权限申请2.activity实现3.有版本更新弹框UpdateappUtilDialog4.下载弹框DownloadAppUtils5.弹框背景图 总结 前言 对于做Android的朋友来说&#xff0c;APP更新功能再常见不过了&#xff0c;因为平台更新审核时间较长&am…

PHP课程预约小程序源码

&#x1f4f1; 课程预约小程序&#xff1a;为您专属定制的便捷预约新体验 在这个快节奏的时代&#xff0c;我们深知每一位瑜伽爱好者、普拉提追随者以及培训机构管理者对高效、便捷服务的迫切需求。因此&#xff0c;我们匠心独运&#xff0c;推出了一款基于PHPUniApp框架开发的…

WebXR教学 02 配置开发环境

默认操作系统为Windows 1.VS Code VS Code 是一款轻量级、功能强大的代码编辑器&#xff0c;适用于多种编程语言。 下载 步骤 1&#xff1a;访问 VS Code 官方网站 打开浏览器&#xff08;如 Chrome、Edge 等&#xff09;。 在地址栏输入以下网址&#xff1a; https://code.v…

unity学习51:所有UI的父物体:canvas画布

目录 1 下载资源 1.1 在window / Asset store下下载一套免费的UI资源 1.2 下载&#xff0c;导入import 1.3 导入后在 project / Asset下面可以看到 2 画布canvas&#xff0c;UI的父物体 2.1 创建canvas 2.1.1 画布的下面是 event system是UI相关的事件系统 2.2 canvas…

JavaWeb开发入门:从前端到后端的完整流程解析

一、JavaWeb简介 1、C/S 客户端/服务器结构 2、B/S&#xff08;Browser/Server&#xff0c;浏览器/服务器&#xff09;结构 二、开发环境搭建 1. 安装Tomcat--一个小型的web容器。 2. 在eclipse中配置tomcat创建项目 三、JavaWeb开发流程 1. 前端页面设计 2. 后端逻辑…