当前位置: 首页 > news >正文

Linux内核slab分配器

一、slab思想以及编程接口

1、slab 核心思想

         为每种对象类型创建一个内存缓存,每个内存缓存由多个大块组成,一个大块是一个或多个连续的物理页,每个大块包含多个对象。slab 采用面向对象的思想,基于对象类型管理内存,每种对象被划分为一个类,比如进程描述符(task_struct)是一个类,每个进程描述符实现是一个对象。内存缓存组成结构如下:

2.编程接口

【通用分配接口】

分配内存: void *kmalloc (size_t size,gfp_t flags);
重新分配内存: void *krealloc (const void *p, size_t new_size, gfp_t flags)
释放内存: void kfree (const void *objp);

【slab专用接口】

创建内存缓存: struct kmem_cache *kmem_cache_create (const char , size_t, size_t, unsigned long, void ()(void *));
指定内存缓存分配对象: void *kmem_cache_alloc (struct kmem_cache *, gfp_t);
释放对象: void kmem_cache_free (struct kmem_cache *, void *);
销毁内存缓存: void kmem_cache_destroy (struct kmem_cache *);

【接口细节】

1、创建内存缓存
struct kmem_cache *kmem_cache_create(const char *name, size_t size, size_t align,unsigned long flags,void (*strc)(void *));
name:名称
size:对象的长度
align:对象需要对齐的数值
flags:slab 标志位
strc:对象的构造函数

2、指定内存缓存分配对象
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)
cachep:从指定的内存缓存分配
flags:传给页分配器的分配标志位,当内存缓存没有空闲对象,向页分配器请求分配页的时候使用这个分配标志位。

3、释放对象
void kmem_cache_free(struct kmem_cache *cachep, void *objp);
cachep:对象所属的内存缓存
objp:对象的地址

4、销毁内存缓存
void kmem_cache_destroy(struct kmem_cache *s);
s:内存缓存

【kmalloc与kmem_cache_alloc核心区别】

kmalloc 和 kmem_cache_alloc 虽然都基于 Slab 分配器,但它们在用途、灵活性和性能优化方面存在显著差异。以下是两者的主要区别及适用场景:


1. 核心区别

特性kmallockmem_cache_alloc
缓存类型使用预定义的通用缓存(如 kmalloc-32kmalloc-64使用用户自定义的专用缓存(需通过 kmem_cache_create 创建)
内存对齐默认对齐(通常按 CPU 缓存行对齐)可自定义对齐方式(如按对象大小对齐)
构造函数/析构函数不支持支持(可在分配/释放时自动调用初始化或清理函数)
适用场景通用的小块内存分配高频分配/释放固定大小的对象(如结构体)
性能优化适用于非频繁分配的通用场景针对特定对象优化,减少碎片和查找开销

2. 使用示例

场景 1:通用内存分配(kmalloc
  • 需求:分配 128 字节的临时缓冲区。

  • 实现

    void *buffer = kmalloc(128, GFP_KERNEL);
    if (!buffer) {
        // 错误处理
    }
    // 使用 buffer...
    kfree(buffer);
  • 特点:无需预定义缓存,内核自动选择 kmalloc-128 缓存。

场景 2:专用对象分配(kmem_cache_alloc
  • 需求:频繁分配/释放固定大小的结构体(如 struct my_struct,大小为 256 字节)。

  • 实现

    // 创建专用缓存
    注意:新版内核只使用五个参数,析构函数参数被舍弃
    struct kmem_cache *my_cache = kmem_cache_create(
        "my_struct_cache",       // 缓存名称
        sizeof(struct my_struct),// 对象大小
        0,                       // 对齐偏移(默认按对象大小对齐)
        SLAB_HWCACHE_ALIGN,      // 标志(按 CPU 缓存行对齐)
        NULL, NULL               // 构造函数/析构函数(可选)
    );
    
    // 分配对象
    struct my_struct *obj = kmem_cache_alloc(my_cache, GFP_KERNEL);
    if (!obj) {
        // 错误处理
    }
    // 使用 obj...
    kmem_cache_free(my_cache, obj);
    
    // 销毁缓存(模块卸载时)
    kmem_cache_destroy(my_cache);
  • 特点:通过预定义缓存减少分配开销,支持自定义初始化和清理逻辑。


3. 性能对比

操作kmallockmem_cache_alloc
分配速度较慢(需匹配通用缓存)更快(直接命中专用缓存)
内存碎片可能产生更多碎片碎片较少(专用缓存隔离对象大小)
适用高频操作不推荐推荐

4. 总结

  • 使用 kmalloc 的场景
    适用于临时、非频繁的小块内存分配,无需关心缓存管理(如临时缓冲区、字符串操作)。

  • 使用 kmem_cache_alloc 的场景
    适用于高频分配/释放固定大小的对象(如网络数据包、文件描述符),需通过 kmem_cache_create 预定义缓存以优化性能。

  • 本质区别
    kmalloc 是对通用 Slab 缓存的封装,而 kmem_cache_alloc 是对用户自定义缓存的精细化控制。前者简化了接口,后者提供了更高的灵活性和性能优化空间。

二、slab数据结构

  • 每个内存缓存对应一个 kmem_cache 实例,所有cpu共享同一个全局对象。
  • 每个内存节点对应一个 kmem_cache_node 实例。
  • kmem_cache 实例的成员 cpu_slab 指向 array_cache 实例,每个处理器对应一个 array_cache 实例,称为数组缓存,用来缓存刚刚释放的对象,分配时首先从当前处理器的数据缓存分配,避免每次都要从 slab 分配,减少链表操作的锁操作,提高分配的速度。slab 分配器数据结构源码分析如下:

 

高速缓存描述符数据结构:struct kmem_cache

         一个高速缓存中可以含有多个 kmem_cache 对应的高速缓存,就拿 L1 高速缓存来举例,一个 L1 高速缓存对应一个 kmem_cache 链表,这个链表中的任何一个 kmem_cache 类型的结构体均描述一个高速缓存,而这些高速缓存在 L1 cache 中各自占用着不同的区域。

具体源码分析如下:

         

三、每处理器数组缓存array_cache

        内存缓存为每个处理器创建一个数组缓存(结构体 array_cache)。释放对象时,把对象存放到当前处理器对应的数组缓存中;分配对象的时候,先从当前处理器的数组缓存分配对象,采用后进先出(LIFO)原则,可以提高性能。

  1. array_cache 是什么?

    • array_cache 是一个本地缓存结构,通常每个 CPU 或 NUMA 节点有独立实例,用于存储已分配但未使用的对象,提升内存分配效率,减少全局缓存访问、锁争用及内存访问延迟,主要针对小对象缓存。
  2. 共享的含义

    • 指不同 NUMA 节点共享 array_cache 中缓存的对象,shared 字段指向跨 NUMA 节点共享的缓存。
    • 每个 NUMA 节点有本地 array_cache,本地缓存用尽时,可从其他 NUMA 节点缓存获取对象,提高内存重用率,减少跨节点内存访问延迟。
  3. 共享的具体实现

    • NUMA 系统中,节点有本地 array_cache,为优化性能,多个节点可共享缓存对象。如某节点本地缓存满或无空闲对象,可从其他节点缓存(存储在 shared 指向的 array_cache 中)获取。
    • shared 目的是提升内存分配效率,避免单一节点缓存资源过度消耗与访问延迟。
  4. 共享的对象是什么?

    • 共享 array_cache 中存储的小对象。节点本地缓存对象耗尽时,内核可访问其他 NUMA 节点缓存,快速获取对象,无需等待全局缓存或其他内存池分配。
  5. 谁去共享?

    • 每个 NUMA 节点的 array_cache 均可共享缓存。如 NUMA 系统中,节点 A 的 array_cache 对象可与节点 B 的共享。CPU 核心请求对象时,可从本地或其他节点共享缓存获取。
    • 内核内存分配器执行共享机制,依当前 NUMA 节点缓存状况(如空闲对象数)决策。若节点 A 本地缓存不足,可从节点 B 缓存获取对象,减少对全局 kmem_cache 的访问,提升性能。

1. 设计优化
  • 引入 per-CPU array_cache
    传统分配需操作全局 slab 缓存,多核 CPU 易引发锁争用。per-CPU 的 array_cache 作为私有缓存,仅在缓存耗尽 / 满载时操作全局 slab,减少锁争用:

    • 分配:优先从本地 array_cache 分配,无需全局锁;缓存空时批量从全局获取。
    • 释放:优先将对象存入本地 array_cache;缓存满时批量释放回全局 slab。
  • 减少全局锁争用

    • 独立 per-CPU 缓存:每个 CPU 有自己的 array_cache,分配释放优先用本地缓存。
    • 批量操作array_cache 耗尽 / 满时批量操作全局 slab,降低锁操作频率。
    • 减少链表操作:用数组存储指针,避免频繁操作全局 slab 链表。
    • 提高缓存命中率:最近释放对象优先重新分配(LIFO),提高 CPU 缓存命中率。
2. 分配释放规则
  • 分配:先从当前处理器 array_cache 分配,若空则批量填充(批量值为 batchcount)。
  • 释放:若 array_cache 满,先批量归还对象到 slab,再将当前释放对象存入缓存。
3. 全局锁存在必要性
  • 共享对象管理:跨 NUMA 节点请求内存时,需全局锁确保分配公平性和一致性。
  • 跨 NUMA 节点请求:防止不同节点同时修改全局状态,避免竞态条件。
  • 内存池管理回收:内存池创建、销毁、调整等全局操作需同步,确保原子性。

为什么 array_cache 还需要全局锁?

  1. 共享对象的管理
    array_cache 是每个 NUMA 节点的本地缓存,存储预分配的小对象。当本地缓存对象被用完时,需向全局的 kmem_cache 请求更多内存。若各 NUMA 节点的缓存完全独立,缺乏协调内存需求的机制,会导致跨节点资源分配不均衡。为确保内存分配的公平性和全局一致性,内核需要全局锁来同步不同节点之间的内存请求。

  2. 跨 NUMA 节点的对象请求
    即使 array_cache 是每个节点的本地缓存,当某个 NUMA 节点的缓存用尽时,可能需要从其他节点的 array_cache 或全局的 kmem_cache 中请求对象。此时,全局锁可确保跨节点的内存请求不发生竞态条件,防止不同节点同时修改全局状态,保障操作的安全性。

  3. 内存池的管理和回收
    内核通过 kmem_cache 管理内存池。当节点本地缓存中的对象过多时,会将多余对象回收到全局缓存或共享缓存中。这一过程需要全局锁来确保回收操作的同步。即便每个节点有独立的 array_cache,内存的最终回收和分配仍需全局协调,以维持整体内存管理的秩序。

  4. 避免内存碎片
    在多核系统中,若没有全局同步机制,多个节点同时修改 kmem_cache 可能导致内存碎片增加。通过全局锁管理内存池,能在一定程度上避免因并行操作引发的内存碎片化问题,提升内存使用效率。

  5. 内存池的创建和销毁
    例如,kmem_cache 本身的创建、销毁和大小调整等操作具有全局性,涉及跨节点的内存管理。此时需要全局锁来保证这些操作的原子性和一致性,确保 kmem_cache 生命周期管理与状态更新的正确性。

解决方案:

全局锁并非保护所有操作。实际上,array_cache 在处理常规对象分配时,通过局部缓存避免全局锁争用,使每个 CPU 或 NUMA 节点能独立工作,减少竞争和延迟。但当涉及跨节点内存请求(如某节点 array_cache 资源短缺),或触发全局内存池操作(如 kmem_cache 的创建、清理)时,仍需要全局锁来避免数据不一致或资源冲突,在性能优化与全局一致性间取得平衡。

【图解】

  • kmem_cache_node 与 array_cache 区别

    特性kmem_cache_nodearray_cache
    作用管理 NUMA 节点上 kmem_cache 的 slab 页,优化分配回收加速小对象分配,减少全局分配锁争用
    粒度以 slab 页为单位管理对象分配回收以单个对象为单位缓存小对象
    分配目标管理空闲、部分满、完全满的 slab 页缓存小对象以便快速分配
    NUMA 相关性每个 NUMA 节点一个实例,管理该节点 slab 页每个 NUMA 节点一个或多个实例,加速分配
  • 数据结构关系

    • kmem_cache:管理对象类型缓存池,包含 CPU 私有缓存指针、对象大小、分配标志等。
    • kmem_cache_node:管理特定 NUMA 节点缓存状态,包含 slab 页链表(slab_partialslab_fullslab_free)。
    • array_cache:每个 CPU/NUMA 节点的本地缓存,存储预分配小对象,优化分配效率。
    • slabinfo:监控 kmem_cache 状态的结构体,通过 /proc/slabinfo 展示缓存名称、活跃对象数等信息。

 

四、内存回收

        对于所有对象空间的slab,没有立即释放,而是放在空闲的slab链表中。只有内存节点上的空闲对象的数量超过限制,才开始回收空闲slab,知道空闲对象的数量小于或等于限制。

1. Slab 页与内存管理
  • slab 页概念:Slab 分配器的内存管理 “容器”,承载多个内存块。从系统页(4KB 或 8KB)分配,包含多个固定大小内存块,每个块存储一个对象,提升内存管理效率,避免频繁页分配释放和内存碎片化。
  • 管理方式:将多个内存块打包到 slab 页,按需分配给应用程序 / 内核代码。
2. 回收机制
  • 空闲对象数量限制
    节点 n 的空闲对象数量限制 =(1 + 节点处理器数量)* kmem_cache.batchcount + keme_cache.num
  • 定期回收
    slab 分配器通过每个处理器向全局工作队列添加延迟工作项(处理函数 cache_reap)定期回收:
    • 每 2 秒执行:回收节点 n 对应远程节点数组缓存对象;若当前处理器数组缓存 2 秒无分配,回收其缓存对象。
    • 每 4 秒执行:若共享数组缓存 4 秒无分配,回收其对象;若空闲 slab 4 秒无分配,回收空闲 slab。

https://github.com/0voice

http://www.dtcms.com/a/113651.html

相关文章:

  • Linux 系统安装与优化全攻略:打造高效开发环境
  • Airflow量化入门系列:第四章 A股数据处理与存储优化
  • 浅谈StarRocks 常见问题解析
  • (5)模拟后——Leonardo的可视化操作
  • 探秘叁仟智盒设备:智慧城市的智能枢纽
  • Django4.0 快速集成jwt
  • ASP.NET Core Web API 参数传递方式
  • NLP简介及其发展历史
  • docker stack常用命令
  • C#结构体(Struct)深度解析:轻量数据容器与游戏开发应用 (Day 20)
  • pinia-plugin-persist、vuex
  • Spring Boot项目连接MySQL数据库及CRUD操作示例
  • Java Timer:老派但好用的“定时任务小闹钟“
  • 【Linux】进程间通信、匿名管道、进程池
  • 将OpenFOAM中的lduMatrix数据转换为CSC稀疏矩阵格式
  • 混合编程的架构
  • Java EE期末总结(第三章)
  • Leedcode刷题 | 回溯算法小总结01
  • kali——masscan
  • Matlab轴承故障信号仿真与故障分析
  • spring-cloud-alibaba-nacos-config使用说明
  • 《K230 从熟悉到...》无线网络
  • LINUX 4 tar -zcvf -jcvf -Jcvf -tf -uf
  • Transformer+BO-SVM多变量时间序列预测(Matlab)
  • 力扣刷题——508.出现次数最多的子树和
  • Docker存储策略深度解析:临时文件 vs 持久化存储选型指南
  • 每日算法-250405
  • 4. 面向对象程序设计
  • 分布式事务解决方案全解析:从经典模式到现代实践
  • 每天五分钟深度学习框架pytorch:搭建LSTM完成手写字体识别任务?