首先说缓存区的数据结构:
struct kmem_cache_s { /* 1) each alloc & free */ /* full, partial first, then free */ struct list_head slabs;//指向所有的slab块链表,前面是完全块,然后是非完全块,最后是空闲块 struct list_head *firstnotfull;//指向第一个非完全块,如果没有非完全块,就指向上面的slabs unsigned int objsize;//对象的大小 unsigned int flags; /* constant flags */ unsigned int num; //对象的数量 spinlock_t spinlock; #ifdef CONFIG_SMP unsigned int batchcount; #endif /* 2) slab additions /removals */ /* order of pgs per slab (2^n) */ unsigned int gfporder;//对象的页面数2 ^ gfporder /* force GFP flags, e.g. GFP_DMA */ unsigned int gfpflags; size_t colour; /* cache colouring range */ unsigned int colour_off; /* colour offset */ unsigned int colour_next; /* cache colouring */ kmem_cache_t *slabp_cache; unsigned int growing; unsigned int dflags; /* dynamic flags */ /* constructor func */ void (*ctor)(void *, kmem_cache_t *, unsigned long);//构造函数 /* de-constructor func */ void (*dtor)(void *, kmem_cache_t *, unsigned long);//析构函数 unsigned long failures; /* 3) cache creation/removal */ char name[CACHE_NAMELEN];//名字 struct list_head next;//链接下一个缓冲区 }
再说slab块的数据结构:
typedef struct slab_s { struct list_head list; //链接下个slab块 unsigned long colouroff; //当前slab块中的第一个对象距离slab块所在页面的页面边界位置的偏移量 void *s_mem; //指向此slab块中的第一个对象的指针,等于页面边界位置+colouroff unsigned int inuse; //被使用的对象的数目 kmem_bufctl_t free; //当前第一个空闲对象在此数组中的下标 } slab_t;
我们主要研究on_slab的方法,针对于小对象。看完了数据结构,我们结合图再来理解下slab块。
图 1
我们看到上图,有一个kmem_bufctl_t类型数组。起初被赋值为1,2,3,4.....,slab中free字段赋值为0。表示第一个可供分配的是第0个对象,随后free被赋值为kmem_bufctl_t类型数组[0] = 1,表示下次第1个对象是可供分配的。
图 2
刚才缓冲区的colour_next ,colour_off,colour我们都没有解释,这里结合图2解释一下。其中页面偏移量实际上是缓存区的colour_next * colour_off得到的。colour_next 初始化为0,当colour_next达到colour时,会被重新置0。colour_off一般被赋值为L1_CACHE_BYTES,colour被赋值为slab块剩余空间/colour_off。
整体框图如下:
图 3
完全slab块在前面,非完全或者说部分slab块放在中间,空闲slab块放到最后。通过kmem_cache_s的slabs,和slab_s的list链接成双向链表。kmem_cache_s的firstnotfull指向第一个非完全slab块。
每个slab块都是 2 ^ gfporder个页面。
下面我结合一个例子,来分析slab块的分配与释放
1、创建缓存区,kmem_cache_create
kmem_cache_t * kmem_cache_create (const char *name, size_t size, size_t offset, unsigned long flags, void (*ctor)(void*, kmem_cache_t *, unsigned long), void (*dtor)(void*, kmem_cache_t *, unsigned long)) { const char *func_nm = KERN_ERR "kmem_create: "; size_t left_over, align, slab_size; kmem_cache_t *cachep = NULL; /* * Sanity checks... these are all serious usage bugs. */ if ((!name) || ((strlen(name) >= CACHE_NAMELEN - 1)) || in_interrupt() || (size < BYTES_PER_WORD) || (size > (1<<MAX_OBJ_ORDER)*PAGE_SIZE) || (dtor && !ctor) || (offset < 0 || offset > size)) BUG(); ...... ...... if (flags & ~CREATE_MASK) BUG(); /* Get cache‘s description obj. */ cachep = (kmem_cache_t *) kmem_cache_alloc(&cache_cache, SLAB_KERNEL);//分配缓存区 if (!cachep) goto opps; memset(cachep, 0, sizeof(kmem_cache_t));//将缓存区清0 ...... if (size & (BYTES_PER_WORD-1)) {//将size进行字对齐 size += (BYTES_PER_WORD-1); size &= ~(BYTES_PER_WORD-1); printk("%sForcing size word alignment - %s\n", func_nm, name); } ...... align = BYTES_PER_WORD; if (flags & SLAB_HWCACHE_ALIGN)//调整align align = L1_CACHE_BYTES; /* Determine if the slab management is ‘on‘ or ‘off‘ slab. */ if (size >= (PAGE_SIZE>>3)) ....... flags |= CFLGS_OFF_SLAB;//我们假定现在是on_slab模式 if (flags & SLAB_HWCACHE_ALIGN) { ...... while (size < align/2) align /= 2; size = (size+align-1)&(~(align-1));//调整size } ...... do { unsigned int break_flag = 0; cal_wastage: kmem_cache_estimate(cachep->gfporder, size, flags, &left_over, &cachep->num);//用来计算缓冲区中的一个slab块中可以包含的对象的个数,和slab块中剩余空间 if (break_flag) break; if (cachep->gfporder >= MAX_GFP_ORDER) break; if (!cachep->num)//N个页面的大小无法放下一个对象 goto next;//再试试更大的空间 if (flags & CFLGS_OFF_SLAB && cachep->num > offslab_limit) {//我们不关心off_slab模式 /* Oops, this num of objs will cause problems. */ cachep->gfporder--; break_flag++; goto cal_wastage; } /* * Large num of objs is good, but v. large slabs are currently * bad for the gfp()s. */ if (cachep->gfporder >= slab_break_gfp_order) break; if ((left_over*8) <= (PAGE_SIZE<<cachep->gfporder))//N个页面的大小至少要可以放下一个对象,且剩余空间不大于整个空间的1/8 break; /* Acceptable internal fragmentation. */ next: cachep->gfporder++;//试试更大的空间 } while (1); if (!cachep->num) {//说明N个页面至少可以容纳一个对象 printk("kmem_cache_create: couldn‘t create cache %s.\n", name); kmem_cache_free(&cache_cache, cachep); cachep = NULL; goto opps; } slab_size = L1_CACHE_ALIGN(cachep->num*sizeof(kmem_bufctl_t)+sizeof(slab_t)); /* * If the slab has been placed off-slab, and we have enough space then * move it on-slab. This is at the expense of any extra colouring. */ if (flags & CFLGS_OFF_SLAB && left_over >= slab_size) {//我们不关心off_slab模式 flags &= ~CFLGS_OFF_SLAB; left_over -= slab_size; } /* Offset must be a multiple of the alignment. */ offset += (align-1); offset &= ~(align-1); if (!offset)//offset为0 offset = L1_CACHE_BYTES; cachep->colour_off = offset;//前面解释了这个变量的用途 cachep->colour = left_over/offset; /* init remaining fields */ if (!cachep->gfporder && !(flags & CFLGS_OFF_SLAB)) flags |= CFLGS_OPTIMIZE; cachep->flags = flags; cachep->gfpflags = 0; if (flags & SLAB_CACHE_DMA) cachep->gfpflags |= GFP_DMA; spin_lock_init(&cachep->spinlock); cachep->objsize = size;//对象的大小 INIT_LIST_HEAD(&cachep->slabs); cachep->firstnotfull = &cachep->slabs;//刚开始时fistnotfull指向cachep->slabs if (flags & CFLGS_OFF_SLAB) cachep->slabp_cache = kmem_find_general_cachep(slab_size,0); cachep->ctor = ctor;//构造函数 cachep->dtor = dtor;//析构函数 /* Copy name over so we don‘t have problems with unloaded modules */ strcpy(cachep->name, name);//名字 ...... /* Need the semaphore to access the chain. */ down(&cache_chain_sem); { struct list_head *p; list_for_each(p, &cache_chain) { kmem_cache_t *pc = list_entry(p, kmem_cache_t, next); /* The name field is constant - no lock needed. */ if (!strcmp(pc->name, name))//看看是否重复了 BUG(); } } /* There is no reason to lock our new cache before we * link it in - no one knows about it yet... */ list_add(&cachep->next, &cache_chain);//如果不重复,就把缓存区加入到cache_chain中,如上图 up(&cache_chain_sem); opps: return cachep; }
其中kmem_cache_estimate,用来计算缓冲区中的一个slab块中可以包含的对象的个数,和slab块中剩余空间。代码如下:
static void kmem_cache_estimate (unsigned long gfporder, size_t size, int flags, size_t *left_over, unsigned int *num) { int i; size_t wastage = PAGE_SIZE<<gfporder;//2 ^ gfporder个页面的字节数 size_t extra = 0; size_t base = 0; if (!(flags & CFLGS_OFF_SLAB)) { base = sizeof(slab_t);//slab_s数据结构大小 extra = sizeof(kmem_bufctl_t);//kmem_bufctl_t类型数组个数 } i = 0; while (i*size + L1_CACHE_ALIGN(base+i*extra) <= wastage)//对象的大小 * 对象个数 + slab_s数据结构大小 + kmem_bufctl_t类型数组个数 * 对象个数 i++;//统计出最合适的对象个数 if (i > 0) i--; if (i > SLAB_LIMIT) i = SLAB_LIMIT; *num = i;//对象的个数赋值给cachep->num wastage -= i*size; wastage -= L1_CACHE_ALIGN(base+i*extra); *left_over = wastage;//剩余空间赋值给left_over }
2、分配,kmem_cache_alloc
void * kmem_cache_alloc (kmem_cache_t *cachep, int flags) { return __kmem_cache_alloc(cachep, flags); }
__kmem_cache_alloc如下:
static inline void * __kmem_cache_alloc (kmem_cache_t *cachep, int flags)//cachep就是我们刚刚创建的缓存区 { unsigned long save_flags; void* objp; kmem_cache_alloc_head(cachep, flags);//DEBUG开关开的时候才有意义 try_again: local_irq_save(save_flags); #ifdef CONFIG_SMP ...... #else objp = kmem_cache_alloc_one(cachep);//分配一个对象 #endif local_irq_restore(save_flags); return objp; alloc_new_slab: ...... local_irq_restore(save_flags); if (kmem_cache_grow(cachep, flags)) /* Someone may have stolen our objs. Doesn‘t matter, we‘ll * just come back here again. */ goto try_again; return NULL; }
其中cachep就是我们刚刚创建的缓存区。调用kmem_cache_alloc_one在slab块中分配一个对象,如果没有分配到,那么会执行kmem_cache_grow增加一个slab块。
kmem_cache_alloc_one函数代码如下:
#define kmem_cache_alloc_one(cachep) ({ slab_t *slabp; /* Get slab alloc is to come from. */ { struct list_head* p = cachep->firstnotfull; if (p == &cachep->slabs) \ //如果fistnotfull指向cachep->slabs,那么要增加一个slab块 goto alloc_new_slab; slabp = list_entry(p,slab_t, list); } kmem_cache_alloc_one_tail(cachep, slabp); })
目前firstnotfull指向cachep->slabs,goto alloc_new_slab,执行kmem_cache_grow,代码如下:
static int kmem_cache_grow (kmem_cache_t * cachep, int flags) { slab_t *slabp; struct page *page; void *objp; size_t offset; unsigned int i, local_flags; unsigned long ctor_flags; unsigned long save_flags; /* Be lazy and only check for valid flags here, * keeping it out of the critical path in kmem_cache_alloc(). */ if (flags & ~(SLAB_DMA|SLAB_LEVEL_MASK|SLAB_NO_GROW)) BUG(); if (flags & SLAB_NO_GROW) return 0; ...... if (in_interrupt() && (flags & SLAB_LEVEL_MASK) != SLAB_ATOMIC) BUG(); ctor_flags = SLAB_CTOR_CONSTRUCTOR; local_flags = (flags & SLAB_LEVEL_MASK); if (local_flags == SLAB_ATOMIC) ...... ctor_flags |= SLAB_CTOR_ATOMIC; /* About to mess with non-constant members - lock. */ spin_lock_irqsave(&cachep->spinlock, save_flags); /* Get colour for the slab, and cal the next value. */ offset = cachep->colour_next;//目前为0 cachep->colour_next++;//下一次为1 if (cachep->colour_next >= cachep->colour)//当达到最大值时,colour_next清零 cachep->colour_next = 0; offset *= cachep->colour_off;//当前偏移为0 cachep->dflags |= DFLGS_GROWN; cachep->growing++;//缓存区正在增长中 spin_unlock_irqrestore(&cachep->spinlock, save_flags); ...... /* Get mem for the objs. */ if (!(objp = kmem_getpages(cachep, flags)))//获取slab块对应大小的空间(2 ^ gfporder个页面) goto failed; /* Get slab management. */ if (!(slabp = kmem_cache_slabmgmt(cachep, objp, offset, local_flags)))//初始化slab块中slab_s数据结构 goto opps1; /* Nasty!!!!!! I hope this is OK. */ i = 1 << cachep->gfporder;//页面数 page = virt_to_page(objp); do { SET_PAGE_CACHE(page, cachep);//page结构中的list的next域赋值为指向它所在的缓存区的kmem_cache_t类型描述结构的指针 SET_PAGE_SLAB(page, slabp);//page结构中的list的next域赋值为指向它所在的slab的slab_t类型描述结构的指针 PageSetSlab(page); page++; } while (--i); kmem_cache_init_objs(cachep, slabp, ctor_flags);//设置slab块中kmem_bufctl_t类型数组和对象的初始化 spin_lock_irqsave(&cachep->spinlock, save_flags); cachep->growing--;//缓存区已经停止生长 /* Make slab active. */ list_add_tail(&slabp->list,&cachep->slabs);//将slabp链入cachep->slabs末尾,如图3,不过现在只有空闲块 if (cachep->firstnotfull == &cachep->slabs) cachep->firstnotfull = &slabp->list;//将firstnotfull指向刚刚的空闲slab块 STATS_INC_GROWN(cachep); cachep->failures = 0; spin_unlock_irqrestore(&cachep->spinlock, save_flags); return 1; opps1: kmem_freepages(cachep, objp); failed: spin_lock_irqsave(&cachep->spinlock, save_flags); cachep->growing--; spin_unlock_irqrestore(&cachep->spinlock, save_flags); return 0; }
kmem_getpages用来获取slab块对应大小的空间(2 ^ gfporder个页面),代码如下:
static inline void * kmem_getpages (kmem_cache_t *cachep, unsigned long flags) { void *addr; /* * If we requested dmaable memory, we will get it. Even if we * did not request dmaable memory, we might get it, but that * would be relatively rare and ignorable. */ flags |= cachep->gfpflags; addr = (void*) __get_free_pages(flags, cachep->gfporder);//获取 /* Assume that now we have the pages no one else can legally * messes with the ‘struct page‘s. * However vm_scan() might try to test the structure to see if * it is a named-page or buffer-page. The members it tests are * of no interest here..... */ return addr; }
kmem_cache_slabmgmt用来初始化slab块中slab_s数据结构,代码如下:
static inline slab_t * kmem_cache_slabmgmt (kmem_cache_t *cachep, void *objp, int colour_off, int local_flags) { slab_t *slabp; if (OFF_SLAB(cachep)) { /* Slab management obj is off-slab. */ slabp = kmem_cache_alloc(cachep->slabp_cache, local_flags); if (!slabp) return NULL; } else { /* FIXME: change to slabp = objp * if you enable OPTIMIZE */ slabp = objp+colour_off;//目前colour_off为0,slab块中slab_s数据结构的位置确定了 colour_off += L1_CACHE_ALIGN(cachep->num * sizeof(kmem_bufctl_t) + sizeof(slab_t));//当前slab块中的第一个对象距离slab块所在页面的页面边界位置的偏移量 } slabp->inuse = 0;//活跃对象数量是0 slabp->colouroff = colour_off; slabp->s_mem = objp+colour_off;//第一个对象的内存地址 return slabp; }
SET_PAGE_CACHE(page, cachep),page结构中的list的next域赋值为指向它所在的缓存区的kmem_cache_t类型描述结构的指针,代码如下:
#define SET_PAGE_CACHE(pg,x) ((pg)->list.next = (struct list_head *)(x))
SET_PAGE_SLAB(page, slabp),page结构中的list的next域赋值为指向它所在的slab的slab_t类型描述结构的指针,代码如下:
#define SET_PAGE_SLAB(pg,x) ((pg)->list.prev = (struct list_head *)(x))
PageSetSlab,设置页的flags,代码如下:
#define PageSetSlab(page) set_bit(PG_slab, &(page)->flags)
kmem_cache_init_objs,设置slab块中kmem_bufctl_t类型数组和对象的初始化,代码如下:
static inline void kmem_cache_init_objs (kmem_cache_t * cachep, slab_t * slabp, unsigned long ctor_flags) { int i; for (i = 0; i < cachep->num; i++) { void* objp = slabp->s_mem+cachep->objsize*i; ...... if (cachep->ctor) cachep->ctor(objp, cachep, ctor_flags);//用构造函数构造所有对象 ...... slab_bufctl(slabp)[i] = i+1;//形成的格局是图2,数组中元素是1,2,3,4.... } slab_bufctl(slabp)[i-1] = BUFCTL_END;//比如有5个对象,那么第5个元素依次是1,2,3,4,BUFCTL_END slabp->free = 0;//表示第0个对象是可供分配的 }
回到__kmem_cache_alloc,kmem_cache_grow返回1,goto try_again,kmem_cache_alloc_one分配对象,代码如下:
#define kmem_cache_alloc_one(cachep) ({ slab_t *slabp; /* Get slab alloc is to come from. */ { struct list_head* p = cachep->firstnotfull; if (p == &cachep->slabs) \ //此时firstnotfull指向刚刚的空闲slab块 goto alloc_new_slab; slabp = list_entry(p,slab_t, list); \ //空闲的slab块 } kmem_cache_alloc_one_tail(cachep, slabp); })
kmem_cache_alloc_one_tail,代码如下:
static inline void * kmem_cache_alloc_one_tail (kmem_cache_t *cachep, slab_t *slabp) { void *objp; STATS_INC_ALLOCED(cachep); STATS_INC_ACTIVE(cachep); STATS_SET_HIGH(cachep); /* get obj pointer */ slabp->inuse++;//被分配了的对象数量加1 objp = slabp->s_mem + slabp->free*cachep->objsize;//被分配对象的地址,目前分配的是第一个对象,slabp->free为0 slabp->free=slab_bufctl(slabp)[slabp->free];//slabp->free为1 if (slabp->free == BUFCTL_END) /* slab now full: move to next slab for next alloc */ cachep->firstnotfull = slabp->list.next;//如果当前slab没有了空闲对象,那么firstnotfull执行下一个slab块 ...... return objp; }
不断调用kmem_cache_alloc,直到分配到三个完全块,一个部分块为至,假设完全块有5个对象,部分块现在只有两个对象,这是图3,最初期的样子,随着对象的释放,会逐渐变成图 3的样子。
3、释放,kmem_cache_free
(1)、我们首先释放第4个部分块的对象,上面我们说过部分块一共两个对象,我们首先释放第2个对象。
void kmem_cache_free (kmem_cache_t *cachep, void *objp)//objp为要释放对象的地址,cachep就是我们刚刚创建的缓存区 { unsigned long flags; ...... local_irq_save(flags); __kmem_cache_free(cachep, objp); local_irq_restore(flags); }
__kmem_cache_free,代码如下:
static inline void __kmem_cache_free (kmem_cache_t *cachep, void* objp) { ...... kmem_cache_free_one(cachep, objp); ...... }
kmem_cache_free_one,代码如下:
现在kmem_bufctl_t类型数组,元素是1,2,3,4,BUFCTL_END,slabp->free为2。
static inline void kmem_cache_free_one(kmem_cache_t *cachep, void *objp) { slab_t* slabp; CHECK_PAGE(virt_to_page(objp)); /* reduces memory footprint * if (OPTIMIZE(cachep)) slabp = (void*)((unsigned long)objp&(~(PAGE_SIZE-1))); else */ slabp = GET_PAGE_SLAB(virt_to_page(objp));//得到这个对象所在slab块的描述结构指针 ...... { unsigned int objnr = (objp - slabp->s_mem)/cachep->objsize;//得到第2个对象在slab块内的对象数组中的下标,为1 slab_bufctl(slabp)[objnr] = slabp->free;//现在kmem_bufctl_t类型数组,元素是1,2,3,4,BUFCTL_END,不变 slabp->free = objnr;//free为1 } STATS_DEC_ACTIVE(cachep); /* fixup slab chain */ if (slabp->inuse-- == cachep->num)//不是由完全块变成部分块 goto moveslab_partial; if (!slabp->inuse)//不是由部分块变成完全块 goto moveslab_free; return; moveslab_partial: ...... { struct list_head *t = cachep->firstnotfull; cachep->firstnotfull = &slabp->list; if (slabp->list.next == t) return; list_del(&slabp->list); list_add_tail(&slabp->list, t); return; } moveslab_free: ...... { struct list_head *t = cachep->firstnotfull->prev; list_del(&slabp->list); list_add_tail(&slabp->list, &cachep->slabs); if (cachep->firstnotfull == &slabp->list) cachep->firstnotfull = t->next; return; } }
virt_to_page,代码如下:
#define virt_to_page(kaddr) (mem_map + (__pa(kaddr) >> PAGE_SHIFT))
GET_PAGE_SLAB,得到这个对象所在slab块的描述结构指针,代码如下:
#define GET_PAGE_SLAB(pg) ((slab_t *)(pg)->list.prev)
(2)、接着,释放第4个部分块的对象,上面我们说过部分块一共两个对象,刚才释放了第2个对象,我们再释放第1个对象。
kmem_cache_free_one,代码如下:
现在kmem_bufctl_t类型数组,元素是1,2,3,4,BUFCTL_END,slabp->free为1。
static inline void kmem_cache_free_one(kmem_cache_t *cachep, void *objp) { slab_t* slabp; CHECK_PAGE(virt_to_page(objp)); /* reduces memory footprint * if (OPTIMIZE(cachep)) slabp = (void*)((unsigned long)objp&(~(PAGE_SIZE-1))); else */ slabp = GET_PAGE_SLAB(virt_to_page(objp));//得到这个对象所在slab块的描述结构指针 ...... { unsigned int objnr = (objp - slabp->s_mem)/cachep->objsize;//得到第1个对象在slab块内的对象数组中的下标,为0 slab_bufctl(slabp)[objnr] = slabp->free;//现在kmem_bufctl_t类型数组,元素是1,2,3,4,BUFCTL_END,不变 slabp->free = objnr;//free为0 } STATS_DEC_ACTIVE(cachep); /* fixup slab chain */ if (slabp->inuse-- == cachep->num)//不是由完全块变成部分块 goto moveslab_partial; if (!slabp->inuse)//是由部分块变成空闲块 goto moveslab_free;//转到moveslab_free return; moveslab_partial: ...... { struct list_head *t = cachep->firstnotfull; cachep->firstnotfull = &slabp->list; if (slabp->list.next == t) return; list_del(&slabp->list); list_add_tail(&slabp->list, t); return; } moveslab_free: ...... { struct list_head *t = cachep->firstnotfull->prev;//指向前面的第三个完全块 list_del(&slabp->list); list_add_tail(&slabp->list, &cachep->slabs);//将这个空闲块挪到cachep->slabs末尾 if (cachep->firstnotfull == &slabp->list)//如果当前firstnotfull指向了刚才的空闲块 cachep->firstnotfull = t->next;//firstnotfull指向第三个完全块的下一个块,其实还是刚才的空闲块 return; } }
第4个部分块已经变成了空闲块
(3)、我们开始释放第3个完全块的对象,上面我们说过完全块一共5个对象,我们首先释放第2个对象。
kmem_cache_free_one,代码如下:
现在kmem_bufctl_t类型数组,元素是1,2,3,4,BUFCTL_END,slabp->free为BUFCTL_END。
static inline void kmem_cache_free_one(kmem_cache_t *cachep, void *objp) { slab_t* slabp; CHECK_PAGE(virt_to_page(objp)); /* reduces memory footprint * if (OPTIMIZE(cachep)) slabp = (void*)((unsigned long)objp&(~(PAGE_SIZE-1))); else */ slabp = GET_PAGE_SLAB(virt_to_page(objp));//得到这个对象所在slab块的描述结构指针 ...... { unsigned int objnr = (objp - slabp->s_mem)/cachep->objsize;//得到第2个对象在slab块内的对象数组中的下标,为1 slab_bufctl(slabp)[objnr] = slabp->free;//现在kmem_bufctl_t类型数组,元素是1,BUFCTL_END,3,4,BUFCTL_END slabp->free = objnr;//free为1 } STATS_DEC_ACTIVE(cachep); /* fixup slab chain */ if (slabp->inuse-- == cachep->num)//是由完全块变成部分块 goto moveslab_partial;//执行这里 if (!slabp->inuse)//不是由部分块变成空闲块 goto moveslab_free; return; moveslab_partial: ...... { struct list_head *t = cachep->firstnotfull;//原来指向第4个空闲块 cachep->firstnotfull = &slabp->list;//fistnotfull指向第三个部分块 if (slabp->list.next == t)//第三个部分块的下一个 等于 第4个空闲块,return return; list_del(&slabp->list); list_add_tail(&slabp->list, t); return; } moveslab_free: ...... { struct list_head *t = cachep->firstnotfull->prev; list_del(&slabp->list); list_add_tail(&slabp->list, &cachep->slabs); if (cachep->firstnotfull == &slabp->list) cachep->firstnotfull = t->next; return; } }
第3个完全块,已经变成了部分块。
(4)、接着,我们释放第3个部分块的对象,上面已经释放了第2个对象,现在我们释放第4个对象。
kmem_cache_free_one,代码如下:
现在kmem_bufctl_t类型数组,元素是1,BUFCTL_END,3,4,BUFCTL_END,slabp->free为1。
static inline void kmem_cache_free_one(kmem_cache_t *cachep, void *objp) { slab_t* slabp; CHECK_PAGE(virt_to_page(objp)); /* reduces memory footprint * if (OPTIMIZE(cachep)) slabp = (void*)((unsigned long)objp&(~(PAGE_SIZE-1))); else */ slabp = GET_PAGE_SLAB(virt_to_page(objp));//得到这个对象所在slab块的描述结构指针 ...... { unsigned int objnr = (objp - slabp->s_mem)/cachep->objsize;//得到第4个对象在slab块内的对象数组中的下标,为3 slab_bufctl(slabp)[objnr] = slabp->free;//现在kmem_bufctl_t类型数组,元素为1,BUFCTL_END,3,1,BUFCTL_END slabp->free = objnr;//free为3 } STATS_DEC_ACTIVE(cachep); /* fixup slab chain */ if (slabp->inuse-- == cachep->num)//不是由完全块变成部分块 goto moveslab_partial; if (!slabp->inuse)//不是由部分块变成空闲块 goto moveslab_free; return; moveslab_partial: ...... { struct list_head *t = cachep->firstnotfull; cachep->firstnotfull = &slabp->list; if (slabp->list.next == t) return; list_del(&slabp->list); list_add_tail(&slabp->list, t); return; } moveslab_free: ...... { struct list_head *t = cachep->firstnotfull->prev; list_del(&slabp->list); list_add_tail(&slabp->list, &cachep->slabs); if (cachep->firstnotfull == &slabp->list) cachep->firstnotfull = t->next; return; } }
这样就形成了图 3的布局,两个完全块,一个部分块,最后一个是空闲块。
下次,在分配对象时,从部分块开始分配,free为3,首先分配第4个对象;然后free赋值为1(kmem_bufctl_t类型数组[4] 为 1),分配第二个对象;free赋值为BUFCTL_END((kmem_bufctl_t类型数组[1] 为 BUFCTL_END)),已经没有可供分配的对象了。这时开始在空闲块中分配,相关代码为:
static inline void * kmem_cache_alloc_one_tail (kmem_cache_t *cachep, slab_t *slabp) { void *objp; STATS_INC_ALLOCED(cachep); STATS_INC_ACTIVE(cachep); STATS_SET_HIGH(cachep); /* get obj pointer */ slabp->inuse++; objp = slabp->s_mem + slabp->free*cachep->objsize; slabp->free=slab_bufctl(slabp)[slabp->free]; if (slabp->free == BUFCTL_END) /* slab now full: move to next slab for next alloc */ cachep->firstnotfull = slabp->list.next;//指向第4个空闲块 ...... return objp; }