slab 思想及编程接口
-
slab核心思想 为每种对象类型创建一个内存缓存,每个内存缓存由多个大块组成,一个大块是一个或多个连续的物理页,每个包含多个对象。slab采用面向对象的思想,基于对象类型管理内存,每种对象被划分为一个类,比如进程描述符(task_struct)是一个类,每个进程描述符实现是一个对象
内存缓存组成结构如下
-
编程接口
//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)原则,可以提高性能。
- 刚释放的对象很可能还在处理器的缓存中,可以更好地利用处理器的缓存;
- 减少对链表的操作;
- 避免处理器之间互斥,减少自旋锁操作。
分配对象的时候,先从当前处理器的数组缓存分配对象,如果数组缓存是空的,那么批量分配对象以重新填充数组缓存,批量值就是数组缓存的成员batchcount.
释放对象的时候,如果数组缓存是满的,那么先把数组缓存中的对象地址批量归还给slab,批量值是数组缓存的成员batchcount,然后把正在释放的对象存放到数组缓存中。
回收内存
对于所有对象空间的slab,没有立即释放,而是放在空闲slab 链表中,只有内存节点上空闲对象的数量超过限制,才开始回收空闲slab,知道空闲对象的数量小于或等于限制
节点n的空闲对象数量限制 = (1+节点的处理器) * kmem_cache.batchcount + kmem_cache.num。
Slab分配器定期回收对象和空闲slab,实现方法是再每个处理器上向全局工作队列添加一个延迟工作项,工作项的处理函数是cache_reap。
- 每个处理器每隔2秒钟对每个内存缓存执行回收节点n对应的远程节点数组缓存中的对象;如果过2s没有从当前处理器的数组缓存分配对象,那么回收数组缓存中的对象。
- 每个处理器每隔4秒钟对每个内存缓存执行,如果过4s没有从当共享数组缓存分配对象,那么回收共享数组缓存中的对象;如果过去4s没有从空闲slab分配对象,那么回收空闲slab。