优雅的slab内存分配器(二)——slab初始化kmem_cache_init

slab内存分配器初始化有函数kmem_cache_init来完成,本文会详细介绍slab内存分配器的初始化流程。

软件架构


kmem_cache_init
   |----->kmem_cache = &kmem_cache_boot;
   |        第一个kmem cache实例,静态定义,不过尚需必须要的初始化
   |----->kmem_cache_node_init(&init_kmem_cache_node[i]);
   |        初始化静态定义的struct kmem_cache_node实例,这里有两个node实例;它们是分别为前两个
   |        kmem cache实例准备的,第一个kmem cache实例用于为创建其他kmem cache实例分配空间,
   |        第二个kmem cache实例用于为创建struct kmem_cache_node实例分配空间,所以前两个kmem cache
   |        实例需要静态分配struct kmem_cache_node实例。
   |----->create_boot_cache(kmem_cache, "kmem_cache", offsetof(struct kmem_cache, node) +
   |              nr_node_ids * sizeof(struct kmem_cache_node *),SLAB_HWCACHE_ALIGN);
   |        完成第一个kmem cache实例kmem_cache的初始化,第三个参数很重要,是该kmem cache实例所维护
   |        的object的size。
   |   |----->s->size = s->object_size = size;
   |   |      设置object size
   |   |----->s->align = calculate_alignment(flags, ARCH_KMALLOC_MINALIGN, size);
   |   |      计算按多少字节对齐,详见重要函数分析。
   |   |----->err = __kmem_cache_create(s, flags);
   |   |   |----->set_objfreelist_slab_cache(cachep, size, flags)
   |   |   |   |----->left = calculate_slab_order(cachep, size, flags | CFLGS_OBJFREELIST_SLAB);
   |   |   |   |      计算该kmem cache实例对应的slab order,原则是slab尽量小,浪费尽量少,详见重要函数分析。
   |   |   |   |      这里没有设置CFLGS_OFF_SLAB,是因为还没有创建freelist index的kmem cache实例,freelist 
   |   |   |   |      只能存放在slab内。
   |   |   |----->cachep->freelist_size = cachep->num * sizeof(freelist_idx_t);
   |   |   |      freelist index占用空间大小
   |   |   |----->err = setup_cpu_cache(cachep, gfp);
   |   |   |   |----->cachep->cpu_cache = alloc_kmem_cache_cpus(cachep, 1, 1);
   |   |   |   |      分配array_cache实例
   |   |   |   |----->set_up_node(kmem_cache, CACHE_CACHE);
   |   |   |   |      设置node
   |   |   |   |----->cpu_cache_get(cachep)->avail = 0;
   |   |   |   |      由于还没有申请slab,不存在可用的object,所以可用的object为0
   |----->list_add(&kmem_cache->list, &slab_caches);
   |      kmem cache实例加入slab_caches链表
   |----->slab_state = PARTIAL;
   |----->kmalloc_caches[INDEX_NODE] = create_kmalloc_cache("kmalloc-node",
   |                kmalloc_size(INDEX_NODE), ARCH_KMALLOC_FLAGS);
   |      创建第二个kmem cache实例,从object size可以看出,该实例用于为struct kmem_cache_node分配空间。
   |      指针数组kmalloc_caches是kmalloc用的kmem cache实例数组,该数组十分重要,详见重要变量分析。
   |   |----->struct kmem_cache *s = kmem_cache_zalloc(kmem_cache, GFP_NOWAIT);
   |   |      从kmem cache kmem_cache中分配一个object
   |   |   |----->return kmem_cache_alloc(k, flags | __GFP_ZERO);
   |   |   |   |----->void *ret = slab_alloc(cachep, flags, _RET_IP_);
   |   |   |   |   |----->objp = __do_cache_alloc(cachep, flags);
   |   |   |   |   |      分配一个object,该函数比较复杂,详情请参考重要函数分析
   |   |----->create_boot_cache(s, name, size, flags);
   |   |----->list_add(&s->list, &slab_caches);
   |----->slab_state = PARTIAL_NODE;
   |----->setup_kmalloc_cache_index_table();
   |----->init_list(kmem_cache, &init_kmem_cache_node[CACHE_CACHE + nid], nid);
   |   |----->ptr = kmalloc_node(sizeof(struct kmem_cache_node), GFP_NOWAIT, nodeid);
   |   |      分配struct kmem_cache_node
   |   |----->memcpy(ptr, list, sizeof(struct kmem_cache_node));
   |   |      将静态定义的struct kmem_cache_node拷贝到新分配的struct kmem_cache_node中
   |   |----->cachep->node[nodeid] = ptr;
   |----->init_list(kmalloc_caches[INDEX_NODE], &init_kmem_cache_node[SIZE_NODE + nid], nid);
   |----->create_kmalloc_caches(ARCH_KMALLOC_FLAGS);
   |      创建kmalloc所需要的kmem cache实例

重要函数分析


calculate_alignment

unsigned long calculate_alignment(unsigned long flags,
        unsigned long align, unsigned long size)
{
    /*
     * If the user wants hardware cache aligned objects then follow that
     * suggestion if the object is sufficiently large.
     *
     * The hardware cache alignment cannot override the specified
     * alignment though. If that is greater then use it.
     */
    if (flags & SLAB_HWCACHE_ALIGN) {
        unsigned long ralign = cache_line_size();
        while (size <= ralign / 2)
            ralign /= 2;
        align = max(align, ralign);
    }

    if (align < ARCH_SLAB_MINALIGN)
        align = ARCH_SLAB_MINALIGN;

    return ALIGN(align, sizeof(void *));
}

参数解析:

参数 说明
flags flag标记
align 最小对齐要求,字节
size object size

功能解析
如果定义了SLAB_HWCACHE_ALIGN,说明要求按硬件的cache line对齐。这里并不是说每个object都按硬件cache line对齐,也可能是多个object按一个硬件cache line对齐。如果object size不小于硬件cache line size的一半,则object size按硬件cache line对齐;否则可能多个object按一个cache line对齐。

calculate_slab_order

static size_t calculate_slab_order(struct kmem_cache *cachep,
                size_t size, unsigned long flags)
{
    size_t left_over = 0;
    int gfporder;

    for (gfporder = 0; gfporder <= KMALLOC_MAX_ORDER; gfporder++) {
        unsigned int num;
        size_t remainder;

        num = cache_estimate(gfporder, size, flags, &remainder);
        if (!num)
            continue;

        /* Can't handle number of objects more than 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;

            /*
             * Needed to avoid possible looping condition
             * in cache_grow_begin()
             */
            if (OFF_SLAB(freelist_cache))
                continue;

            /* check if off slab has enough benefit */
            if (freelist_cache->size > cachep->size / 2)
                continue;
        }

        /* Found something acceptable - save it away */
        cachep->num = num;
        cachep->gfporder = gfporder;
        left_over = remainder;

        /*
         * A VFS-reclaimable slab tends to have most allocations
         * as GFP_NOFS and we really don't want to have to be allocating
         * higher-order pages when we are unable to shrink dcache.
         */
        if (flags & SLAB_RECLAIM_ACCOUNT)
            break;

        /*
         * Large number of objects is good, but very large slabs are
         * currently bad for the gfp()s.
         */
        if (gfporder >= slab_max_order)
            break;

        /*
         * Acceptable internal fragmentation?
         */
        if (left_over * 8 <= (PAGE_SIZE << gfporder))
            break;
    }
    return left_over;
}

参数解析:

参数 说明
cachep kmem cache实例
size object实例
flags flag标记

功能解析:
从order=0开始选择满足要求的order值,如果order=0不满足,则继续选择order=1,直到order=KMALLOC_MAX_ORDER。某个order值是否满足要求,判断步骤如下:

  1. 计算当前order对应的slab可以拆分成的object的数量num,以及剩余的空间left_over。这里需要注意的是,如果每一个object的freelist index存放在slab内部的话,需要算上这部分的空间消耗。
  2. 如果object的freelist indes存放在slab外部的话,需要查找到一个合适的kmem cache为freelist index分配空间。
  3. 更新cachep->num,cachep->gfporder = gfporder,left_over = remainder。之所以先更新这些信息,再判断order是否满足,是为了保证在所有order都不满足的情况下,默认使用最后一个order。
  4. 判断order是否合适,如果剩余空间left_over不超过该order对应空间的八分之一,则认为满足条件。如果不满足条件,继续判断下一个order。
  5. 将剩余空间order返回。

那么如何决定freelist是放在slab内部还是放在slab外部呢?
首先,这个决定不是calculate_slab_order能做的。然而既然在该函数中提到了这个话题,我们就就地解决这个疑问好了!

freelist index选择放在slab内部还是外部的原则很简单,也很质朴,那就是怎么节省内存怎么来!要搞清楚怎么存放节省内存,首先需要搞清楚freelist index在slab内部和slab外部是如何存放的。

freelist存放在slab内部时,会放在slab的最后。
freelist存放在slab外部时,从别的kmem cache实例中为freelist分配空间。说到这里可以看出来,前面所说的怎么节省内存怎么来并不完全正确,因为freelist存放在外部是有条件的,那就是有可用的kmem cache实例可以为freelist分配空间。

首先调用set_objfreelist_slab_cache尝试把freelist放在slab内部,如果一个object不足以存放freelist index,则意味着freelist index不太合适,应该选择其他合适的kmem cache实例分配object,来存放freelist index。
如果上一步没有成功,则调用set_off_slab_cache尝试把freelist index存放在slab外部。set_off_slab_cache首先会查找合适的kmem cache实例,如果找不到,直接歇菜,freelist index无法存放在slab外部;如果找到了,判断当前kmem cache实例的剩余空间是否足以存放freelist index,如果空间不够,则将freelist存放在slab外面;如果空间足够,则倾向于将freelist存放在剩余空间。
如果上一步没有成功,则会调用set_on_slab_cache将freelist存放在slab内部,这一步跟第一步的区别在于,这一步不会判断freelist index是否超过一个object的大小。

kmalloc_index

static __always_inline int kmalloc_index(size_t size)
{
    if (!size)
        return 0;

    if (size <= KMALLOC_MIN_SIZE)
        return KMALLOC_SHIFT_LOW;

    if (KMALLOC_MIN_SIZE <= 32 && size > 64 && size <= 96)
        return 1;
    if (KMALLOC_MIN_SIZE <= 64 && size > 128 && size <= 192)
        return 2;
    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 (size <=  4 * 1024 * 1024) return 22;
    if (size <=  8 * 1024 * 1024) return 23;
    if (size <=  16 * 1024 * 1024) return 24;
    if (size <=  32 * 1024 * 1024) return 25;
    if (size <=  64 * 1024 * 1024) return 26;
    BUG();

    /* Will never be reached. Needed because the compiler may complain */
    return -1;
}

参数解析

参数 说明
size 需要分配的空间的大小

功能分析
该函数用于根据需要分配的内存的size来选择kmalloc_caches数组的下标,kmalloc_caches详情请参考重要变量分析:kmalloc_caches。
KMALLOC_MIN_SIZE是一个非常重要的参数,对于ARM64来说,它是128B,即L1 cache line的长度。为了保证object按L1 cache line对齐,所有1-128B的size所选择的下标都是7,即通过kmem cache实例kmalloc-128分配size为128B的object。
- 如果size为 0,则索引为0。
- 如果size为 1 - 128,则索引为7。
- 如果size为 129 - 256,则索引为8。
依此类推。

该函数很有意思的一点是,当KMALLOC_SHIFT_LOW不超过32B,并且申请内存在65-96范围内时,会选择kmem cache实例kmalloc-96来分配空间,而该kmem cache实例所维护的object size是96B。我们假设L1 cache line是32B,即KMALLOC_MIN_SIZE是32B,如果分配的内存size在65-96之间,则实际分配的object size是96B,96B也是L1 cache line对齐的,这样既节省了空间,又不违背cache对齐的要求。
同样,当KMALLOC_MIN_SIZE不超过64B,并且申请的内存在129-192B之间时,会选择kmalloc-192这个kmem cache实例来分配空间也是一样道理。

为什么单单针对65-96和129-192这两段memory size做了优化,而没有对其他的memory size做这种优化呢?这个问题我无法给出明确的回答,不过我自己猜测有两点可能性:

  1. 通过kmalloc分配的memory size很可能绝大多数不超过256B,所以针对65-96和129-192这两段memory size做优化作用明显。
  2. 该优化本质上在不违背cache line对齐的前提下,支持更多的object size,这样避免了浪费,但如果支持的object size过多的话,反而会导致内存浪费,比如对于某个size的object,可能只需要一个object,然而slab分配器从buddy sysem申请的时候确是一次申请多个object,这就会出现内存的浪费,支持的object size越多,这种浪费就越普遍也越严重。

create_kmalloc_caches

void __init create_kmalloc_caches(unsigned long flags)
{
    int i;

    for (i = KMALLOC_SHIFT_LOW; i <= KMALLOC_SHIFT_HIGH; i++) {
        if (!kmalloc_caches[i])
            new_kmalloc_cache(i, flags);

        /*
         * Caches that are not of the two-to-the-power-of size.
         * These have to be created immediately after the
         * earlier power of two caches
         */
        if (KMALLOC_MIN_SIZE <= 32 && !kmalloc_caches[1] && i == 6)
            new_kmalloc_cache(1, flags);
        if (KMALLOC_MIN_SIZE <= 64 && !kmalloc_caches[2] && i == 7)
            new_kmalloc_cache(2, flags);
    }

    /* Kmalloc array is now usable */
    slab_state = UP;

#ifdef CONFIG_ZONE_DMA
    for (i = 0; i <= KMALLOC_SHIFT_HIGH; i++) {
        struct kmem_cache *s = kmalloc_caches[i];

        if (s) {
            int size = kmalloc_size(i);
            char *n = kasprintf(GFP_NOWAIT,
                 "dma-kmalloc-%d", size);

            BUG_ON(!n);
            kmalloc_dma_caches[i] = create_kmalloc_cache(n,
                size, SLAB_CACHE_DMA | flags);
        }
    }
#endif
}

参数解析

参数 说明
flags flags标记

功能分析
该函数的目的是创建用于kmalloc的kmem cache实例,这里并不一定会创建kmalloc_info中定义的所有的kmem cache实例,其index从KMALLOC_SHIFT_LOW到KMALLOC_SHIFT_HIGH,起始下标是为了cache line对齐,最大的下标受限于buddy system所支持的最大的order,同时也会判断是否可以创建kmalloc_info[1]和kmalloc_info[2]对应的kmem cache实例,关于这两个kmem cache实例,请参考重要变量分析。

如果定义了CONFIG_ZONE_DMA,即定义了DMA内存域,则会创建用于DMA内存域内存分配的kmem cache实例,创建的kmem cache实例地址存放在指针数组kmalloc_dma_caches中。

__do_cache_alloc

__do_cache_alloc直接调用____cache_alloc,所以我们主要分析____cache_alloc

static inline void *____cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
    void *objp;
    struct array_cache *ac;

    check_irq_off();

    ac = cpu_cache_get(cachep);
    if (likely(ac->avail)) {
        ac->touched = 1;
        objp = ac->entry[--ac->avail];

        STATS_INC_ALLOCHIT(cachep);
        goto out;
    }

    STATS_INC_ALLOCMISS(cachep);
    objp = cache_alloc_refill(cachep, flags);
    /*
     * the 'ac' may be updated by cache_alloc_refill(),
     * and kmemleak_erase() requires its correct value.
     */
    ac = cpu_cache_get(cachep);

out:
    /*
     * To avoid a false negative, if an object that is in one of the
     * per-CPU caches is leaked, we need to make sure kmemleak doesn't
     * treat the array pointers as a reference to the object.
     */
    if (objp)
        kmemleak_erase(&ac->entry[ac->avail]);
    return objp;
}

参数解析

参数 说明
cachep 分配object的kmem cache实例
flags flags标记

功能分析
该函数从cache中分配object,首先到cachep->cpu_cache查看是否有可用的object,如果有则直接将object的地址返回,并将可用的object计数减一;如果没有则需要从buddy system分配slab,并拆分成object填充到cachep->cpu_cache中,这通过调用cache_alloc_refill函数实现。

重要变量分析

kmalloc_caches

详细定义

struct kmem_cache *kmalloc_caches[KMALLOC_SHIFT_HIGH + 1];

功能解析
kmalloc分配内存时,会根据所需要分配的空间的大小,从kmalloc_caches数组中选择一个kmem cache实例来分配object,这个很好理解,因为不同的kmem cache实例维护不同size的object。那么kmalloc_caches数组的下标必然与分配内存的size有某种联系,这种联系就体现在kmalloc_info数组中,定义如下:

static struct {
    const char *name;
    unsigned long size;
} const kmalloc_info[] __initconst = {
    {NULL,                      0},     {"kmalloc-96",             96},
    {"kmalloc-192",           192},     {"kmalloc-8",               8},
    {"kmalloc-16",             16},     {"kmalloc-32",             32},
    {"kmalloc-64",             64},     {"kmalloc-128",           128},
    {"kmalloc-256",           256},     {"kmalloc-512",           512},
    {"kmalloc-1024",         1024},     {"kmalloc-2048",         2048},
    {"kmalloc-4096",         4096},     {"kmalloc-8192",         8192},
    {"kmalloc-16384",       16384},     {"kmalloc-32768",       32768},
    {"kmalloc-65536",       65536},     {"kmalloc-131072",     131072},
    {"kmalloc-262144",     262144},     {"kmalloc-524288",     524288},
    {"kmalloc-1048576",   1048576},     {"kmalloc-2097152",   2097152},
    {"kmalloc-4194304",   4194304},     {"kmalloc-8388608",   8388608},
    {"kmalloc-16777216", 16777216},     {"kmalloc-33554432", 33554432},
    {"kmalloc-67108864", 67108864}
};

索引0:代表kmem cache实例维护的object size为0。
索引1:代表kmem cache实例维护的object size为96。
索引2:代表kmem cache实例维护的object size为192。
索引3:代表kmem cache实例维护的object size为8。
以此类推。

这里要注意,kmalloc_info中定义的kmem cache实例并不一定全部会被创建,详情请参考函数create_kmalloc_caches,这个函数在重要函数分析中有介绍。

猜你喜欢

转载自blog.csdn.net/liuhangtiant/article/details/81268669