Day44-回顾slab分配器

slab是通用的分配器;

slob针对微小的嵌入式系统,其算法较简单(最先适配算法);

slub是面向配备大量物理内存的大规模并行系统。

Slab分配器内容概述

slab描述符创建概览

创建slab描述符

kmem_cache数据结构

kmem_cache数据结构是slab分配器中的核心数据结构,成为slab描述符。定义在

struct kmem_cache {    unsigned int object_size;//对象的实际大小    unsigned int size;  //对象的长度,该长度已经加上对齐字节    unsigned int align; //对齐的长度    slab_flags_t flags; //对象的分配掩码    unsigned int useroffset;//usercopy区域的偏移量    unsigned int usersize;  //usercopy区域的大小    const char *name;   //slab描述符的名称    int refcount;       //表示该slab描述符的引用计数,当创建其他slab需要引用该slab时,增加引用计数值    void (*ctor)(void *);   //构造函数    struct list_head list;  //链表节点,用于把slab描述符添加到全局链表slab_caches中};

array_cache数据结构

该数据结构用于描述本地对象缓冲池,也可以用于描述共享对象缓冲池,定义在

struct array_cache {    unsigned int avail; //对象缓冲池中可用的对象数目    unsigned int limit; //对象缓冲池可用对象数目的最大阈值    unsigned int batchcount; //迁移对象的数目,如从共享对象缓冲池或者其他slab中迁移空闲对象到该对象缓冲池的数量    unsigned int touched; //表示该对象缓冲池最近使用过    //entry[],指向存储对象的变长数组,每个成员存放一个对象的指针,这个数组最初最多有limit个成员    void *entry[];  };

kmem_cache_create()函数

kmem_cache_create()实现

//name:要创建的slab对象名称,会显示在/proc/slabinfo中//size:slab对象的大小//align:slab对象的对齐要求//flags:slab分配器的分配掩码和标志位//ctor:对象的构造函数struct kmem_cache *kmem_cache_create(const char *name, unsigned int size, unsigned int align,        slab_flags_t flags, void (*ctor)(void *)){    return kmem_cache_create_usercopy(name, size, align, flags, 0, 0,                      ctor);}EXPORT_SYMBOL(kmem_cache_create);

kmem_cache_create_usercopy()函数

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 *)){    struct kmem_cache *s = NULL;    const char *cache_name;    int err;    get_online_cpus();    get_online_mems();    memcg_get_cache_ids();    //创建slab对象描述符时需要申请一个名为slab_mutex的互斥量进行保护    mutex_lock(&slab_mutex);    //kmem_cache_sanity_check做必要检查    err = kmem_cache_sanity_check(name, size);    if (err) {        goto out_unlock;    }    if (flags & ~SLAB_FLAGS_PERMITTED) {        err = -EINVAL;        goto out_unlock;    }    //用于约束创建slab描述符的标志位,定义在<mm/slab.h>    flags &= CACHE_CREATE_MASK;    /* Fail closed on bad usersize of useroffset values. */    if (WARN_ON(!usersize && useroffset) ||        WARN_ON(size < usersize || size - usersize < useroffset))        usersize = useroffset = 0;    if (!usersize)        //__kmem_cache_alias函数查找是否有现成的slab描述符可以复用        //如果有,则直接跳转到out_unlock标签处        s = __kmem_cache_alias(name, size, align, flags, ctor);    if (s)        goto out_unlock;    //重新创建分配一个缓存区存放slab描述符的名称    cache_name = kstrdup_const(name, GFP_KERNEL);    if (!cache_name) {        err = -ENOMEM;        goto out_unlock;    }    //调用create_cache函数创建slab描述符    s = create_cache(cache_name, size,             calculate_alignment(flags, align, size),             flags, useroffset, usersize, ctor, NULL, NULL);    if (IS_ERR(s)) {        err = PTR_ERR(s);        kfree_const(cache_name);    }out_unlock:    mutex_unlock(&slab_mutex);    memcg_put_cache_ids();    put_online_mems();    put_online_cpus();    if (err) {        if (flags & SLAB_PANIC)            panic("kmem_cache_create: Failed to create slab '%s'. Error %d\n",                name, err);        else {            pr_warn("kmem_cache_create(%s) failed with error %d\n",                name, err);            dump_stack();        }        return NULL;    }    return s;}EXPORT_SYMBOL(kmem_cache_create_usercopy);

create_cache()函数

static struct kmem_cache *create_cache(const char *name,        unsigned int object_size, unsigned int align,        slab_flags_t flags, unsigned int useroffset,        unsigned int usersize, void (*ctor)(void *),        struct mem_cgroup *memcg, struct kmem_cache *root_cache){    struct kmem_cache *s;    int err;    if (WARN_ON(useroffset + usersize > object_size))        useroffset = usersize = 0;    err = -ENOMEM;    //调用kmem_cache_zalloc函数分配一个kmem_cache数据结构    s = kmem_cache_zalloc(kmem_cache, GFP_KERNEL);    if (!s)        goto out;    //填充kmem_cache数据结构,如name、size等    s->name = name;    s->size = s->object_size = object_size;    s->align = align;    s->ctor = ctor;    s->useroffset = useroffset;    s->usersize = usersize;    err = init_memcg_params(s, root_cache);    if (err)        goto out_free_cache;    //调用__kmem_cache_create函数创建slab缓存描述符    err = __kmem_cache_create(s, flags);    if (err)        goto out_free_cache;    s->refcount = 1;    //将slab缓存描述符加入全局链表slab_caches中    list_add(&s->list, &slab_caches);    memcg_link_cache(s, memcg);out:    if (err)        return ERR_PTR(err);    return s;out_free_cache:    destroy_memcg_params(s);    kmem_cache_free(kmem_cache, s);    goto out;}

__kmem_cache_create()函数

slab分配器有slab、slob、slub三种,这三种在该函数中有不同的实现方式。函数位于

源码较多,节选部分

int __kmem_cache_create(struct kmem_cache *cachep, slab_flags_t flags){        size_t ralign = BYTES_PER_WORD;    gfp_t gfp;    int err;    unsigned int size = cachep->size;......#endif    //让slab描述符大小size和系统的word长度对齐(BYTES_PER_WORD)    //如果size小于word长度,以word长度为准进行创建    size = ALIGN(size, BYTES_PER_WORD);    //检查SLAB_RED_ZONE是否溢出,实现调试功能    if (flags & SLAB_RED_ZONE) {        ralign = REDZONE_ALIGN;        size = ALIGN(size, REDZONE_ALIGN);    }    //slab对象对齐    if (ralign < cachep->align) {        ralign = cachep->align;    }......    cachep->align = ralign;    //colour_off表示一个着色区长度,他和L1高速缓存对齐    cachep->colour_off = cache_line_size();    if (cachep->colour_off < cachep->align)        cachep->colour_off = cachep->align;    //slab_is_available表示当slab分配器处于UP或者FULL状态时,    //分配掩码可以使用GFP_KERNEL,否则只能使用GFP_NOWAIT    if (slab_is_available())        gfp = GFP_KERNEL;    else        gfp = GFP_NOWAIT;......done:    //freelist_size表示一个slab分配器中管理区 freelist大小    cachep->freelist_size = cachep->num * sizeof(freelist_idx_t);    cachep->flags = flags;    cachep->allocflags = __GFP_COMP;    if (flags & SLAB_CACHE_DMA)        cachep->allocflags |= GFP_DMA;    if (flags & SLAB_CACHE_DMA32)        cachep->allocflags |= GFP_DMA32;    if (flags & SLAB_RECLAIM_ACCOUNT)        cachep->allocflags |= __GFP_RECLAIMABLE;    //size表示一个slab对象的大小    cachep->size = size;    cachep->reciprocal_buffer_size = reciprocal_value(size);......    if (OFF_SLAB(cachep)) {        cachep->freelist_cache =            kmalloc_slab(cachep->freelist_size, 0u);    }    //调用setup_cpu_cache继续配置slab描述符    err = setup_cpu_cache(cachep, gfp);    if (err) {        __kmem_cache_release(cachep);        return err;    }    return 0;}

slab分配器内存布局

slab分配器布局模式

代码集成在__kmem_cache_create()函数内

//如果数组freelist小于一个slab对象的大小并且没有指定构造函数,    //那么slab分配器就可以采用CFLGS_OBJFREELIST_SLAB模式    if (set_objfreelist_slab_cache(cachep, size, flags)) {        flags |= CFLGS_OBJFREELIST_SLAB;        goto done;    }    //如果一个slab分配器的剩余空间(leftover)小于freelist数组大小,    //那么slab分配器就可以采用CFLGS_OFF_SLAB模式    if (set_off_slab_cache(cachep, size, flags)) {        flags |= CFLGS_OFF_SLAB;        goto done;    }    //如果一个slab分配器的剩余空间(leftover)大于slab管理数组大小,    //那么slab分配器就可以采用正常模式    if (set_on_slab_cache(cachep, size, flags))        goto done;

calculate_slab_order

set_objfreelist_slab_cache、set_off_slab_cache、set_on_slab_cache三个函数都会调用calculate_slab_order进行计算

calculate_slab_order是计算slab分配器核心参数的函数。主要处理如下内容

1个slab分配器中需要多少个连续的物理页面

1个slab分配器中能高寒多少个slab对象

1个slab分配器中包含多少个着色区

static size_t calculate_slab_order(struct kmem_cache *cachep,                size_t size, slab_flags_t flags){    size_t left_over = 0;    int gfporder;    //从0阶开始计算合适的gfporder值,最多支持的页面数量是2^KMALLOC_MAX_ORDER,    //KMALLOC_MAX_ORDER = MAX_ORDER - 1, 即等于10阶    for (gfporder = 0; gfporder <= KMALLOC_MAX_ORDER; gfporder++) {        unsigned int num;        size_t remainder;        //调用cache_estimate计算在2^gfporder个页面大小的情况下可以容纳多少个对象,剩下的空间用于着色        num = cache_estimate(gfporder, size, flags, &remainder);        if (!num)        //1个slab分配器中的对象不能超过SLAB_OBJ_MAX_NUM        if (num > SLAB_OBJ_MAX_NUM)            break;        if (flags & CFLGS_OFF_SLAB) {            struct kmem_cache *freelist_cache;            size_t freelist_size;            freelist_size = num * sizeof(freelist_idx_t);            freelist_cache = kmalloc_slab(freelist_size, 0u);            if (!freelist_cache)                continue;            if (OFF_SLAB(freelist_cache))                continue;            if (freelist_cache->size > cachep->size / 2)                continue;        }        cachep->num = num;        cachep->gfporder = gfporder;        left_over = remainder;        if (flags & SLAB_RECLAIM_ACCOUNT)            break;        //尽可能选择gfporder小的方案        if (gfporder >= slab_max_order)            break;        //left_over表示一个slab分配器中剩余的空间,要检查剩余空间是否太大,太大会浪费空间        if (left_over * 8 <= (PAGE_SIZE << gfporder))            break;    }    return left_over; //返回slab分配器的剩余空间}

配置slab描述符

确认slab分配器的内存布局后,调用setup_cpu_cache()函数继续进行slab描述符配置。当slab_state=FULL时,直接调用enable_cpu_cache函数

enable_cpucache

static int enable_cpucache(struct kmem_cache *cachep, gfp_t gfp){    int err;    int limit = 0;    int shared = 0;    int batchcount = 0;    err = cache_random_seq_create(cachep, cachep->num, gfp);    if (err)        goto end;    if (!is_root_cache(cachep)) {        struct kmem_cache *root = memcg_root_cache(cachep);        limit = root->limit;        shared = root->shared;        batchcount = root->batchcount;    }    if (limit && shared && batchcount)        goto skip_setup;    //根据对象的大小计算空闲对象的最大阈值limit    if (cachep->size > 131072)        limit = 1;    else if (cachep->size > PAGE_SIZE)        limit = 8;    else if (cachep->size > 1024)        limit = 24;    else if (cachep->size > 256)        limit = 54;    else        limit = 120;    //在SMP系统中切slab对象大小不大于一个页面的情况下,shared的变量为8,否则为0    shared = 0;    if (cachep->size <= PAGE_SIZE && num_possible_cpus() > 1)        shared = 8;#if DEBUG    if (limit > 32)        limit = 32;#endif//计算batchcount,通常是最大阈值limit的一半,//用于表示本地对象缓冲池和共享对象缓冲池之间填充对象的数量    batchcount = (limit + 1) / 2;skip_setup:    err = do_tune_cpucache(cachep, limit, batchcount, shared, gfp);end:    if (err)        pr_err("enable_cpucache failed for %s, error %d\n",               cachep->name, -err);    return err;}

do_tune_cpucache -> __do_tune_cpucache

static int do_tune_cpucache(struct kmem_cache *cachep, int limit,                int batchcount, int shared, gfp_t gfp){...    ret = __do_tune_cpucache(cachep, limit, batchcount, shared, gfp);...}static int __do_tune_cpucache(struct kmem_cache *cachep, int limit,                int batchcount, int shared, gfp_t gfp){    struct array_cache __percpu *cpu_cache, *prev;    int cpu;    //通过alloc_kmem_cache_cpus函数分配Per-CPU类型的array_cache数据结构    //称之为对象缓冲池    cpu_cache = alloc_kmem_cache_cpus(cachep, limit, batchcount);    if (!cpu_cache)        return -ENOMEM;    prev = cachep->cpu_cache;    cachep->cpu_cache = cpu_cache;    /*     * Without a previous cpu_cache there's no need to synchronize remote     * cpus, so skip the IPIs.     */    if (prev)        kick_all_cpus_sync();    check_irq_on();    cachep->batchcount = batchcount;    cachep->limit = limit;    cachep->shared = shared;    if (!prev)        goto setup_node;    //遍历在线CPU,调用free_block清空本地对象缓冲池    //正常初始化时,不需要清空。    for_each_online_cpu(cpu) {        LIST_HEAD(list);        int node;        struct kmem_cache_node *n;        struct array_cache *ac = per_cpu_ptr(prev, cpu);        node = cpu_to_mem(cpu);        n = get_node(cachep, node);        spin_lock_irq(&n->list_lock);        free_block(cachep, ac->entry, ac->avail, node, &list);        spin_unlock_irq(&n->list_lock);        slabs_destroy(cachep, &list);    }    free_percpu(prev);setup_node:    //调用setup_kmem_cache_nodes,继续配置slab描述符    //setup_kmem_cache_nodes会遍历系统素有内存节点,    //并调用setup_kmem_cache_node函数进行初始化slab信息    return setup_kmem_cache_nodes(cachep, gfp);}

setup_kmem_cache_nodes->setup_kmem_cache_node

static int setup_kmem_cache_nodes(struct kmem_cache *cachep, gfp_t gfp){...    for_each_online_node(node) {        ret = setup_kmem_cache_node(cachep, node, gfp, true);        if (ret)            goto fail;...}static int setup_kmem_cache_node(struct kmem_cache *cachep,                int node, gfp_t gfp, bool force_change){......    //通过init_cache_node函数分配一个新的kmem_cache_node节点    //简称为slab节点    ret = init_cache_node(cachep, node, gfp);    if (ret)        goto fail;......}

分配slab对象

kmem_cache_alloc()是分配slab缓存对象的核心函数,内部调用slab_alloc()函数。在slab分配对象过程中是全程关闭本地中断的。

kmem_cache_alloc() -> slab_alloc()

void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags){    void *ret = slab_alloc(cachep, flags, _RET_IP_);    trace_kmem_cache_alloc(_RET_IP_, ret,                   cachep->object_size, cachep->size, flags);    return ret;}EXPORT_SYMBOL(kmem_cache_alloc);slab_alloc(struct kmem_cache *cachep, gfp_t flags, unsigned long caller){    unsigned long save_flags;    void *objp;    flags &= gfp_allowed_mask;    cachep = slab_pre_alloc_hook(cachep, flags);    if (unlikely(!cachep))        return NULL;    cache_alloc_debugcheck_before(cachep, flags);    //关闭本地中断    local_irq_save(save_flags);    //调用__do_cache_alloc函数获取slab对象    /*        __do_cache_alloc(struct kmem_cache *cachep, gfp_t flags)        {            return ____cache_alloc(cachep, flags);        }    __do_cache_alloc内部调用 ____cache_alloc函数    */    objp = __do_cache_alloc(cachep, flags);    //恢复本地中断    local_irq_restore(save_flags);    objp = cache_alloc_debugcheck_after(cachep, flags, objp, caller);    prefetchw(objp);    if (unlikely(slab_want_init_on_alloc(flags, cachep)) && objp)        memset(objp, 0, cachep->object_size);    slab_post_alloc_hook(cachep, flags, 1, &objp);    return objp; //返回slab对象}

__do_cache_alloc ->____cache_alloc

__do_cache_alloc(struct kmem_cache *cachep, gfp_t flags){    return ____cache_alloc(cachep, flags);}
static inline void *____cache_alloc(struct kmem_cache *cachep, gfp_t flags){    void *objp;    struct array_cache *ac;    check_irq_off();    //通过cpu_cache_get获取slab描述符cachep中的本地对象缓冲池ac    ac = cpu_cache_get(cachep);    //判断本地对象缓冲池ac中是否有空闲独享    if (likely(ac->avail)) {        ac->touched = 1;        objp = ac->entry[--ac->avail];        STATS_INC_ALLOCHIT(cachep);        goto out;    }    STATS_INC_ALLOCMISS(cachep);    //如果没有或者为第一次分配,则进入cache_alloc_refill    objp = cache_alloc_refill(cachep, flags);    ac = cpu_cache_get(cachep);out:    if (objp)        kmemleak_erase(&ac->entry[ac->avail]);    return objp;}

cache_alloc_refill()

static void *cache_alloc_refill(struct kmem_cache *cachep, gfp_t flags){    int batchcount;    struct kmem_cache_node *n;    struct array_cache *ac, *shared;    int node;    void *list = NULL;    struct page *page;    check_irq_off();    node = numa_mem_id();    //获取本地对象缓冲池ac    ac = cpu_cache_get(cachep);    batchcount = ac->batchcount;    if (!ac->touched && batchcount > BATCHREFILL_LIMIT) {        batchcount = BATCHREFILL_LIMIT;    }    //通过get_node获取slab节点    n = get_node(cachep, node);    BUG_ON(ac->avail > 0 || !n);    shared = READ_ONCE(n->shared);    //如果slab节点没有空闲对象且共享对象池shared为空或者共享对象缓冲池也没有空闲对象    //直接跳转到标签direct_grow    if (!n->free_objects && (!shared || !shared->avail))        goto direct_grow;    spin_lock(&n->list_lock);    shared = READ_ONCE(n->shared);    //如果共享对象缓冲池有空闲对象,尝试迁移batchcount个空闲对象到本地对象缓冲池ac中    //transfer_objects函数用于从共享对象缓存池把空闲对象迁移至本地对象缓冲池    if (shared && transfer_objects(ac, shared, batchcount)) {        shared->touched = 1;        goto alloc_done;    }    //如果共享对象缓冲池没有空闲对象,通过get_first_slab函数查看slab节点的    //slabs_partial链表(部分空闲链表)和slab_free链表(全部空闲链表)    //返回slab分配器第一个物理页面的page数据结构    while (batchcount > 0) {        page = get_first_slab(n, false);        if (!page)            goto must_grow;        check_spinlock_acquired(cachep);        //通过alloc_block函数将slab分配器中迁移batchcount个空闲对象到本地对象缓冲池        batchcount = alloc_block(cachep, ac, page, batchcount);        fixup_slab_list(cachep, n, page, &list);    }must_grow:    //更新free_object计数值    n->free_objects -= ac->avail;alloc_done:    spin_unlock(&n->list_lock);    fixup_objfreelist_debug(cachep, &list);//如跳转到direct_grow,则表示slab节点没有空闲对象并且共享对象缓冲池中也没有空闲对象//说明这个内存节点没有slab空闲对象。这种情况下只能重新分配slab分配器//调用cache_grow_begin()函数分配一个slab分配器direct_grow:    if (unlikely(!ac->avail)) {        /* Check if we can use obj in pfmemalloc slab */        if (sk_memalloc_socks()) {            void *obj = cache_alloc_pfmemalloc(cachep, n, flags);            if (obj)                return obj;        }        page = cache_grow_begin(cachep, gfp_exact_node(flags), node);        ac = cpu_cache_get(cachep);        //通过alloc_block函数将slab分配器中迁移batchcount个空闲对象到本地对象缓冲池        if (!ac->avail && page)            alloc_block(cachep, ac, page, batchcount);        cache_grow_end(cachep, page);        if (!ac->avail)            return NULL;    }    //touched为1表示刚刚使用过本地对象缓冲池    ac->touched = 1;    return ac->entry[--ac->avail];}

cache_grow_begin ->kmem_getpages->cache_init_objs

cache_grow_begin:负责 重新分配一个slab分配器

kmem_getpages:负责分配slab分配器需要的物理页面

cache_init_objs:初始化空闲对象

static struct page *cache_grow_begin(struct kmem_cache *cachep,                gfp_t flags, int nodeid){......    page = kmem_getpages(cachep, local_flags, nodeid);    if (!page)        goto failed;......    cache_init_objs(cachep, page);......opps1:    kmem_freepages(cachep, page);failed:    if (gfpflags_allow_blocking(local_flags))        local_irq_disable();    return NULL;

释放slab缓存对象

slab系统有两种缓存释放方式:

第一种是通过kmem_cache_free()释放对象

另一种是注册了一个定时器,定时扫描所有的slab描述符,回收一部分空闲对象,达到条件的slab分配器会被销毁,实现函数为cache_reap()

kmem_cache_free() -> __cache_free

void kmem_cache_free(struct kmem_cache *cachep, void *objp){    unsigned long flags;    cachep = cache_from_obj(cachep, objp);    if (!cachep)        return;    local_irq_save(flags);    debug_check_no_locks_freed(objp, cachep->object_size);    if (!(cachep->flags & SLAB_DEBUG_OBJECTS))        debug_check_no_obj_freed(objp, cachep->object_size);    __cache_free(cachep, objp, _RET_IP_);    local_irq_restore(flags);    trace_kmem_cache_free(_RET_IP_, objp);}
void ___cache_free(struct kmem_cache *cachep, void *objp,        unsigned long caller){......    //如果本地对象缓存池的空闲对象ac->avail大于或等于ac->limit    //就会调用cache_flusharray函数,尝试回收空闲对象    if (ac->avail < ac->limit) {        STATS_INC_FREEHIT(cachep);    } else {        STATS_INC_FREEMISS(cachep);        cache_flusharray(cachep, ac);......    //将对象释放到本地对象缓冲池ac中    ac->entry[ac->avail++] = objp;}

cache_flusharray()

cache_flusharra()函数主要用于回收slab分配器

static void cache_flusharray(struct kmem_cache *cachep, struct array_cache *ac){......    //销毁slab分配器    slabs_destroy(cachep, &list);    ac->avail -= batchcount;    //将本地对象缓冲池中的剩余的空闲对象迁移到缓存的头部    memmove(ac->entry, &(ac->entry[batchcount]), sizeof(void *)*ac->avail);

}

其他知识点

名词解释

零长数组

零长数组是一种空数组,即长度为0的数组。它可以用来表示一个没有元素的序列或集合。

着色区

Slab分配器是一种高效的内存管理机制,用于分配和释放内核对象的内存。在Slab分配器中,内存被划分为不同的大小,每个大小的内存块都由一个或多个用于管理内存的Slab对象组成。Slab对象包含着色区(color cache)这一概念。

着色区(color cache)实际上是Slab对象中的一块内存区域,用于缓存已分配的内存块。它可以加快内存分配的速度,减少频繁的内存申请和释放带来的开销。着色区的目的是提供一种快速分配内存的机制,避免频繁地搜索合适的内存块。

Slab分配器的着色区策略可以根据实际需求进行调整,以便更好地满足内存分配的性能要求。它在内核中的作用是提供高效的内存分配和释放,以优化系统的性能。

着色区可以让每一个slab分配器对应不同数量的高速缓存行,可以使不同slab分配器上同一个相对位置slab对象的起始地址在高速缓存中互相错开,有利于提高高速缓存的访问效率

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值