内存管理-15-slab、slob和slub分配器-初探


一、slab简介

1. 简介

首先,“slab”已成为一个通用名称,指的是使用对象缓存的内存分配策略,可实现内核对象的高效分配和释放。它最初由 Sun 工程师 Jeff Bonwick 记录下来,并在 Solaris 2.4 内核中实现。

Linux 目前为其“slab”分配器提供了三种选择:

Slab 是最初的分配器,基于 Bonwick 的开创性论文,自 Linux-2.2 开始可用。它是 Bonwick 提案的忠实实现,并由 Bonwick 的后续论文2 中描述的多处理器更改进行了增强。

Slub 是下一代替代内存分配器,自 Linux-2.6.23 以来一直是内核中的默认分配器。它继续采用基本的“slab”模型,但修复了 Slab 设计中的几个缺陷,特别是在具有大量处理器的系统周围。Slub 比 Slab 更简单。

Slob(简单块列表)是一种针对内存非常少(大约兆字节)的嵌入式系统进行优化后的内存分配器。它对块列表应用非常简单的首次适合算法,与旧的 K&R 式堆分配器类似。Slob 消除了内存分配器中几乎所有的过载,非常适合内存极其受限的系统,但它不提供 Slab/Slub 中描述的任何好处,并且可能会出现病态碎片。

2. 概述

slab机制一般和伙伴系统配合使用,它是对伙伴系统的改进和补充。伙伴系统对物理内存的管理是以page为单位的,粒度比较大。slab是将页拆分成小内存块进行管理。slab首先通过伙伴系统的接口向伙伴系统申请一个或多个物理页,然后将其切割成固定大小的块,缓存起来,当分配此大小的内存块的时候,直接返回缓存的内存块,用户释放时,会释放给slab继续缓存起来。slab中的内存块使用链表链起来。对于常用的结构体,比如 task_struct,可以提前创建slab缓存,使用时直接去拿即可。

有3种类似的分配器,slab、slob、slub

slab: 是内核中最早出现的分配器,随着内核的发展,其实现越来越臃肿,
slob: 面向小型嵌入式系统很少内存的简单管理,比如32MB.
slub: 是一个轻量级的slab,一般使用在嵌入式系统中。

这三种可以通过内核deconfig进行配置,配置不同结构体定义和函数走的分支不同。

CONFIG_SLAB //没使能
CONFIG_SLOB //没使能
CONFIG_SLUB //使能
CONFIG_SLUB_DEBUG //使能
CONFIG_SLUB_CPU_PARTIAL //使能

msm-4.14 和 msm-5.4 都是这样的配置。


二、相关数据结构

kmem_cache
kmem_cache_cpu //每cpu变量
kmem_cache_node

struct kmem_cache_cpu {
    /* 每个内存块内部也占用一部分空间,作为一个指针指向下一个内存块,组成一个单链表,
     * 将所有的空闲内存块链接起来。此变量时刻指向下一个可用的内存块。
     */
    void **freelist;
}

struct kmem_cache_node {
    /* 上面串联的都是大小相同的内存块 */
    struct list_head partial;
}

如下,通过这三个结构体就把slab的框架给搭建起来了。

kmem_cache 每个大小的slab内存块都会使用一个 kem_cache 进行管理。所有的 kmem_cache 使用一个双循环链表链接起来,链表头是全局变量 slab_caches。申请内存时,通过这个全局链表头就可以找到要申请大小对应的 kmem_cache,进而找到其 partial 链表,拿到对应大小的内存块。

kmem_cache_cpu 是个每cpu的变量,每个cpu都缓存一部分slab, 也就是一个page,这样就可以避免链表访问拿锁。当这个page上的slab内存块被使用完了后,重新通过伙伴系统申请一个page来使用。

当申请slab缓存的时候,优先从cpu本地缓存上申请,即先从 kmem_cache_cpu 的 page 内申请,没有的话再看 partial 链表上是否有,若也没有的话才会去全局链表上去申请,若全局链表上也没有,则会向伙伴系统申请物理页再次创建slab缓存


三、相关文件节点

1. /proc/slabinfo

/proc/slabinfo  里面的 <pagesperslab> 表示每个slab占几个物理页。


四、编程接口

/* 创建一个 kmem_cache, 并对每一个内存块调用ctor回调 */
struct kmem_cache *kmem_cache_create(const char *name, unsigned int size, unsigned int align, slab_flags_t flags,
            void (*ctor)(void *)) //slab_common.c
struct kmem_cache *kmem_cache_create_usercopy(const char *name, unsigned int size, unsigned int align,
            slab_flags_t flags, unsigned int useroffset, unsigned int usersize,
            void (*ctor)(void *)) //slab_common.c TODO: 看它两个的区别,视频上说,用户空间经常使用

/* 销毁一个 kmem_cache */
void kmem_cache_destroy(struct kmem_cache *s) //slab_common.c

/* 从 kmem_cache 对应的slab中分配一个object */
void *kmem_cache_alloc(struct kmem_cache *s, gfp_t gfpflags) //slub.c

/* 释放object给原先的slab */
void kmem_cache_free(struct kmem_cache *s, void *x) //slub.c

slab_flags_t 比如 SLAB_CACHE_DMA
ctor: 当创建完kmem_cache后,会回调这个函数,可以在这个函数中进行结构体成员初始化,当一次性创建多个slab块时,此函数会多次针对每个块都进行调用。

若是需要频繁申请释放一个大小的内存块,你也可以使用 kmem_cache_create 创建一个slab.

注意:写上标志的作用。注意这几个机制的文件是如何组织的


五、实验

1. 创建slab缓存

#include <linux/slab.h>

/* 32B */
struct student {
    int id;
    int age;
    float score;
    void (*print_score)(int id);
    void (*print_age)(int id);
};

struct mm_test_2 {
    int unique_id;
    struct kmem_cache *stu_c;
    struct student *stu_p;
};

struct mm_test_2 mt2;

void stu_init(void *p) {
    struct student *stu_p = p;
    stu_p->id = ++mt2.unique_id;
    pr_info("%s called, stu_p->id=%d\n", __func__, stu_p->id); //打印从1--22
}

static void mem_test_2_init(void)
{
    mt2.stu_c = kmem_cache_create("student", sizeof(struct student), 0, SLAB_PANIC, stu_init);
    if (!mt2.stu_c) {
        pr_info("%s NOMEM\n", __func__);
        return;
    }
    mt2.stu_p = kmem_cache_alloc(mt2.stu_c, GFP_KERNEL);
    if (mt2.stu_p) {
        pr_info("sizeof(*mt2.stu_p)=%d, sizeof(struct student)=%d\n", sizeof(*mt2.stu_p), sizeof(struct student)); //32 32
    }
}

static void mem_test_2_exit(void)
{
    kmem_cache_free(mt2.stu_c, mt2.stu_p);
    kmem_cache_destroy(mt2.stu_c);
}

实验结果:

# dmesg -c | grep mm_test
[  221.397263] (3)[6366:sh]mm_test: stu_init called, stu_p->id=1
[  221.397429] (3)[6366:sh]mm_test: stu_init called, stu_p->id=2
...
[  221.401152] (1)[6366:sh]mm_test: stu_init called, stu_p->id=22
[  221.401332] (1)[6366:sh]mm_test: sizeof(*mt2.stu_p)=32, sizeof(struct student)=32

# cat /proc/slabinfo | grep student
slabinfo - version: 2.1
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
student                1     22    368   22    2 : tunables    0    0    0 : slabdata      1      1      0

TODO: objsize 为啥差那么多(32/368)?

 

 

参考:
图解slub:http://www.wowotech.net/memory_management/426.html //蜗蜗 TODO
linux内核之slob、slab、slub: https://blog.csdn.net/Rong_Toa/article/details/106440497 //优秀,有英文文档 TODO

 

posted on 2024-07-06 10:20  Hello-World3  阅读(460)  评论(0编辑  收藏  举报

导航