内存管理机制SLAB

1. 为什么需要内存分配管理?为什么需要SLAB?

  • 在学习c语言时,我们常常会使用到malloc()去申请一块内存空间,用于存放我们的数据,这是代码层面的语言

  • 如果我们想要关心malloc这个命令向系统发出后,系统会做什么呢?系统会给这个变量分配一个内存空间。那么系统是如何分配的呢,这就需要了解系统的内存分配管理方法了。

  • 在linux中,最先推出用于分配内存的管理单元和算法是伙伴分配器(buddy allocator),它是以页为单位管理和分配内存,最小分配一页,也就是4KB 大小。而可能内核的需求只是以字节为单位。

    • 假如我们需要动态申请一个内核结构体(占 20 字节),若仍然分配一页内存,这将严重浪费内存。这也将会导致内部碎片问题
  • 这时候就出现了slab分配器,它专门用于分配小内存,分配内存以字节为单位,基于伙伴分配器的大内存进一步细分成小内存分配。

    • 概括来讲:slab 分配器仍然从buddy分配器中申请内存,之后自己对申请来的内存细分管理。从而达到减少内存碎片化的目的。
    • 形象概括:buddy分配器理解成一个仓库,slab分配器理解为一个商店,仓库给商店进行批发的货物,商店从仓库进货以后,再零售给消费者(使用kmalloc的用户)
      在这里插入图片描述
  • SLAB分配器内存管理机制的核心思想:

    • 提供小内存,减少内存碎片
    • 维护常用对象的缓存
    • 提高CPU硬件缓存的利用率
  • 随着时间的推移,SLAB分配器演变成SLUB和SLOB分配器

2. SLAB底层机制

2.1 基本概念

2.1.1 Slab

  • 是SLAB机制中的基本组成单元,它是预先分配的一块连续的内存区域
  • 每个Slab由一个或多个大小相同的对象组成,这些对象属于同一个Cache。
  • Slab可以处于三种状态之一:满(full),部分满(partial)和空(empty)。
  • 系统优先从部分满的Slab中分配对象,以提高内存利用率。

2.1.2 Slab Cache

  • Slab Cache专门用于管理一种特定大小和类型的对象。
  • 每个Slab Cache都由多个Slab组成,它们共同组成了该类型对象的存储池。
  • 通过Slab Cache,系统可以快速地分配和释放对象,避免了每次分配时都进行昂贵的内存搜索和设置操作。
  • 主要负责三个事情:
    • 对象缓存:缓存常用对象,加快分配速度。
    • 内存管理:根据需要增加或释放Slab,优化内存使用。
    • 碎片最小化:通过维护大小相同的对象集合,减少内存碎片。

2.1.3 缓冲色彩(Cache Coloring)

  • 缓存色彩是一种用于优化CPU缓存利用率的技术。通过对内存分配的微小调整,它能减少不同Slab中对象的缓存行冲突。

2.1.4 构造器和析构器

  • 为了进一步提高效率,SLAB机制允许为每种类型的对象定义构造器(Constructor)和析构器(Destructor)。构造器在对象第一次被创建时被调用,用于初始化对象。析构器在对象最终被释放回系统前被调用,用于执行必要的清理工作。通过这种方式,SLAB机制确保了资源的有效利用和稳定的性能表现。

2.2 结构定义

2.2.1 slab分配的内存大小

  • 问题:Linux中采用4KB大小的页框作为标准的内存分配单元,在实际应用中,经常需要分配一组连续的页框,而频繁的申请和释放不同大小的连续页框,必然导致在已分配页框的内存块中分散了许多小块的空闲页框,这样,即使这些页框是空闲的,其他需要分配连续页框的应用也很难得到满足。
  • Linux内核引入了伙伴系统算法来避免这种情况。其把所有的空闲页框分组为11个块链表,每个块链表分别包含大小为1、2、4、8、16、32、64、128、256、512和1024个连续页框的页框块。最大可以申请1024个连续页框,也即4MB大小的连续空间。
  • 而slab则基于伙伴系统,进一步将页框划分成各个小的内存块,而他的实现则是通过在kmem_cache_init过程中,通过kmalloc_caches[KMALLOC_NORMAL][INDEX_NODE]来建立caches数组
void __init kmem_cache_init(void){
	kmalloc_caches[KMALLOC_NORMAL][INDEX_NODE] = create_kmalloc_cache(
        kmalloc_info[INDEX_NODE].name[KMALLOC_NORMAL],
        kmalloc_info[INDEX_NODE].size,
        ARCH_KMALLOC_FLAGS, 0,
        kmalloc_info[INDEX_NODE].size);
        ...
        create_kmalloc_caches(ARCH_KMALLOC_FLAGS);
}
  • kmalloc_caches[KMALLOC_NORMAL][INDEX_NODE]中的INDEX_NODE即为kmalloc_index,INDEX_NODE取值范围为0-21,分别存放着不同大小的内存caches
static __always_inline unsigned int __kmalloc_index(size_t size, bool size_is_constant){
    /* 0 = zero alloc */
    if (!size)
        return 0;
        
    if (size <= KMALLOC_MIN_SIZE)
        return KMALLOC_SHIFT_LOW;
 
    /* 1 =  65 .. 96 bytes分配65-96bytes的内存大小的块*/
    if (KMALLOC_MIN_SIZE <= 32 && size > 64 && size <= 96)
        return 1;
        
    /* 2 = 129 .. 192 bytes分配65-96bytes的内存大小的块*/
    if (KMALLOC_MIN_SIZE <= 64 && size > 128 && size <= 192)
        return 2;
        
    /* n = 2^(n-1)+1 .. 2^n 分配2^(n-1)+1 .. 2^n大小内存的块内存*/
    if (size <= 8) return 3;
    if (size <= 16) return 4;
    if (size <= 32) return 5;
    if (size <= 64) return 6;
    if (size <= 128) return 7;
    if (size <= 256) return 8;
    if (size <= 512) return 9;
    if (size <= 1024) return 10;
    if (size <= 2 * 1024) return 11;
    if (size <= 4 * 1024) return 12;
    if (size <= 8 * 1024) return 13;
    if (size <= 16 * 1024) return 14;
    if (size <= 32 * 1024) return 15;
    if (size <= 64 * 1024) return 16;
    if (size <= 128 * 1024) return 17;
    if (size <= 256 * 1024) return 18;
    if (size <= 512 * 1024) return 19;
    if (size <= 1024 * 1024) return 20;
    if (size <= 2 * 1024 * 1024) return 21;
 
    if (!IS_ENABLED(CONFIG_PROFILE_ALL_BRANCHES) && size_is_constant)
        BUILD_BUG_ON_MSG(1, "unexpected size in kmalloc_index()");
    else
        BUG();
    return -1;
}
  • 通过create_kmalloc_cache创建出对应的内存区域,且它们对应着0,96byte,192byte,8byte,16byte,32byte,64byte . . . 2M的连续内存空间。

2.2.2 slab分配的内存类型

  • 内存类型则由kmalloc_caches[KMALLOC_NORMAL][INDEX_NODE]中的KMALLOC_NORMAL决定,可选诸如KMALLOC_NORMAL、KMALLOC_DMA、KMALLOC_CGROUP、NR_KMALLOC_TYPES、KMALLOC_RECLAIM等,也可以由用户自己定义自己的专用内存类型,诸如kvm_vcpu、dquot、signal_cache等等都是其他模块自行定义的内存类型。
enum kmalloc_cache_type {
    /* 对应着kmalloc的内存 */
    KMALLOC_NORMAL = 0,
#ifndef CONFIG_ZONE_DMA
    KMALLOC_DMA = KMALLOC_NORMAL,
#endif

#ifndef CONFIG_MEMCG_KMEM
    KMALLOC_CGROUP = KMALLOC_NORMAL,
#endif

#ifdef CONFIG_SLUB_TINY
    KMALLOC_RECLAIM = KMALLOC_NORMAL,
#else
    KMALLOC_RECLAIM,
#endif

#ifdef CONFIG_ZONE_DMA
    /* 对应着dma-kmalloc的内存 */
    KMALLOC_DMA,
#endif

#ifdef CONFIG_MEMCG_KMEM
    KMALLOC_CGROUP,
#endif

    NR_KMALLOC_TYPES
};

2.2.3 基本结构

在这里插入图片描述

  • kmem_cache数据结构代表一个slab 缓存
  • kmem_cache_cpu表示了每个 CPU 对象的缓存信息。它用于存储每个 CPU 上的缓存数组(array_cache)以及一些与 CPU 相关的缓存统计信息
  • array_cache用于表示该缓存在各个CPU中的slab对象
  • kmem_cache_node用于管理各个内存节点上slab对象的分配
    在这里插入图片描述
kmem_cache
struct kmem_cache {
    struct array_cache __percpu *cpu_cache;  //表示每个cpu中的slab对象
 
    unsigned int batchcount; 
    //当cpu_cache为空时,从缓存slab中获取的对象数目,它还表示缓存增长时分配的对象数目。
    //初始时为1,后续会调整
    
    unsigned int limit; 
    //cpu_cache中的对象数目上限
    //当slab free达到limit时,需要将array_caches中的部分obj返回到kmem_cache_node的页帧中
    
    unsigned int shared; //表示该缓存是否是共享的
 
    unsigned int size; //表示slab中的每个对象大小
    
    struct reciprocal_value reciprocal_buffer_size; //用于存储一个缓存的倒数大小的数据结构
 
    slab_flags_t flags; //用于存储常量标志的位掩码
    unsigned int num;   //每个slab中的对象数目
 
    unsigned int gfporder; //slab关联页数
 
    gfp_t allocflags;  //强制使用的 GFP 标志,例如 GFP_DMA
 
    size_t colour;   //缓存颜色范围
    unsigned int colour_off;  //颜色偏移量
    
    struct kmem_cache *freelist_cache; // 空闲对象管理
    unsigned int freelist_size; // 空闲对象数量
 
    //构造函数指针
    void (*ctor)(void *obj); //这个在2.6之后已经废弃了
 
    const char *name;      //缓存名称
    struct list_head list; //用于将缓存连接到全局缓存列表的链表节点
    int refcount;          //引用计数
    int object_size;       //对象的大小
    int align;             //对齐方式

#ifdef CONFIG_DEBUG_SLAB
    unsigned long num_active;       //活动对象的数量
    unsigned long num_allocations;  //分配的对象数量
    unsigned long high_mark;        //高水位标记
    unsigned long grown;            //已增长的对象数量
    unsigned long reaped;           //已收割的对象数量
    unsigned long errors;           //错误数量
    unsigned long max_freeable;     //最大可释放的空闲数量
    unsigned long node_allocs;      //节点分配数量
    unsigned long node_frees;       //节点释放数量
    unsigned long node_overflow;    //节点溢出数量
    atomic_t allochit;              //分配命中计数 
    atomic_t allocmiss;             //分配未命中计数
    atomic_t freehit;               //释放命中计数
    atomic_t freemiss;              //释放未命中计数
    
#ifdef CONFIG_DEBUG_SLAB_LEAK
    atomic_t store_user_clean;
#endif

    int obj_offset; //对象偏移量
#endif 
 
#ifdef CONFIG_MEMCG
    struct memcg_cache_params memcg_params; //用于内存控制组的参数
#endif

#ifdef CONFIG_KASAN
    struct kasan_cache kasan_info; //KASan 相关信息
#endif
 
#ifdef CONFIG_SLAB_FREELIST_RANDOM
    unsigned int *random_seq;  //用于slab freelist随机化的随机序列
#endif
 
    unsigned int useroffset;   //用户复制区域的偏移量
    unsigned int usersize;     //用户复制区域的大小
 
    struct kmem_cache_node *node[MAX_NUMNODES];  
    //每个内存节点上的slab对象信息,每个node上包括部分空闲,全满以及全部空闲三个队列
};
keme_cache_cpu
struct kmem_cache_cpu{
	void **freelist;        //指向下一个可用的object
	unsigned long tid;      //全局独一无二的事物ID
	struct page *page;      //slab内存的page指针

#ifdef CONFIG_SLUB_CPU_PARTIAL
	struct page *partial;   //本地slab partial链表,主要是一些部分使用object的slab
#endif
};
array_cache
  • array_cache是一个per_cpu数组,访问不需要加锁,是与cpu cache打交道的直接数据结构,每次获取空闲slab对象时都是通过entry[avail--]去获取,当avail==0时,又从kmem_cache_node中获取batchcount个空闲对象到array_cache中。
struct array_cache {
    unsigned int avail; //保存了当前array中的可用数目
    unsigned int limit; //同上
    unsigned int batchcount; //同上
    unsigned int touched; 
    //缓存收缩时置0,缓存移除对象时置1,使得内核能确认在上一次收缩之后是否被访问过
    void *entry[]; 
    //用于存储缓存的条目的数组,大小在运行时动态确定
};     
kmem_cache_node
  • kmem_cache_node用于管理slab(实际对象存储伙伴页帧),其会管理三个slab列表:
    • 部分空闲partial
    • 全部空闲empty
    • 全部占用full
  • array_cache获取batchcount空闲对象时,先尝试从partial分配,如果不够则再从empty分配剩余对象,如果都不够,则需要grow分配新的slab页帧。
struct kmem_cache_node {
    spinlock_t list_lock;       //自旋锁,用于保护缓存节点的链表操作
    unsigned long nr_partial;   //slab节点中slab的数量
    struct list_head partial;   //slab节点的slab partial链表
};
struct page
  • 用于描述slab页面,一个slab页面由一个或多个page组成,page页帧是物理存储地址

2.3 工作原理

2.3.1 对象分配和释放过程

  • 三个指针:

    • current指针,仅指向一个slab
    • partial指针,指向未满slab链表
    • full指针,指向全满slab链表
      在这里插入图片描述
  • 对象分配:使用current slab,若满,从partial指向的slab中取空闲区域,把current指向的slab移到full

      1. 查找合适的Slab Cache:当系统需要一个特定类型的对象时,首先在对应的Slab Cache中查找。
      1. 选择Slab:在找到的Slab Cache中,系统会寻找状态为部分满(Partial)或空(Empty)的Slab。优先选择部分满的Slab,以提高内存利用率。
      1. 分配对象:从选定的Slab中分配一个空闲对象。如果选择的是空Slab,系统会先初始化该Slab,然后分配对象。
      1. 更新Slab状态:分配对象后,更新Slab的状态。如果所有对象都被分配,Slab状态变为满(Full)。
  • 对象释放:若是full则移动到partial,若partial全空则还给buddy分配器

      1. 确定对象所属的Slab:释放对象时,系统首先确定该对象属于哪个Slab。
      1. 释放对象:将对象标记为未使用,返回到Slab的空闲对象池中。
      1. 更新Slab状态:如果释放对象前Slab是满的,则释放后状态变为部分满(Partial)。如果释放后Slab中所有对象都是空闲的,则状态变为空(Empty)。
      1. Slab的回收:如果一个Slab长时间处于空状态,系统可能会决定回收该Slab,释放内存给操作系统。

2.3.2 缓存色彩和内存对齐

  • 缓存色彩:
    • 缓存色彩是一种用于优化CPU缓存利用率的技术。由于CPU缓存行的存在,不同的内存地址可能会映射到同一个缓存行,这种现象称为缓存行冲突。缓存色彩通过在对象的内存地址上加上小的偏移量,使得连续分配的对象不会映射到相同的缓存行上,从而减少缓存行冲突,提高缓存的使用效率。
  • 内存对齐:
    • 内存对齐是指按照一定的边界来分配内存地址,使得数据的存取更加高效。在处理器架构中,对齐的内存访问通常比非对齐的内存访问速度要快。SLAB内存管理机制通过确保对象在内存中正确对齐,提高了数据访问的速度,减少了内存访问时间。
    • 内存对齐的另一个好处是减少了系统的内存碎片。

3. SLAB、SLUB和SLOB

  • SLAB分配器:适用于内存分配和释放频繁,且需要稳定内存使用的环境。它的设计注重减少内存碎片和提高内存利用率,非常适合长时间运行的服务或系统。
  • SLUB分配器:适用于对性能要求高的场景,特别是在多核处理器上。它的设计简化了内存管理的数据结构,减少了锁的竞争,优化了CPU缓存的使用,提供了高效的内存分配。
  • SLOB分配器:适用于内存资源非常有限的环境,如嵌入式系统或老旧的硬件。它的设计优先考虑内存的紧凑使用,尽可能减少内存的浪费。

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

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

相关文章

javaee前后端交互

1.选择Java Enterprise创建项目 2.勾选Web Profile 3.项目名称 4.创建包和类 5.继承HttpServlet并重写方法doGet和doPost 6.在web.xml里添加代码 7.点击Add Configuration,进去后点击加号 8.选择选项 9.调整如图&#xff0c;后选择Deployment进入 10.点击加号选择第一个 11.…

【InternLM 实战营第二期笔记】使用茴香豆搭建你的RAG智能助理

RAG RAG是什么 RAG&#xff08;Retrieval Augmented Generation&#xff09;技术&#xff0c;通过检索与用户输入相关的信息片段&#xff0c;并结合外部知识库来生成更准确、更丰富的回答。解决 LLMs 在处理知识密集型任务时可能遇到的挑战, 如幻觉、知识过时和缺乏透明、可追…

【vue】v-bind动态属性绑定

v-bind 简写:value <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><styl…

【赛题】2024年“认证杯”数模网络挑战赛赛题发布

2024年"认证杯"数学建模网络挑战赛——正式开赛&#xff01;&#xff01;&#xff01; 赛题已发布&#xff0c;后续无偿分享各题的解题思路、参考文献、完整论文可运行代码&#xff0c;帮助大家最快时间&#xff0c;选择最适合是自己的赛题。祝大家都能取得一个好成…

Python如何安装第三方模块

cmd窗口中使用pip install命令安装 1、键盘按下win R&#xff0c;然后在输入框中输入cmd&#xff0c;回车&#xff0c;就打开了cmd窗口。 下图的运行框会出现到屏幕左下角。 2、输入下面的命令&#xff0c;回车即可。 pip install xxx # xxx为要安装的模块名 如图所示&…

线程池参数如何设置

线程池参数设置 hello丫&#xff0c;各位小伙伴们&#xff0c;好久不见了&#xff01; 下面&#xff0c;我们先来复习一下线程池的参数 1、线程池参数有哪些&#xff1f; corePoolSize&#xff08;核心线程数&#xff09;&#xff1a;线程池中的常驻核心线程数。即使这些线程…

AI大模型日报#0411:国内首款音乐大模型、面壁智能数亿融资、MyScale AI开源

导读&#xff1a; 欢迎阅读《AI大模型日报》&#xff0c;内容基于Python爬虫和LLM自动生成。目前采用“文心一言”生成了每条资讯的摘要。 ​标题: 大模型做时序预测也很强&#xff01;华人团队激活LLM新能力&#xff0c;超越一众传统模型实现SOTA 摘要: 大语言模型通过新提…

Vue结合el-table实现合并单元格(以及高亮单元表头和指定行)

实现效果如下&#xff1a; 思路&#xff1a; 1.首先使用动态表头表格。 2.其次实现动态计算合并单元格。&#xff08;计算规则 传递需要合并的字段&#xff09; 3.然后封装公共的计算单元格方法 export导出供多个页面使用。 4.同时需要封装成公共的组件供多个页面使用。 5…

PostgreSQL入门到实战-第十九弹

PostgreSQL入门到实战 PostgreSQL中表连接操作(三)官网地址PostgreSQL概述PostgreSQL中INNER JOIN命令理论PostgreSQL中INNER JOIN命令实战更新计划 PostgreSQL中表连接操作(三) 使用PostgreSQL INNER JOIN子句从多个表中选择数据。 官网地址 声明: 由于操作系统, 版本更新等…

Innodb架构解析

整体架构 通过《面试官&#xff1a;一条SQL是如何执行的&#xff1f;》我们了解了MySQL架构&#xff0c;下面我们看下Innodb架构。 innodb最早由Innobase Oy公司开发&#xff0c;5.5版本开始是MySQL默认存储引擎&#xff0c;该存储引擎是第一个完整支持ACID事务的MySQL存储引…

电子元器件商城开发用什么技术框架?

随着信息技术的飞速发展&#xff0c;电子元器件商城已成为电子工程师和采购人员获取元器件的重要渠道。电子元器件商城的开发涉及众多技术和开发语言的选择&#xff0c;本文将详细分析电子元器件商城开发中常用的技术和开发语言&#xff0c;以及它们各自的优势。 一、电子元器…

“我哭死!用ChatGPT完成的硕士论文被评不及格……”

我隔壁专业用ChatGPT写的论文被老师判不及格了&#xff0c;大家还是慎用吧&#xff01; 匿名 自从去年11月份ChatGPT面世以来&#xff0c;因为它天然适合撰写学术论文&#xff0c;越来越多的同学开始使用它辅助论文写作。 学习写作有所谓的鲁迅体、莫言体、余华体&#xff0c;但…

从头开发一个RISC-V的操作系统(三)编译与链接

文章目录 前提GCCGCC简介GCC的主要执行步骤GCC涉及的文件类型 ELFELF简介ELF文件格式ELF文件处理工具&#xff1a;Binutils 练习参考链接 目标&#xff1a;通过这一个系列课程的学习&#xff0c;开发出一个简易的在RISC-V指令集架构上运行的操作系统。 前提 这个系列的大部分文…

[StartingPoint][Tier2]Vaccine

Task 1 Besides SSH and HTTP, what other service is hosted on this box? (除了SSH和HTTP&#xff0c;这个盒子上还托管了什么其他服务) # nmap -sS -T4 10.129.230.43 --min-rate 1000 ftp Task 2 This service can be configured to allow login with any password fo…

SAP HCM get pernr无法查询到主数据

今天遇到一个比较奇怪的问题&#xff0c;就是ger pernr在2月的时候能找到员工主数据&#xff0c;但是在3月的时候无法找到员工主数据。首先SE36&#xff1a;逻辑数据库页面&#xff0c;看看标准逻辑数据库执行&#xff0c;是否能获取数据。 从上述标准的逻辑书而言&#xff0c;…

Linux操作系统的学习

Linux系统的目录结构 / 是所有目录的顶点目录结构像一颗倒挂的树 Linux常用命令 常见命令 序号命令对应英文作用1lslist查看当前目录下的内容2pwdprint work directory查看当前所在目录3cd [目录名]change directory切换目录4touch [文件名]touch如果文件不存在&#xff0c;新…

深度学习每周学习总结P4(猴痘识别)

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 –来自百度网盘超级会员V5的分享 目录 0. 总结1. 数据导入部分2. 划分数据集3. 模型构建部分3.1 模型构建3.2 公式推导 4. 设置超参数5. …

HTTP请求报文介绍

本章简要介绍渗透测试员在攻击Web应⽤程序时可能遇到的关键技术。 将分析HTTP协议、服务器和客⼾端常⽤的技术以及⽤于在各种情形下呈现数据的编码⽅案。 这些技术⼤都简单易懂&#xff0c;掌握其相关特性对于向Web应⽤程序发动有效攻击极其重要。 1.1 HTTP协议概述介绍 HTT…

【学习笔记】R语言入门与数据分析1

数据分析 数据分析的过程&#xff1a; 数据采集 数据存储 数据分析 数据挖掘 数据可视化 进行决策 数据挖掘 数据量大 复杂度高&#xff0c;容忍一定的误差限 追求相关性而非因果性 数据可视化 直观明了 R语言介绍 R是免费的&#xff08;开源软件、扩展性好&#xff09;…

每天学点儿Python(6) -- 列表和枚举

列表是Python中内置的可变序列&#xff0c;类使用C/C中的数组&#xff0c;使用 [ ] 定义列表&#xff0c;列表中的元素与元素之间用英文逗号&#xff08; , &#xff09;分隔&#xff0c; 但是Python中列表可以存储任意类型的数据&#xff0c;且可以混存&#xff08;即类型可以…