SLAB分配器

内存在进行申请和释放内存的情况下,难免会产生碎片。Linux采用伙伴系统解决外部碎片的问题,采用slab解决内部碎片的问题。

什么是碎片

内存碎片是由内存的申请和释放产生的,通常分为内部碎片和外部碎片。

  1. 内部碎片是由于采用固定大小的内存分区,即以固定的大小块为单位来分配,采用这种方法,进程所分配的内存可能会比所需要的大,这多余的部分便是内部碎片。
  2. 外部碎片是由于未分配的连续内存区域太小,以至于不能满足任意进程所需要的内存分配请求,这些小片段且不连续的内存空间被称为外部碎片。

SLAB分配器

malloc函数是我们常用的用于分配内存空间的函数,同样,内核也经常需要分配内存,但如果按照伙伴系统分配内存,一次就会分配一个4KB的完整页面,这个单位太大了,所以需要把页拆分成更小的单位,可以容纳大量小对象。为此,这里引入slab分配器。

提供小内存并不是slab分配器的唯一任务。由于结构的特点,它也可以作为一个缓存,主要针对经常分配和释放的对象。通过建立slab缓存,内核能够储备一些对象供之后使用。举个例子,内核每创建一个进程都会给进程分配一个mm_struct,考虑到系统中进程的数量,内核需要维护大量的mm_struct,而且该结构体在内核中初始化、分配和释放的频率相当大,为了应付这种场景,slab为这样的对象创建缓存,slab分配器将释放的内存块保存在内部列表中,并不马上返回给伙伴系统,在下一次请求该类型对象实例时,会使用最近释放的内存块。通过这种方式,内核会减少使用伙伴系统的次数,减少处理时间;也可以使内存块驻留在CPU高速缓存的概率增大。

slab分配器对大多数系统都工作良好,但是在另一些情况中,它无法提供最优性能,为此在内核版本2.6后增加了两个slab分配器的替代品:

  • slob分配器为了配合某些微小嵌入式系统,减少了代码量。围绕一个简单的内存块链展开,在分配内存时使用了同样简单的最先适配算法。
  • slub分配器为了配合大规模并行系统,通过将页帧打包为组,并用struct page中未使用的字段来管理这些组,试图最小化所需要的内存开销。

SLAB分配器结构

slab分配器由下图所示的两部分组成:保存管理性数据的缓存对象和保存被管理对象的各个slab。

数据结构

每个内存缓存都对应一个kmem_cache实例。

/* include/linux/slab_def.h */
/* slab分配器中的slab高速缓存 */
struct kmem_cache {
    /* 指向包含空闲对象的本地高速缓存,每个CPU有一个该结构,当有对象释放时,优先放入本地CPU高速缓存中 */
    struct array_cache __percpu *cpu_cache;

/* 1) Cache tunables. Protected by slab_mutex */
    /* 要转移进本地高速缓存或从本地高速缓存中转移出去的对象的数量 */
    unsigned int batchcount;
    /* 本地高速缓存中空闲对象的最大数目 */
    unsigned int limit;
    /* 是否存在CPU共享高速缓存,CPU共享高速缓存指针保存在kmem_cache_node结构中 */
    unsigned int shared;

    /* 对象长度 + 填充字节 */
    unsigned int size;
    /* size的倒数,加快计算 */
    struct reciprocal_value reciprocal_buffer_size;
/* 2) touched by every alloc & free from the backend */

    /* 高速缓存永久属性的标识,如果SLAB描述符放在外部,则CFLAGS_OFF_SLAB置1 */
    slab_flags_t flags;     /* constant flags */
    /* 每个SLAB中对象的个数(在同一个高速缓存中slab中对象个数相同) */
    unsigned int num;       /* # of objs per slab */

/* 3) cache_grow/shrink */
    /* order of pgs per slab (2^n) */
    /* slab的阶数 */
    unsigned int gfporder;

    /* force GFP flags, e.g. GFP_DMA */
    /* 分配页框时传递给伙伴系统的一组标识 */
    gfp_t allocflags;

    /* SLAB使用的颜色范围 */
    size_t colour;          /* cache colouring range */
    /* SLAB中基本对齐偏移,当新SLAB着色时,偏移量需要乘上这个基本对齐偏移,通俗讲就是1个偏移量等于多少B大小的值 */
    unsigned int colour_off;    /* colour offset */
    /* 空闲对象链表放在外部时使用,其指向的SLAB高速缓存来存储空闲对象链表 */
    struct kmem_cache *freelist_cache;
    /* 空闲对象链表的大小 */
    unsigned int freelist_size;

    /* constructor func */
    /* 构造函数,一般用于初始化这个SLAB高速缓存中的对象 */
    void (*ctor)(void *obj);

/* 4) cache creation/removal */
    /* 高速缓存的名字 */
    const char *name;
    /* 高速缓存描述符双向链表指针 */
    struct list_head list;
    int refcount;
    /* 高速缓存中对象的大小 */
    int object_size;
    int align;

/* 5) statistics */
#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

    /*
     * If debugging is enabled, then the allocator can add additional
     * fields and/or padding to every object. 'size' contains the total
     * object size including these internal fields, while 'obj_offset'
     * and 'object_size' contain the offset to the user object and its
     * size.
     */
    /* 对象间的偏移 */
    int obj_offset;
#endif /* CONFIG_DEBUG_SLAB */

#ifdef CONFIG_MEMCG
    /* 用于分组资源限制 */
    struct memcg_cache_params memcg_params;
#endif
#ifdef CONFIG_KASAN
    struct kasan_cache kasan_info;
#endif

#ifdef CONFIG_SLAB_FREELIST_RANDOM
    unsigned int *random_seq;
#endif

    unsigned int useroffset;    /* Usercopy region offset */
    unsigned int usersize;      /* Usercopy region size */

    /* 结点链表,此高速缓存可能在不同NUMA的结点都有SLAB链表 */
    struct kmem_cache_node *node[MAX_NUMNODES];
};

其中,gfporder是slab的阶数,num是每个slab包含的对象数量,object_size是对象的原始长度,size是包括填充的对象长度,node用于存放结点链表。每一个结点链表对应一个kmem_cache_node实例。

/* mm/slab.h */
struct kmem_cache_node {
    /* 锁 */
    spinlock_t list_lock;

#ifdef CONFIG_SLAB
    /* 只使用了部分对象的SLAB描述符的双向循环链表 */
    struct list_head slabs_partial; /* partial list first, better asm code */
    /* 满SLAB对象的SLAB描述符的双向循环链表 */
    struct list_head slabs_full;
    /* 只包含空闲对象的SLAB描述符的双向循环链表 */
    struct list_head slabs_free;
    unsigned long total_slabs;  /* length of all slab lists */
    unsigned long free_slabs;   /* length of free slab list only */
    /* 高速缓存中空闲对象个数(包括slabs_partial和slabs_free中的空闲对象) */
    unsigned long free_objects;
    /* 高速缓存中空闲对象的上限 */
    unsigned int free_limit;
    /* 下一个被分配的SLAB使用的颜色 */
    unsigned int colour_next;   /* Per-node cache coloring */
    /* 指向这个结点上所有CPU共享的一个本地高速缓存 */
    struct array_cache *shared; /* shared per node */
    struct alien_cache **alien; /* on other nodes */
    /* 两次缓存收缩时的间隔,降低次数,提高性能 */
    unsigned long next_reap;    /* updated without locking */
    /* 0:收缩,1:获取一个对象 */
    int free_touched;       /* updated without locking */
#endif

};

kmem_cache_node实例包括三条slab链表:slabs_partial链表把部分对象空闲的slab链接起来,slabs_full链表把没有空闲对象的slab链接起来,slabs_free链表把所有空闲对象的slab链接起来,total_slabs统计slab个数。

page结构体中与slab相关的成员:

  1. flags设置标志位PG_slab,表示页属于SLAB分配器。
  2. s_mem存放slab第一个对象的地址。
  3. active表示已分配对象的数量。
  4. lru作为链表结点加入其中一条slab链表。
  5. slab_cache指向kmem_cache实例。
  6. freelist指向空闲对象链表。

kmem_cache实例的成员cpu_cache指向array_cache实例,每一个处理器都对应一个array_cache,称为数组缓存,用来存放刚刚被释放的对象,分配时会首先从当前处理器的数组缓存中分配,如果没有空闲的再去找对象链表,这样可以避免每次都要从slab中分配,减少链表的处理次数或锁操作,提高效率。

每个对象的结构如上图所示,其中:

  1. 红色区域1长度为8字节,如果值被修改说明对象被改写过。
  2. 真实对象长度是kmem_cache.obj_size,偏移是kmem_cache.obj_offset。
  3. 填充区域用于对齐。
  4. 红色区域2长度为8字节,如果值被修改说明对象被改写过。
  5. 最后一个使用者在64位系统上长度为8字节,存放最后一个调用者的地址,用来确定对象是被谁改写的。

API实现

  • void *kmalloc(size_t size, gfp_t flags);
/* include/linux/slab.h */
static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
    if (__builtin_constant_p(size)) {
        if (size > KMALLOC_MAX_CACHE_SIZE)
            return kmalloc_large(size, flags);
#ifndef CONFIG_SLOB
        if (!(flags & GFP_DMA)) {
            unsigned int index = kmalloc_index(size);

            if (!index)
                return ZERO_SIZE_PTR;

            return kmem_cache_alloc_trace(kmalloc_caches[index],
                    flags, size);
        }
#endif
    }
    return __kmalloc(size, flags);
}

其中,传入参数size是申请的空间大小,flags是传给页分配器的分配标识符。

函数入口判断语句if (__builtin_constant_p(size))是GCC内建函数,用于判断一个值是否为编译时常量。当kmalloc()传入常量size大于KMALLOC_MAX_CACHE_SIZE时即申请的空间大于kmalloc所能分配最大cache大小时,将交于kmalloc_large函数进行分配,过程为kmalloc_large->kmalloc_order->__get_free_pages,最终通过伙伴算法申请所需内存。而另一条路径是通过__kmalloc函数进行分配。

/* mm/slab.c */
void *__kmalloc(size_t size, gfp_t flags)
{
    return __do_kmalloc(size, flags, _RET_IP_);
}

static __always_inline void *__do_kmalloc(size_t size, gfp_t flags,
                      unsigned long caller)
{
    struct kmem_cache *cachep;
    void *ret;

    if (unlikely(size > KMALLOC_MAX_CACHE_SIZE))
        return NULL;
    cachep = kmalloc_slab(size, flags);
    if (unlikely(ZERO_OR_NULL_PTR(cachep)))
        return cachep;
    ret = slab_alloc(cachep, flags, caller);

    kasan_kmalloc(cachep, ret, size, flags);
    trace_kmalloc(caller, ret,
              size, cachep->size, flags);

    return ret;
}

/* mm/slab_common.c */
struct kmem_cache *kmalloc_slab(size_t size, gfp_t flags)
{
    unsigned int index;

    if (size <= 192) {
        if (!size)
            return ZERO_SIZE_PTR;

        index = size_index[size_index_elem(size)];
    } else {
        if (unlikely(size > KMALLOC_MAX_CACHE_SIZE)) {
            WARN_ON(1);
            return NULL;
        }
        index = fls(size - 1);
    }

#ifdef CONFIG_ZONE_DMA
    if (unlikely((flags & GFP_DMA)))
        return kmalloc_dma_caches[index];

#endif
    return kmalloc_caches[index];
}

如果申请的内存大小小于192且不为0,将通过size_index_elem宏转换为下标后,由size_index全局数组取得索引值;否则直接通过fls()取得索引值。

  • void kfree(const void *objp)
void kfree(const void *objp)
{
    struct kmem_cache *c;
    unsigned long flags;

    /* 记录kfree轨迹 */
    trace_kfree(_RET_IP_, objp);

    /* 对地址做非零判断 */
    if (unlikely(ZERO_OR_NULL_PTR(objp)))
        return;
    local_irq_save(flags);
    kfree_debugcheck(objp);
    /* 将虚拟地址转换为缓存 */
    c = virt_to_cache(objp);
    debug_check_no_locks_freed(objp, c->object_size);

    debug_check_no_obj_freed(objp, c->object_size);
    __cache_free(c, (void *)objp, _RET_IP_);
    local_irq_restore(flags);
}

该函数首先经过trace_kfree函数记录kfree轨迹,然后对objp地址进行非零判断,接着virt_to_cache将对象的虚拟地址转换为缓存,这个函数会返回一个缓存引用,然后再__cache_free调用中使用该引用释放对象。

  • struct kmem_cache * kmem_cache_create(const char *name, unsigned int size, unsigned int align, slab_flags_t flags, void (ctor)(void ))
/* mm/slab_common.c */
/* name:名称;size:对象长度;align:对象需要对齐的数值;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);
}

创建缓存通过接口kmem_cache_create进行,创建新的缓存主要内容是申请管理结构所需要的空间,并初始化。这些管理结构包括kmem_cache、kmem_cache_node、kmem_cache_cpu。最后把kmem_cache加入到全局的缓存链表中。注意此时只是创建了size大小的缓存,并不会调用对象构造函数,只有当调用kmem_cache_alloc函数时才会调用构造函数。

  • void kmem_cache_alloc(struct kmem_cache cachep, gfp_t flags)
/* mm/slab.c */
函数调用过程:
kmem_cache_alloc -> slab_alloc -> __do_cache_alloc -> 
____cache_alloc -> 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();

    /* 获取当前CPU对应的array cache */
    ac = cpu_cache_get(cachep);
    batchcount = ac->batchcount;
    /* 对批量申请的slab对象数进行重新调整 */
    if (!ac->touched && batchcount > BATCHREFILL_LIMIT) {
        batchcount = BATCHREFILL_LIMIT;
    }
    n = get_node(cachep, node);

    BUG_ON(ac->avail > 0 || !n);
    shared = READ_ONCE(n->shared);
    if (!n->free_objects && (!shared || !shared->avail))
        goto direct_grow;

    spin_lock(&n->list_lock);
    shared = READ_ONCE(n->shared);

    /* See if we can refill from the shared array */
    if (shared && transfer_objects(ac, shared, batchcount)) {
        shared->touched = 1;
        goto alloc_done;
    }

    /* 
     * 内核必须找到array_cache->batchcount个未使用对象重新填充per-CPU缓存,
     * get_first_slab函数将首先扫描部分空闲slab链表,然后通过 slab_get_obj依次获取所有对象,直至相应的slab中没有空闲对象为止,
     * 然后遍历空闲slab链表,
     * slab获取对象时,内核必须将slab放置到正确的slab链表中
     * 直到batchcount == 0
     */
    while (batchcount > 0) {
        /* Get slab alloc is to come from. */
        page = get_first_slab(n, false);
        if (!page)
            goto must_grow;

        check_spinlock_acquired(cachep);

        /* 
         * alloc_block中通过while循环检查slab缓存中的对象是否已经全部被使用,
         * 并更新batchcount值 
         * 最后通过slab_get_obj函数将对象填充到array cache中
         */
        batchcount = alloc_block(cachep, ac, page, batchcount);
        /*
         * 将该slab从之前的链表中游离出来
         * 如果该slab对象全部被分配出去,则把它放到满slab链表
         * 否则放到部分空闲slab链中
         */
        fixup_slab_list(cachep, n, page, &list);
    }

must_grow:
    n->free_objects -= ac->avail;
alloc_done:
    spin_unlock(&n->list_lock);
    fixup_objfreelist_debug(cachep, &list);

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);

        /*
         * cache_grow_begin() can reenable interrupts,
         * then ac could change.
         */
        ac = cpu_cache_get(cachep);
        if (!ac->avail && page)
            alloc_block(cachep, ac, page, batchcount);
        cache_grow_end(cachep, page);

        if (!ac->avail)
            return NULL;
    }
    ac->touched = 1;

    /* 对象分配 */
    return ac->entry[--ac->avail];
}
  • void kmem_cache_free(struct kmem_cache cachep, void objp)
/* mm/slab.c */
void kmem_cache_free(struct kmem_cache *cachep, void *objp)
{
    unsigned long flags;
    /* 获取回收对象的kmem_cache并检查这个slab是否合法 */
    cachep = cache_from_obj(cachep, objp);
    if (!cachep)
        return;

    /* 释放mem的时候,会禁止当前cpu的irq */
    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);
    /* 
     * 释放mem
     * 会首先检查per-CPU数组中是否有空间可用
     * 是,会将对象加入缓存
     * 否,将一些对象从缓存移回slab
     */
    __cache_free(cachep, objp, _RET_IP_);
    local_irq_restore(flags);


    trace_kmem_cache_free(_RET_IP_, objp);
}

SLUB分配器结构

slub的数据结构相对于slab来说要简单很多,并且对外接口和slab兼容。

与slab相同,每个内存缓存都对应一个kmem_cache实例。

/* include/linux/slub_def.h */
struct kmem_cache {
    /* per-CPU变量,对于每个cpu来说相当于一个本地内存缓存池 */
    struct kmem_cache_cpu __percpu *cpu_slab;
    /* Used for retriving partial slabs etc */
    slab_flags_t flags;
    /* 限制kmem_cache_node中partial链表slab的数量 */
    unsigned long min_partial;
    /* 包括元数据的对象长度 */
    unsigned int size;  /* The size of an object including meta data */
    /* 对象的原始长度 */
    unsigned int object_size;/* The size of an object without meta data */
    /*
     * slub分配在管理object的时候采用的方法是:
     * 既然每个object在没有分配之前不在乎每个object中存储的内容,
     *那么完全可以在每个object中存储下一个object内存首地址,就形成了一个单链表。
     * offset存储下一个object地址数据相对于这个object首地址的偏移量 
     */
    unsigned int offset;    /* Free pointer offset. */
#ifdef CONFIG_SLUB_CPU_PARTIAL
    /* Number of per cpu partial objects to keep around */
    /* 
     * per-CPU的partial中所有slab的free object的数量的最大值
     * 超过这个值就会把所有slab转移到kmem_cache_node的partial链表中
     */
    unsigned int cpu_partial;
#endif
    /* 存放最优slab的阶数和对象数,低16位是对象数,高16位是slab阶数,即oo等于((slab的阶数<<16)|对象数)。最优slab是剩余部分最小的slab */
    struct kmem_cache_order_objects oo;

    /* Allocation and freeing of slabs */
    /* 与oo相等 */
    struct kmem_cache_order_objects max;
    /* 当按照oo大小分配内存的时候出现内存不足就会考虑用min大小分配,最小slab只需要足够存放一个对象 */
    struct kmem_cache_order_objects min;
    /* 从伙伴系统分配内存掩码 */
    gfp_t allocflags;   /* gfp flags to use on each alloc */
    int refcount;       /* Refcount for slab cache destroy */
    void (*ctor)(void *);
    /* 按照元数据的对象对齐后的大小 */
    unsigned int inuse;     /* Offset to metadata */
    /* 字节对齐大小 */
    unsigned int align;     /* Alignment */
    unsigned int red_left_pad;  /* Left redzone padding size */
    /* sysfs文件系统显示使用 */
    const char *name;   /* Name (only for display!) */
    /* 系统有一个slab_cache链表,所有的slab都会挂在这个链表上 */
    struct list_head list;  /* List of slab caches */
#ifdef CONFIG_SYSFS
    struct kobject kobj;    /* For sysfs */
    struct work_struct kobj_remove_work;
#endif
#ifdef CONFIG_MEMCG
    struct memcg_cache_params memcg_params;
    /* for propagation, maximum size of a stored attr */
    unsigned int max_attr_size;
#ifdef CONFIG_SYSFS
    struct kset *memcg_kset;
#endif
#endif

#ifdef CONFIG_SLAB_FREELIST_HARDENED
    unsigned long random;
#endif

#ifdef CONFIG_NUMA
    /*
     * Defragmentation by allocating from a remote node.
     */
    unsigned int remote_node_defrag_ratio;
#endif

#ifdef CONFIG_SLAB_FREELIST_RANDOM
    unsigned int *random_seq;
#endif

#ifdef CONFIG_KASAN
    struct kasan_cache kasan_info;
#endif

    unsigned int useroffset;    /* Usercopy region offset */
    unsigned int usersize;      /* Usercopy region size */

    /* slab节点。在NUMA系统中,每个node都有一个kmem_caceh_node数据结构 */
    struct kmem_cache_node *node[MAX_NUMNODES];
};

每个内存结点都对应一个kmem_cache_node实例。

struct kmem_cache_node {
    spinlock_t list_lock;
#ifdef CONFIG_SLUB
    /* slab结点中slab的数量 */
    unsigned long nr_partial;
    /* slab结点的slab partial链表,和kmem_cache_cpu的partial链表功能类似 */
    struct list_head partial;
#ifdef CONFIG_SLUB_DEBUG
    atomic_long_t nr_slabs;
    atomic_long_t total_objects;
    struct list_head full;
#endif
#endif
};

kmem_cache_cpu是对本地内存缓存池的描述,每个cpu对应一个kmem_cache_cpu结构体。

struct kmem_cache_cpu {
    /* 指向下一个可用object */
    void **freelist;    /* Pointer to next available object */
    /* 用于同步 */
    unsigned long tid;  /* Globally unique transaction id */
    /* slab内存的page指针 */
    struct page *page;  /* The slab from which we are allocating */
#ifdef CONFIG_SLUB_CPU_PARTIAL
    /* 本地slab partial链表 */
    struct page *partial;   /* Partially allocated frozen slabs */
#endif
#ifdef CONFIG_SLUB_STATS
    unsigned stat[NR_SLUB_STAT_ITEMS];
#endif
};

page结构体中与slub相关的成员:

  1. flags设置标志位PG_slab,表示页属于SLUB分配器。
  2. freelist指向第一个空闲对象。
  3. inuse表示已分配对象的数量。
  4. objects表示slab对象的数量。
  5. frozen表示slab是否被冻结在每处理器slab缓存中。如果slab在每处理器slab缓存中,它处于冻结状态;如果slab在内存结点的部分空闲slab链表中,它处于解冻状态。
  6. lru作为链表结点加入部分空闲slab链表。
  7. slab_cache指向kmem_cache实例。

SLAB分配器的每处理器数组entry是以对象为单位的,而SLUB分配器的每处理器slab缓存是以slab位单位的,freelist指向下一个可用对象,page指向当前正在使用的slab对应的page实例,partial指向每处理器部分空闲slab链表。

SLUB中对象有两种内存布局,区别是空闲指针的位置不同。

两种内存布局的区别是空闲指针的位置,第二种布局空闲指针充用真实对象的第一个字。kmem_cache.offset是空闲指针的偏移,空闲指针的地址等于真实对象的地址+空闲指针偏移。

每个对象的结构如上图所示,其中:

  1. 红色区域1的长度 = kmem_cache.red_left_pad = 字长对齐到指定的对齐值
  2. 红色区域2的长度 = 字长 - (对象长度 % 字长)

SLOB分配器结构

与slab相比,slob的特点就是小巧,代码只有600行左右,适合某些微小嵌入式系统。

每个内存缓存对应一个kmem_cache实例。所有内存缓存共享slab,所有对象长度小于256字节的内存缓存共享小对象slab链表中的slab,所有对象长度小于1024字节的内存缓存共享中等对象slab链表中的slab,所有对象长度小于一页的内存缓存共享大对象slab链表中的slab。对象长度大于或等于一页的内存缓存,直接从页分配器分配页。每个slab大小是一页,page结构体中与slob相关的成员:

  1. flags设置标志位PG_slab,表示页属于SLOB分配器;设置标志位PG_slob_free表示slab在slab链表中。
  2. freelist指向第一个空闲对象。
  3. units为空闲单元的数量。
  4. lru作为链表节点加入slab链表。

引用

《Linux内核深度解析》
《深入Linux内核架构》
http://www.wowotech.net/memory_management/426.html