slab分配器(3):slab内存申请和释放

cpp 复制代码
void *kmem_cache_alloc(struct kmem_cache *s, gfp_t gfpflags)
{
	void *ret = slab_alloc(s, gfpflags, _RET_IP_);

	trace_kmem_cache_alloc(_RET_IP_, ret, s->object_size,
				s->size, gfpflags);

	return ret;
}


static __always_inline void *slab_alloc(struct kmem_cache *s,
		gfp_t gfpflags, unsigned long addr)
{
	return slab_alloc_node(s, gfpflags, NUMA_NO_NODE, addr);
}

https://elixir.bootlin.com/linux/v5.4.285/source/mm/slub.c#L2822

可以看到,核心函数是

https://elixir.bootlin.com/linux/v5.4.285/source/mm/slub.c#L2727

二、关键代码逻辑分解

struct kmem_cache用s表示

struct kmem_cache_cpu用c表示

struct kmem_cache_node用n表示

slub内存申请逻辑包括快速通道和慢速通道。我们把slub的申请速度由快到慢分为以下5个速度级别,L1速度最快,L5速度最慢。

  1. 从c->freelist获取obj为L1
  2. 从c->page获取obj为L2
  3. 从c->partial获取obj为L3
  4. 从n->parital获取obj为L4
  5. 从alloc_pages中获取obj为L5

下面结合源码依次分析slub的申请流程:

  • L5路径

刚开始无slub缓存,走慢速路径,最慢速的路径只能从伙伴系统中申请一个slub。会调用new_slab函数申请一个slab,并填充page中freelist=start,page->inuse=page->objects,page->frozen=1;同时填充obj为单向链表,然后迁移到c->freelist中,同时page->freelist=NULL,然后返回第一个空闲的object。

  • L1路径

L1路径即c->freelist!=NULL,则直接从c->freelist拿一个obj。

如果第一个slub通过慢慢申请殆尽,需要重新申请个slub,老的slub游离,同时把该slub的page->frozen=0

  • L2路径

随着slub中的object申请释放,c->freelist的object为NULL,而c->page!=NULL,此时需要从c->page->freelist迁移到c->freelist中,在slab_alloc_node函数中判断c->freelist为NULL,未关中断,则进入__slab_alloc函数中,在此函数中local_irq_save关中断,此时有可能c->freelist被释放了,因此在___slab_alloc函数中判断c->page不为空,此时还需要判断c->freelist,如果不为空,则从c->freelist中获取obj,如果为空,则通过freelist =get_freelist(s, page)函数获取。重点看下get_freelist函数

1. ​预处理与事务同步
cpp 复制代码
s = slab_pre_alloc_hook(s, gfpflags);
  • 作用:调用预处理钩子函数,可能涉及内存控制组(memcg)的检查或调试功能(如KASAN)
  • 返回值 :若缓存无效(如被销毁),则直接返回NULL。
2. ​事务ID与CPU缓存同步
cpp 复制代码
do {
    tid = this_cpu_read(s->cpu_slab->tid);
    c = raw_cpu_ptr(s->cpu_slab);
} while (IS_ENABLED(CONFIG_PREEMPT) && unlikely(tid != READ_ONCE(c->tid)));
  • 关键机制 :
    • 事务ID(tid)​:全局唯一性标识,用于检测并发操作(如抢占导致缓存状态变化)
    • 循环检查 :在启用抢占(CONFIG_PREEMPT)时,确保读取的tid与当前CPU缓存的tid一致,避免因线程切换导致数据不一致
3. ​内存屏障与操作顺序保证
cpp 复制代码
barrier();
  • 作用 :确保在读取c->freelist和c->page前,tid的加载操作已完成,防止乱序执行引发竞态条件


4、后处理与安全性
cpp 复制代码
maybe_wipe_obj_freeptr(s, object);
if (unlikely(slab_want_init_on_alloc(...)))
    memset(object, 0, s->object_size);
slab_post_alloc_hook(s, gfpflags, 1, &object);
  • 关键操作 :
    • 擦除空闲指针:防止信息泄漏(如KASAN检测)
    • 对象初始化 :根据__GFP_ZERO标志清零对象内存。
    • 后处理钩子:更新内存统计、调试信息或触发内存回收

ref:

内存管理之slub

图解slub

相关推荐
lzx_0025 小时前
Linux 进程(二) 深入理解进程
linux
沫璃染墨6 小时前
从零入门计算机网络系列(十二):HTTP长连接与会话管理详解——从Connection到Cookie与Session
linux·服务器·c++·tcp/ip·ubuntu·http·交互
温暖小土6 小时前
在 CentOS 上安装 Redis Stack
linux·redis·centos
GeW7 小时前
国产化替代进入深水区之后Linux 与数据库为什么必须走向重建
linux
女神下凡10 小时前
芯参谋(85):Linux_PPI_NAND_驱动开发指南
linux·运维·arm开发·驱动开发·单片机·嵌入式硬件·设计规范
12.=0.11 小时前
【持续更新】shell脚本 + Linux命令
linux·运维·服务器
Escalating_xu11 小时前
【Linux 网络编程】五种 I/O 模型与非阻塞 I/O:从“等数据”到正确处理 EAGAIN
java·linux·网络
无言听风雨丶12 小时前
Linux 容器本质探秘:Cgroup 与 Namespace 深度解析与实战
linux·运维·服务器·kubernetes
万联WANFLOW12 小时前
CVE-2026-98432 等漏洞自查:AI 查出的 Linux、Windows 漏洞,运维怎么确认受不受影响
linux·运维·windows·网络安全
OPEN-F12 小时前
Ubuntu 自动登录后 GNOME 远程桌面连不上?一次登录钥匙环锁定的排查与永久修复
linux·运维·ubuntu