原始出处: http://blog.chinaunix.net/uid-20687780-id-3154025.html
按: 从slab管理来看,当需要释放对象时,如果其中有page没有释放的,会导致页泄漏。
内存slab分配器最初思想来自Solaris的内核态小数据结构(一页以内)的内存分配,受到Solaris的影响,Linux内核也采用类似思想来减少页内碎片,其基本思想是:一次向内核获取整数页,slab根据数据结构的大小进行划分为一个个小的数据结构,当需要时直接从该链表上摘取一个返回应用程序,当应用程序释放时,而非真正释放,只需要该空间放回到链表中,当分散的一页多块又聚集一页时,又会拼成一页,同时判断slab空闲的页数,如果空闲页超过一定的页数,就会向系统释放一定的页数。一个slab分配器只能管理一个指定大小的数据结构分配。总体结构如下:
一个slab分配器管理许多页,而每一页又管理许多的对象。为了有效管理页,即便于分配与释放,又根据页内对象的数量将页分为3个状态:
1.空闲(free)状态:该页没有分配出去任何对象
2.部分分配(partial)状态:该页只有部分对象被分配
3.完全分配(full)状态:该页所有对象都被分配出去。
除此之外,为了方便管理页内对象通常需要将对象进行对齐。这样一页内部的结构如下:
| 对象空间 | 对齐 | 对象空间 | 对齐 | …....... | 空闲空间 | 页描述性信息 |
而提供分配的API主要为:
1.创建一个slab分配器
umem_cache_create(const char *name,size_t size,unsignd int align,
unsigned int flags,void (*ctor)(void *),void (*dctor)(void *))
2.分配一个对象
void *umem_cache_alloc(umem_cache_t *cp);
3.释放一个对象
void *umem_cache_free(umem_cache_t *cp,void *buf)
4.销毁一个slab分配器
void umem_cache_destroy(umem_cache_t *cp)
主要的数据结构则为页管理对象和slab管理对象
为了判断一页中空闲对象的数量,采用引用计数的方式来实现,具体页管理对象为:
struct slab {
struct list list;
struct list slab_list;
int used;
}
而具体的slab管理对象则是链接三个状态的页和其它管理信息,具体如下:
typedef struct umem_cache {
const char *name;//slab名称
unsigned long size;//对象大小
unsigned int nrsize;//一页中对象的数量
….....
void (*ctor)(void *);//对象初始化函数
void (*dtor)(void *);//对象销毁函数
struct list slabs_partial;//部分分配链表
struct list slabs_full;//完全分配链表
struct list slabs_free;//完全空闲链表
union {//用于分配对象的同步机制,按照标志进行设置
sem_t sem;
pthread_spinlock_t spinlock;
}lock;
}umem_cache_t;
当创建一个slab分配器时,则根据标志进行创建,并想(按:向)内存获取一页数据用于预分配(这里将具体源代码放在一个函数中,源码分布在多个函数中):
umem_cache_t *umem_cache_create(const char *name,size_t size......)
{
…....
cachep = (umem_cache_t *)calloc(1,sizeof(umem_cache_t));
…........
//这里从进程地址空间的映射区,也可通过从堆分配一页空间
mem=mmap(NULL,cachep->pagesize,....);
//通过页的起始地址来获取页的尾地址
slab = (struct slab *)(((ptr_t)mem | (pagesize - 1)) - sizeof(struct slab));
…...
for(i = 0;i<nrsize;i++) {<="" p="" style="word-wrap: break-word;">
struct list *n;
n = (struct list *)(mem + i*cachep->size);
list_init(n);
list_add_head(&slab->slab_list,n);
}
cachep->nrpage++;
list_add_head(cachep->slabs_free,&slab->list);
cachep->nrfree += nrsize;
….......
}
分配一个对象,直接从slabs_parital中分配,如果该空间已满,就从slabs_free中获取一页再进行分配:
void *umem_cache_alloc(umem_cache_t *cachep) {
__lock_wait(cachep);
//如果free为空,再获取一页
if(!cachep->nrfree && (get_one_page(cachep,&cachep->slabs_free))<0)
goto __last;
//先从paritial中分配
if(!list_empty(&cachep->slabs_patial)) {
mem = __alloc_node(cachep,cachep->slabs_partial.next);
goto __last;
}
if(!list_empty(&cachep->slabs_free)) {
list_move(&cachep->slabs_partial,&cachep->slabs_free.next);
mem = __alloc_node(cachep,cachep->slabs_parital.next);
}
__last:
{
__lock_post(cachep);
if(mem) {
memset(mem,0,cachep->size);
if(cachep->ctor)//调用构造函数初始化
cachep->ctor(mem);
}
}
}
当释放一个对象时,需要将对象放入对应页链表中,并修改信息,同时根据对象的数量放到slab中不同的状态链表中.
void umem_cache_free(umem_cache_t *cachep,void *mem)
{
if(mem && cachep->dtor)
cachep->dtor(mem);
…..
list_init(mem);
list_add_head(slab_list,mem);
…......
check_free(cachep);
}
当销毁一个对象时,客户端需要保证所有的对象已经被释放,否则出现页没有被释放,造成页泄漏。
void umem_cache_destroy(umem_cache_t *cp)
{
….....
//直接遍历free链表,释放页空间
list_for_each_entry_del(slab,n,&cachep->slabs_free,list)
{
release_one_page(cachep,slab);
}
…......
}
源码见:https://github.com/ytaoWang/utillib
参考资料
2.Linux内核slab分配器
3.Jeff Bonwick,The Slab Allocator:An Object-Caching Kernel Memory Allocator,Sun Microsystem
4. OpenSolaris源码
本文介绍了内存slab分配器的基本思想与实现细节,包括如何减少页内碎片、对象分配与回收机制,以及核心数据结构的设计等。


4529

被折叠的 条评论
为什么被折叠?



