Linux Mem -- Slub内存分配器基础

发布于:2025-07-03 ⋅ 阅读:(20) ⋅ 点赞:(0)

目录

1.为什么会有slub?

2.数据结构与数据

        2.1 相关数据结构

        2.2 数据

3.初始化

4.分配和释放

        4.1 分配

        4.2 释放

5.slub内存debug

        5.1 slub debug

        5.2 kmem_cache_cpu->stat

        5.3 slubinfo

        5.4 slub trace


 

 

        继前两篇文章之后再来梳理slub,本篇将聚焦于slub的基础面。将从slub相关的数据结构、slub的初始化、slub空间的分配和释放及slub的debug等方面对slub进行一次学习整理。

 

1.为什么会有slub?

        与slub相应的早期版本有slab和slob,都是为了解决小内存分配效率与内存碎片化问题。slub的又进一步优化了slab的分配的性能瓶颈和扩展性问题。

2.数据结构与数据

        2.1 相关数据结构

        要处理数据先要知道其对应的数据结构,slub内存管理器相关的数据结构如下图。

        与slub相关的重要数据结构有:strcut kmem_cache、struct kmem_cache_cpu、strcut kmem_cache_node 、struct slab。如上图struct kmem_cache为一个kmem_cache的总结构,通过此结构可以确认kmem_cahce对应的kmem_cache_cpu 和 kmem_cache_node,进而确认其对应的slub。

struct kmem_cache {

    //该kmem_cache对应的kmem_cache_cpu,为precpu变量
struct kmem_cache_cpu __percpu *cpu_slab;

//kmem_cache->kmem_cache_node->partial中应该保留的slab最小个数
unsigned long min_partial;

//size = object_size + 对齐填充 + 调试元数据 + 其他管理开销
//size : 包含了保护、对齐、调试 和 用户申请的空间大小
unsigned int size;                /* Object size including metadata */

//object_size 用户实际请求的内存空间
unsigned int object_size;        /* Object size without metadata */

//一下个object地址保存在上一个object空间的偏移位置
unsigned int offset;                /* Free pointer offset */

//该kmem_cache关联的每个CPU的kmem_cache_cpu中应保留的object数量
unsigned int cpu_partial;

//该kmem_cache关联的每个CPU的kmem_cache_cpu中应保留的slab数量
unsigned int cpu_partial_slabs;

//bit16-bit31:基于page order的slub大小, bit15-bit0:为每个slab中obejct的数量
struct kmem_cache_order_objects oo;

//对分配到的空间进行初始化,ctor为申请空间时要传入函数
void (*ctor)(void *object);        /* Object constructor */

//整体空间中metadata的偏移,是基于object起始(不包含red zone)空间的偏移
unsigned int inuse;                /* Offset to metadata */

//将此kmem_cache结构链接到slab_cahces
struct list_head list;                /* List of slab caches */

//该kmem_cache对应的kmem_cache_node,系统中每个node都会有对应的kmem_cache_node
struct kmem_cache_node *node[MAX_NUMNODES];

…
}
struct kmem_cache_cpu {
union {

    struct {
    //freelist : 指向kmem_cache_cpu->slab中第一个空闲的object
    void **freelist;        /* Pointer to next available object */

    unsigned long tid;        /* Globally unique transaction id */
    };

    freelist_aba_t freelist_tid;
};
//kmem_cache_cpu->freelist所属的slab,该slab来自kmem_cache_cpu->partial
struct slab *slab;        /* The slab from which we are allocating */

//给kmem_cache_cpu->slab分配slab
struct slab *partial;        /* Partially allocated slabs */

#ifdef CONFIG_SLUB_STATS
//slab状态,应用slab debug。后续具体介绍
unsigned int stat[NR_SLUB_STAT_ITEMS];
#endif
…
}
struct kmem_cache_node {
//表示该node中有多少个slab,即slab_list链表slab的数量
unsigned long nr_partial;

//partial 链接 slab->slab_list
struct list_head partial;
…
};
struct slab {
//指向拥有该slab的kmem_cache
struct kmem_cache *slab_cache;
    union {
        struct {
            union {
                struct list_head slab_list;
                #ifdef CONFIG_SLUB_CPU_PARTIAL
                struct {
                    struct slab *next;

                    //该slab缓存中剩余的未被使用额slab数量(主要是slab数量,非object数量)
                    int slabs;        /* Nr of slabs left */
                 };
                #endif
             };
    /* Double-word boundary */
    union {
        struct {
            //应该指向kmem_cache_cpu->slab->freelist中第一个未分配的object。
            void *freelist;                /* first free object */
            union {
                unsigned long counters;
                struct {

                    //表示有多少个object已经被使用
                    unsigned inuse:16;

                    //objects : slab的object的个数
                    unsigned objects:15;
    
                    unsigned frozen:1;
                };
...

        2.2 数据

        与slub有关的几个系统变量:

                LIST_HEAD(slab_caches) ,slab_caches为链表结构,系统所有的struct kmem_cache结构变量都会通过其list链接到给链表;故通过slab_caches可以遍历系统中所有的kmem_cache数据。

                struct kmem_cache *kmem_cache ,该kmem_cache变量为系统中所有kmem_cache结构变量提供空间。

                static nodemask_t slab_nodes ,与系统中memory node对应,为slab记录当前系统哪些node是active状态,对应node的kmem_cahce_node可操作。

                static struct kmem_cache *kmem_cache_node,该kmem_cache_node变量为系统所有kmem_cache->kmem_cache_node[x]提供空间。

                struct kmem_cache * kmalloc_caches[NR_KMALLOC_TYPES]                                       [KMALLOC_SHIFT_HIGH + 1] ,kmalloc_caches是依据kmalloc_type和object size进行区分的kmem_cache的数组变量。

                const struct kmalloc_info_struct kmalloc_info[],kmem_cache对应的 kmem_cache->name。

3.初始化

        slub的初始化主要由kmem_cache_init完成,如上图kmem_cache_init函数对于slub的初始化可以划分为3部分。

      a. kmem_cache 、kmem_cache_node变量初始化

        通过在函数中声明init空间数据boot_kmem_cache、boot_kmem_cache_node,并将boot_kmem_cache、boot_kmem_cache_node做为kmem_cache基础变量,完成其对应的元数据初始化和其指向的object空间分配,最后通过bootstrap函数分别将boot_kmem_cache、boot_kmem_cache_node变量数据分别copy到kmem_cache、kmem_cache_node。

       bootstrap,该函数实现kmem_cache空间结构数据的copy,因为在kmem_cache未创建时首个kmem_cache结构的变量来自init数据区域的boot_kmem_cache,该区域在内核初始化完成后会被释放,所以需要对该变量的数据进行copy。具体实现:从boot_kmem_cache中分配一个object,将该object空间转化为struct kmem_cache结构空间,将入参的kmem_cahce结构数据copy到分配object的kmem_cache结构空间,并更新该kmem_cache对应的slab->kmem_cache指向该kmem_cache。

      b.kmalloc_caches初始化

      kmalloc_cache数组的初始化由create_kmalloc_caches函数完成。该函数会遍历系统支持的kmalloc type和 需要预分配的object大小,通过create_kmalloc_cache函数为每一个系统支持的kmalloc type 和object size创建对应的kmem_cache空间。

      c.slub相关系统机制初始化

      init_freelist_randomization object序列随机化处理,是指将原本slab空间顺序划分的object随机的打乱排序,随机化的目的在于保护slab空间内容,防止因顺序性object的顺序性导致object空间的易推理,导致object空间被恶意访问。 slub中还有一个object地址的混淆处理机制,即当前object的kmem_cache->offset偏移地址空间保存的不是下一object的起始地址的明文地址。

     hotplug_memory_notifier 通过该函数注册内存热插拔事件的slab回调处理函数slab_memory_callback。MEM_GOING_ONLINE:内存热插入事件,通过slab_caches遍历系统中所有的kmem_cache,为kmem_cache分配创建该node对应的kmem_cache_node[node]数据。MEM_GOING_OFFLINE:内存热移除事件,首先将kmem_cache_cpu->slab 和 kmem_cache_cpu->partial 所指向的slab添加到kmem_cache_node->partial,然后将kmem_cache_node[热移除内存node]->partial中未分配使用过的slab进行释放。

     cpuhp_setup_state_nocalls 通过函数注册CPU热移除时slab的回调处理函数slub_cpu_dead。CPUHP_SLUB_DEAD CPU热移除事件,通过slab_caches遍历系统所有的kmem_cache,将系统所有kmem_cache->kmem_cache_cpu[移除的cpu]中的object移到其对应的kmem_cache_node中。

        slub初始化完成时,除了kmem_cache、kmem_cache_node的kmem_cache_cpu中有object外,系统中其他kmem_cache的kmem_cache_cpu不具有object如上图。

4.分配和释放

        4.1 分配

       slub机制中提供的内存分配函数较多,此处以最基础的kmalloc为来说明slub内存分配处理流程。在kmalloc函数处理流程中主要看__do_kmalloc_node子流程部分该部更能够体现slub分配内存机制。

        __do_kmalloc_node函数先根据要申请的内存大小和申请的内存属性、调用kmalloc_slab函数确认从哪个kmem_caches中分配object空间。确认kmem_cache后再通过__slab_alloc_node函数获取kmem_cache对应的空闲object给申请者使用。__slab_alloc_node函数是slub内存分配的核心函数,slub对外提供的所有内存分配函数最终都会调用到__slab_alloc_node函数。

       __slab_alloc_node函数对于slub内存分配分为两种策略:快速分配和慢速分配。

      所谓快速分配是指从kmem_cache->kmem_cache_cpu->freelist获取object,获取后kmem_cache->kmem_cache_cpu->freelist将指向next_object;因为是从当前cpu变量kmem_cache_cpu获取object所以可以不考虑竞态问题,故此不需要通过kmem_cache_cpu->lock保护,所以称之为快速获取。

      通过其他slub链路获取object的方式称为慢速分配,慢速分配路径:

            a.先从kmem_cache->kmem_cache_cpu->slab获取,将该slab->freelist第一给object做为分配空间提供给申请者,将kmem_cache->kmem_cache_cpu->freelist指向next_object;

            b.若kmem_cache->kmem_cache_cpu->slab不存在可用的object时,则从kmem_cache->kmem_cache_cpu->partial的slab链表中获取slab,并将给slab赋值给kmem_cache->kmem_cache_cpu->slab,然后执行a操作;

            c.若kmem_cache->kmem_cache_cpu->partial中不存在可用slab时,则从kmem_cache->kmem_cache_node->partial的slab链表中获取符合要求slab,然后执行a操作。

            d.若kmem_cache->kmem_cache_node->partial中不存在可用的slab时,在调用new_slab函数从系统page buddy中分配slab,然后执行a操作。

      慢速分配不仅仅是要最层获取可用slab、操作复杂,更在于其大部分操作逻辑需要使用kmem_cache_cpu->lock保护,因为所得互斥性导致性能降低、分配较为耗时,所有称之为慢速分配。

        4.2 释放

        前面关于slub思考的章节中有提到过slub释放的关键函数kfree,有介绍object释放时怎样确认其所示的slab即所属的page空间。这一段将更重点介绍slub的object释放时,slbu自身内部的处理机制。与分配时相同,释放过程也分为两部分:快速释放和慢速释放。

        有了前面快速分配的经验,应该能够猜到快速释放即意味着将要释放的object添加到kmem_cahce->kmem_cache_cpu->slab所以指向的object链表,同样过程可以不需要kmem_cache_cpu->lock的保护。但需要注意的是不是所有的object都可以添加到kmem_cache->kmem_cache_cpu->slab的object链表,要添加到此链表需要满足条件:释放的object属于当前执行释放cpu的kmem_cache_cpu->slab。

        慢速释放,按照常规释放逻辑思考只需要找到要释放object对应的slab,将要释放的object添加到其对应的slab的freelist即可。但是slub的慢速释放有更多逻辑要考虑:a.slub链表层次 b.slub链接每层的slub数量限制。

        当要释放的object释放的slab时,该slab可能存在三种情况:a.slab中的object全部未被分配,即当前slub是empty状态;b.释放到slab的object为slub唯一未被分配出去的object,即释放前slub为full状态;c.slab中的object有部分被分配处理,即slab为partial状态。当前slub为patital状态,不需要考虑前面提到的slub层次和层次限制的问题,object释放后其所属的slub归哪个层次其slub还是属于哪个层次;而full和empty的slub需要考虑这些限制。

       当释放object其所属的slub为full状态时,则该slub已经不存在于kmem_cache->kmem_cache->partial 和 kmem_cache->kmem_cache_node->partial的任何一个slub链表,所以需要考虑将该slub添加进slub链表,slub机制中会考虑将此类slub添加进kmem_cache->kmem_cache_cpu->partial slub链表,以达到管理slub目的(否则该slub内存空间会是一段leak memory)(为什么要添加到kmem_cache_cpu->partial而不添加到kmem_cache_node->partial? 在内存资源ok情况下,可加速slub内存分配速度,当然也有些同之前object释放类似新释放的object排在freelist链表的头部的做法:热内存持续热)。当kmem_cache_cpu->partial slub链表中加入新的slub可能会导致kmem_cache_cpu->partial 拥有的slub数量大于kmem_cahce对于kmem_cache_cpu->partial中最大slub数量(kmem_cache->cpu_partial_slabs)要求,导致触发kmem_cache_cpu->partial slub链表中的slub进一步向低层次的kmem_cache_node->partial链表中释放。

        object释放后所属的slub为empty状态时,分两种情况:slub为empty且kmem_cache_node->nr_partial 大于kmem_cache对于kmem_cache_node->partial中最小数量要求,则该slub会从原slub链表中移除释放到page buddy。另一种情况仅slub为empty状态,则该slub还属于原slub链表(可能是kmem_cache_cpu->partial 或者 kmem_cache_node->partial)。

5.slub内存debug

        5.1 slub debug

       继承内核一贯的debug机制,slub debug也由宏空 + cmdline的形式组成,如下表是slub debug和cmdline整理。

        5.2 kmem_cache_cpu->stat

        在前面数据结构的介绍中有提到kmem_cache_cpu->stat变量,该变量来表示当前CPU本地内存分配路径,可以通过该状态的统计量来说明CPU本地slub的持有情况,进而决定是否要对slub的关键参数进行调整,来优化系统slub的内存分配效率。如下是kmem_cache_cpu->stat对应的状态的意义。

FREE_ADD_PARTIAL

要释放的slab被添加到kmem_cache->kmem_cache_node

Free_slab

要释放的slab被释放到page buddy

DEACTIVATE_EMPTY

表示kmem_cache_node中的slab数量满足kmem_cache最小要求,且当前要释放的slab中所有object未被使用

FREE_FASTPATH

有释放slab被释放到kmem_cache->cpu_slab->slab

FREE_REMOVE_PARTIAL

将slab从kmem_cache_node链表中移除

FREE_SLOWPATH

进入慢释处理路径

CPU_PARTIAL_DRAIN

将要归到kmem_cache_cpu->partial的slab移动要kmem_cache_node。例如在object释放过程中将要归类到kmem_cache_cpu->partial的slab,因为kmem_cache_cpu->partia中slabs数量大于kmem_cache对于slab缓存中slab最小要求数cpu_partial_slabs,所以将要释放的slab释放到kmem_cache_node中

ALLOC_FASTPATH

从快速路径分配object

ALLOC_SLOWPATH

从慢速路径分配object

FREE_ADD_PARTIAL

将slab从kmem_cache_cpu->partial添加到kmem_cache->kmem_cache_node

ALLOC_FROM_PARTIAL

从kmem_cache_node->partial中分配slab

CPU_PARTIAL_NODE

将kmem_cache_node->partial的slab移到kmem_cache_cpu->partial

DEACTIVATE_EMPTY

待吊销处理slab中的object未被分配使用即为空,吊销:指将slab由高阶转为低阶、高阶->低阶:kmem_cache_cpu ->kmem_cache_node

DEACTIVATE_TO_TAIL

将代销的slab添加到kmem_cache_node->partial的尾部

DEACTIVATE_TO_HEAD

将代销的slab添加到kmem_cache_node->partial的头部

DEACTIVATE_REMOTE_FREES

待吊销的slab中包含未被使用的object

DEACTIVATE_FULL

在吊销处理过程,待吊销的slab中的object全被分配使用,即为满(Full),object全被分配出去

ALLOC_NODE_MISMATCH

分配object过程时,指定的node与kmem_cache_cpu->slab内存所在的node不一致

ALLOC_SLAB

指从page buddy alloc中分配slab

CPUSLAB_FLUSH

放弃kmem_cache_cpu->slab,指将该slab做吊销处理

DEACTIVATE_BYPASS

跳过吊销处理

ALLOC_REFILL

从kmem_cache_cpu->slab中分配object

CPU_PARTIAL_FREE

将slab释放到kmem_cache_cpu->partial

ORDER_FALLBACK

在从page buddy中分配slab首次成功后在此分配失败时设置此标识

CMPXCHG_DOUBLE_CPU_FAIL

执行this_cpu_cmpxchg_double失败

CMPXCHG_DOUBLE_FAIL

更新slab->freelist信息失败

        5.3 slubinfo

                5.3.1 /proc/slabinfo关键字段解释

                active_objs : kmem_cache->kmem_cache_node上所有node已使分配出去的object数                     量,该数据可能为概数(因为对于kmem_cache_node上free_object的统计可能进行了                   估算)

                num_objs: kmem_cache->kmem_cache_node上所有node总计object数量

                objsize: kmem_cache->size object空间大小,包含了meta数据

                objperslab: kmem_cache中每个slab用于的object数量

                pagesperslab: kmem_cache中每个slab的page order

                tunables : 为slab分配器数据,slub分配器未使用

                active_slabs、num_slabs :数据相同代表kmem_cache中总计的slab数量

注意:slub分配器时,/proc/slabinfo节点仅统计slab_caches链表上每个kmem_cache中node的kmem_cache_node的slab和object情况,并未包含kmem_cache中每个CPU的kmem_cache_cpu的slab和object情况。

 

                5.3.2  slabinfo tool

                代码内核路径:tools/vm/slabinfo.c

                开启slub debug和 cmdline情况下,在结合slabinfo可以很好的debug slub。此处不做               介绍。

        5.4 slub trace

                针对Android产品可通过adb命令开启slub trace功能,抓取slub trace查看slub内存分配              和释放情况。

 

        对于slub的学习到此告段落,很高兴又可以进入新的模块旅行。自我认知:模块代码学习重点在于对流程和数据掌握,流程的目标在于控制和数据传递。

 


网站公告

今日签到

点亮在社区的每一天
去签到