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_PARTIAL
    struct slab *partial;     /* 部分分配的冻结Slab链表(仅启用CPU Partial时存在) */
#endif
    local_lock_t lock;        /* 保护上述字段的本地CPU锁 */
#ifdef CONFIG_SLUB_STATS
    unsigned 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#/)

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

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

相关文章

冯诺依曼体系结构 ──── linux第8课

目录 冯诺依曼体系结构 关于冯诺依曼,必须强调几点: 冯诺依曼体系结构 我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系 输入单元:包括键盘, 鼠标,网卡,扫…

国标28181协议在智联视频超融合平台中的接入方法

一. 国标28181介绍 国标 28181 协议全称是《安全防范视频监控联网系统信息传输、交换、控制技术要求》,是国内视频行业最重要的国家标准,目前有三个版本: 2011 年:推出 GB/T 28181-2011 版本,为安防行业的前端设备、平…

2024-2025 学年广东省职业院校技能大赛 “信息安全管理与评估”赛项 技能测试试卷(四)

2024-2025 学年广东省职业院校技能大赛 “信息安全管理与评估”赛项 技能测试试卷(四) 第一部分:网络平台搭建与设备安全防护任务书第二部分:网络安全事件响应、数字取证调查、应用程序安全任务书任务 1:应急响应&…

音乐游戏Dance Dance Revolution(DDR)模拟器

文章目录 (一)Dance Dance Revolution(1.1)基本情况(1.2)机体 (二)模拟器(2.1)主程序(2.2)模拟器主题 (三)曲谱…

货车一键启动无钥匙进入手机远程启动的正确使用方法

一、移动管家货车无钥匙进入系统的使用方法 基本原理:无钥匙进入系统通常采用RFID无线射频技术和车辆身份识别码识别系统。车钥匙需要随身携带,当车钥匙靠近货车时,它会自动与货车的解码器匹配。开门操作:当靠近货车后&#xff0…

vscode如何使用鼠标滚轮调整字体大小

1.打开设置 2.搜索Font Ligatures 3.编辑配置文件 4.修改代码并保存 修改前 修改后 在最后一行添加:“editor.mouseWheelZoom”: true 记得在上一行最后,加上英文版的“,”逗号 5.配置成功,再次按Ctrl鼠标滚轮便可以缩放了。

视频裂变加群推广分享引流源码

源码介绍 视频裂变加群推广分享引流源码 最近网上很火,很多人都在用,适合引流裂变推广 测试环境:PHP7.4(PHP版本不限制) 第一次访问送五次观看次数,用户达到观看次数后需要分享给好友或者群,好友必须点击推广链接后才会增加观看次…

redis小记

redis小记 下载redis sudo apt-get install redis-server redis基本命令 ubuntu16下的redis没有protected-mode属性,就算sudo启动,也不能往/var/spool/cron/crontabs写计划任务,感觉很安全 #连接到redis redis-cli -h 127.0.0.1 -p 6379 …

IDEA关闭SpringBoot程序后仍然占用端口的排查与解决

IDEA关闭SpringBoot程序后仍然占用端口的排查与解决 问题描述 在使用 IntelliJ IDEA 开发 Spring Boot 应用时,有时即使关闭了应用,程序仍然占用端口(例如:4001 端口)。这会导致重新启动应用时出现端口被占用的错误&a…

Vue04

自定义指令 directives是Vue的一个配置项 这里写自定义指令 自定义指令被调用的时机 指令与元素成功绑定时 指令所在的模板被重新解析时 函数式 <span v-big"n"></span> directives:{ big(element,binding){ element.innerText bingin…

岳阳市美术馆预约平台(小程序论文源码调试讲解)

第4章 系统设计 一个成功设计的系统在内容上必定是丰富的&#xff0c;在系统外观或系统功能上必定是对用户友好的。所以为了提升系统的价值&#xff0c;吸引更多的访问者访问系统&#xff0c;以及让来访用户可以花费更多时间停留在系统上&#xff0c;则表明该系统设计得比较专…

Linux 基本开发工具的使用(yum、vim、gcc、g++、gdb、make/makefile)

文章目录 Linux 软件包管理器 - yum理解什么是软件包和yum如何查看/查找软件包如何安装软件如何实现本地机器和云服务器之间的文件互传如何卸载软件 Linux 编辑器 - vim 的使用vim 的基本概念vim 的基本操作vim 命令模式各命令汇总vim 底行模式各命令汇总vim 的简单配置 Linux …

4部署kibana:5601

kibana 是一个基于浏览器页面的Elasticsearch前端展示工具&#xff0c;, 是一个开源和免费的工具 Kibana可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面, 可以帮你汇总、分析和搜索重要数据日志 1.安装-所有的es节点 # tar xf kibana-6.4.1-linux-x86_64.t…

1.介绍一下TCP/IP模型和OSI模型的区别【中高频】

OSI模型 将 这个协议 划分为7个不同的层级&#xff0c;分别为物理层、数据链路层、网络层、传输层、会话层、表示层和应用层&#xff0c;而TCP/IP模型只有4个层级&#xff0c;分别为网络接口层、网络层、传输层和应用层&#xff0c;其中应用层在用户态&#xff0c;传输层及以下…

【反爬】拦截comBusiness.js disable-devtool.js

一、现象 无法使用ctrls保存网页&#xff0c;但是可以在设置菜单中可以保存&#xff1b;无法使用F12和ctrlshifti打开开发者窗口&#xff0c;但是可以在设置菜单中打开&#xff1b;打开开发者窗口后网站快速关闭&#xff0c;说明被检测到了&#xff1b; 二、涉及js 打开设置菜…

【11】子网

区块链子网概述 什么是子网&#xff1f; 子网是在较大网络上下文中运行的较小网络&#xff0c;因此由对应的“主网”&#xff0c;主网即包含多个子网的较大网络或具有隶属关系的上一级网络。子网允许在主网中进行一些独立的事务或控制网络参数。 对于互联网而言&#xff0c;…

爬虫基础入门之爬取豆瓣电影Top250-Re正则的使用

网址:豆瓣电影 Top 250 本案例所需要的模块 requests (用于发送HTTP请求)re (用于字符串匹配和操作) 确定需要爬取的数据 &#xff1a; 电影的名称电影的年份电影的评分电影评论人数 一. 发送请求 模拟浏览器向服务器发送请求 准备工作 -分析页面: F12 or 右击点击检查 查看…

论文笔记(七十二)Reward Centering(五)

Reward Centering&#xff08;五&#xff09; 文章概括摘要附录B 理论细节C 实验细节D 相关方法的联系 文章概括 引用&#xff1a; article{naik2024reward,title{Reward Centering},author{Naik, Abhishek and Wan, Yi and Tomar, Manan and Sutton, Richard S},journal{arX…

Flash-03

1-问题&#xff1a;Flash软件画两个图形&#xff0c;若有部分重合则变为一个整体 解决方法1&#xff1a;两个图形分属于不同的图层 解决方法2&#xff1a;将每个图形都转化为【元件】 问题2&#xff1a;元件是什么&#xff1f; 在 Adobe Flash&#xff08;现在称为 Adobe Anim…

ssh配置 远程控制 远程协作 github本地配置

0.设备版本 windows11 ubuntu24.0.4 1.1 在 Linux 上启用 SSH 服务 首先&#xff0c;确保 Linux 计算机上安装并启用了 SSH 服务。 安装和启动 OpenSSH 服务&#xff08;如果未安装&#xff09; # 在终端安装 OpenSSH 服务&#xff08;如果尚未安装&#xff09; sudo apt …