slab 伙伴分配器


slab 思想及编程接口

  1. slab核心思想 为每种对象类型创建一个内存缓存,每个内存缓存由多个大块组成,一个大块是一个或多个连续的物理页,每个包含多个对象。slab采用面向对象的思想,基于对象类型管理内存,每种对象被划分为一个类,比如进程描述符(task_struct)是一个类,每个进程描述符实现是一个对象

    内存缓存组成结构如下

  2. 编程接口

      //slab.h
        void * __must_check __krealloc(const void *, size_t,gfp_t);
        void * __must_check krealloc(const void *,size_t, gfp_t);
        void kfree(const void * );
        void kzfree(const void *);
        size_t ksize(const void *);
    
    • 分配内存:void * kmalloc(size_t size,gfp_t flags);
    • 重新分配内存:void krealloc(const void p, size_t new_size, gfp_t flags)
    • 释放内存: void kfree(const void * objp);
    • 创建内存缓存:struct kmem_cache kmem_cache_create(const char,size_t,size_t ,unsigned long ,void()(void ));
    • 指定的内存缓存分配对象:void kmem_cache_alloc(struct kmem_cache , gfp_t);
    • 释放对象:void kmem_cache_free(struct kmem_cache , void );
    • 销毁内存缓存:void kmem_cache_destroy(struct kmem_cache *);

    创建内存缓存,name:名称,size:对象长度,align:对象需要对齐的数组,flags: slab标志位,strc: 对象的构造函数

    struct kmem_cache * kmem_cache_create(const char * name,size_t size,size_t align ,unsigned long flags ,void( strc)(void ));

    指定内存缓存 分配对象 cachep:从指定的内存缓存分配 flags:传递给页分配器的分配标志位,当内存缓存没有空闲对象,向页分配器请求分配页的时候使用这个分配标志位。成功返回地址,失败返回空指针

    void kmem_cache_alloc(struct kmem_cache cachep,gfp_t flags)

    释放对象, cachep:对象所属的内存缓存,objp:对象的地址

    void kmem_cache_free(struct kmem_cache * cachep, void *objp);

    销毁内存缓存, s:内存缓存

    void kmem_cache_destory(struct kmem_cache *s)

slab 数据结构

  • 每个内存缓存对应一个kmem_cache实例
  • 每个内存节点对应一个kmem_cache_node实例
  • kmem_cache实例的成员cpu_slab指向array_cache实例,每个处理器对应一个array_cache实例,成为数组缓存,用来缓存刚刚释放的对象,分配时首先从当前处理器的数据缓存分配,避免每次都要从slab分配,减少链表的锁操作,提高分配的速度,slab分配器数据结构源码分析如下:
     struct slab {
        struct list_head list; //将slab 连接到各个slab链表上面
        unsigned long colouroff;//slab 中第一个对象偏移
        void *s_mem;//slab 中的第一个对象地址
        unsigned int inuse;//有多少对象正在被使用
        kmem_bufctl_t free;//下一个空闲对象的下标
        unsigned short nodeid;//用于寻址在高速缓存中kmem_list3的下标
       }

高速缓存描述符数据结构:struct kmem_cache

一个高速缓存中可以含有多个kmem_cache对应的告诉缓存,对于L1告诉缓存来举例,一个L1高速缓存对应一个kmem_cache链表,这个链表中的任何一个kmem_cache类型的结构体描述一个高速缓存,而这些高速缓存在L1 cache中各自占有者不同的区域

//slab_def.h,高速缓存描述
struct kmem_cache {
    struct array_cache  __percpu *cpu_cache; //为了提高效率,每个cpu都有一个slab空闲对象缓存
    unsigned int batchcount;//从本地高速缓存批量移入或移出对象的数量
    unsigned int limit;//本地高速缓存中空闲对象的最大数量
    unsigned int shared;
    unsigned int buffer_size;
    u32 reciprocal_buffer_size;

    unsigned intnum; //每个slab的obj对象个数
    unsigned int gfporder;//每个slab中连续页框的数量

    size_t colour;//slab使用的颜色个数
    unsigned int colour_off; //slab颜色偏移
    struct kmem_cache *freelist_cache;
    unsigned int freelist_cache;
    //内存节点实例个数
    struct kmem_cache_node *node[MAX_NUMNODES];
}

struct array_cache {
    unsigned int avail; //本地高速缓存中可用对象的个数,也是空闲数组位置的索引
    unsigned int limit; //本地高速缓存大小
    unsigned int batchcount; // 本地高速缓存填充或者清空使使用的对象个数
    unsigned int touched;//如果本地高速缓存最近被使用过,设置成1
    void *entry[]; /* //对象的地址
}



//slab.h
struct kmem_cache_node {
    spinlock_t list_lock;
#ifdef CONFIG_SLAB
    struct list_head slabs_partial;// 对象被使用一部分的slab描述符的链表
    struct list_head slabs_full;//对象被占用了的slab描述符链表
    struct list_head slabs_free;//只包含空闲对象的slab描述符的链表
    unsigned long total_slabs;
    unsigned long free_slabs;
    unsigned long free_objects;//高速缓存中空闲对象的个数
    unsigned int free_limit;
    unsigned int colour_next;
    struct array_cache *shared ;//指向所有cpu所共享的一个本地高速缓存;
    struct array_cache **alien;
    unsigned long next_reap; //由slab的页回收算法使用
    int free_touched ;//由slab页回收算法使用
#endif
}

每个slab由一个或多个连续的物理页组成,页的除数是kmem_cache.gfporder,如果阶数大于0,组成一个复合页。slab被划分为多个对象,大多数情况下slab长度不是对象长度的整数倍

// 该页描述结构的地址空间radix树和page_tree中的对象索引号,即页号
union {
    pgoff_t index;
    void *freelist;
};

空间对象链表:每个slab需要一个空闲对象链表,从而把所有空闲对象连接起来,空闲对象链表使用数组实现,数组的元素个数是slab对象数量,数组存放空闲对象的索引。

每处理器数组缓存

内容缓存为每个处理器创建一个数组缓存(结构体array_cache).释放对象时,把对象存放到当前处理器对应的数组缓存中; 分配对象的时候,先从当前处理器的数组缓存分配对像,采用后进先出(LIFO)原则,可以提高性能。

  1. 刚释放的对象很可能还在处理器的缓存中,可以更好地利用处理器的缓存;
  2. 减少对链表的操作;
  3. 避免处理器之间互斥,减少自旋锁操作。

分配对象的时候,先从当前处理器的数组缓存分配对象,如果数组缓存是空的,那么批量分配对象以重新填充数组缓存,批量值就是数组缓存的成员batchcount.

释放对象的时候,如果数组缓存是满的,那么先把数组缓存中的对象地址批量归还给slab,批量值是数组缓存的成员batchcount,然后把正在释放的对象存放到数组缓存中。

回收内存

对于所有对象空间的slab,没有立即释放,而是放在空闲slab 链表中,只有内存节点上空闲对象的数量超过限制,才开始回收空闲slab,知道空闲对象的数量小于或等于限制

节点n的空闲对象数量限制 = (1+节点的处理器) * kmem_cache.batchcount + kmem_cache.num。

Slab分配器定期回收对象和空闲slab,实现方法是再每个处理器上向全局工作队列添加一个延迟工作项,工作项的处理函数是cache_reap。

  1. 每个处理器每隔2秒钟对每个内存缓存执行回收节点n对应的远程节点数组缓存中的对象;如果过2s没有从当前处理器的数组缓存分配对象,那么回收数组缓存中的对象。
  2. 每个处理器每隔4秒钟对每个内存缓存执行,如果过4s没有从当共享数组缓存分配对象,那么回收共享数组缓存中的对象;如果过去4s没有从空闲slab分配对象,那么回收空闲slab。