为每种对象类型创建一个内存缓存,每个内存缓存由多个大块组成,一个大块是一个或多个连续的物理页,每个大块包含多个对象。slab采用面向对象的思想,基于对象类型管理内存,每种对象被划分为一个类,比如进程描述符(task_struct)是一个类,每个进程描述符实现是一个对象。内存缓存组成结构如下:
1)分配内存:void * kmalloc (size_t size,gfp_t flags);
2)重新分配内存:void *krealloc(const void *p, size_t new_size, gfp_t flags)
3)释放内存:void kfree ( const void * objp);
1)创建内存缓存:struct kmem_cache *kmem_cache_create(const char , size_t, size_t,unsigned long, void ()(void *));
2)指定的内存缓存分配对象:void *kmem_cache_alloc(struct kmem_cache *, gfp_t);
3)释放对象:void kmem_cache_free(struct kmem_cache *, void *);
4)销毁内存缓存:void kmem_cache_destroy(struct kmem_cache *);
• 每个内存缓存对应一个kmem_cache实例。
• 每个内存节点对应一个kmem_cache_node实例。
• kmem_cache实例的成员cpu_slab指向array_cache实例,每个处理器对应一个array_cach实例,称为数组缓存,用来缓存刚刚释放的对象,分配时首先从当前处理器的数据缓存分配,避免每次都要从slab分配,减少链表操作的锁操作,提高分配的速度。slab分配器数据结构源码分析如下:
一个高速缓存中可以含有多个kmem_cache对应的高速缓存,就拿L1高速缓存来举例,一个L1高速缓存对应一个kmem_cache链表,这个链表中的任何一个kmem_cache类型的结构体均描述一个高速缓存,而这些高速缓存在L1 cache中各自占用着不同的区域。
内存缓存的主要数据结构如下:
每个slab由一个或者多个连续的物理页组成,页的阶数是kmem_cache.gfporder,如果阶数大于0,组成一个复合页。Slab被划分位多个对象,大多数情况下slab长度不是对象长度的整数倍。
空间对象链表:每个slab需要一个空闲对象链表,从而把所有的空闲对象连接起来,空闲对象链表是用数组来实现的,数组的元素个数是slab对象数量,数组存放空闲对象的索引。
内存缓存为每个处理器创建一个数组缓存(结构体array_cache)。释放对象时,把对象存放到当前处理器对应的数组缓存中;分配对象的时候,先从当前处理器的数组缓存分配对象,采用后进先出(LIFO)原则,可以提高性能。
刚释放的对象很可能还在处理器的缓存中,可以更好地利用处理器的缓存;减少对链表的操作;避免处理器之间互斥,减少自旋锁操作。
1.分配对象的时候,先从当前处理器的数组缓存分配对象,如果数组缓存是空的,那么批量分配对象以重新填充数组缓存,批量值就是数组缓存的成员batchcount。
2.释放对象的时候,如果数组缓存是满的,那么先把数组缓存中的对象地址批量归还给slab,批量值就是数组缓存的成员batchcount,然后把正在释放的对象存到数组缓存中。
对于所有对象空间的slab,没有立即释放,而是放在空闲slab链表中。只有内存节点上
空闲对象的数量超过限制,才开始回收空闲slab,直到空闲对象的数量小于或等于限制。
节点n的空闲对象数量限制=(1+节点的处理器数量)*kmem_cache.batchcount+kmem_cache.num。
Slab分配器定期回收对象和空闲slab,实现方法是再每个处理器上向全局功罪队列添加一个延迟工作项,工作项的处理函数是cache_reap。
1.每个处理器每隔两秒钟对每个内存缓存执行回收节点n对应的远程节点数组缓存中的对象;如果过两面没有从当前处理器的数组缓存分配对象,那么回收数组缓存中的对象。
2.每个处理器每隔4秒钟对每个内存缓存执行,如果过两面没有从当共享数组缓存分配对象,那么回收共享数组缓存中的对象;如果过去4秒没有从空闲slab分配对象,那么回收空闲slab。



