Linux内核源代码情景分析-内存管理之slab-分配与释放

首先说缓存区的数据结构:

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;
}
时间: 2024-10-10 18:26:24

Linux内核源代码情景分析-内存管理之slab-分配与释放的相关文章

Linux内核源代码情景分析-内存管理之slab-回收

在上一篇文章Linux内核源代码情景分析-内存管理之slab-分配与释放,最后形成了如下图的结构: 图 1 我们看到空闲slab块占用的若干页面,不会自己释放:我们是通过kmem_cache_reap和kmem_cache_shrink来回收的.他们的区别是: 1.我们先看kmem_cache_shrink,代码如下: int kmem_cache_shrink(kmem_cache_t *cachep) { if (!cachep || in_interrupt() || !is_chaine

Linux内核源代码情景分析-内存管理

用户空间的页面有下面几种: 1.普通的用户空间页面,包括进程的代码段.数据段.堆栈段.以及动态分配的"存储堆". 2.通过系统调用mmap()映射到用户空间的已打开文件的内容. 3.进程间的共享内存区. 这些页面的的周转有两方面的意思. 1.页面的分配,使用,回收.如进程压栈时新申请的页面,这类页面不进行盘区交换,不使用时释放得以回收. 这部分通过一个场景来解释: Linux内核源代码情景分析-内存管理之用户堆栈的扩展. 2.盘区交换.如要执行硬盘上的对应代码段.把硬盘上的代码段换入内

Linux内核源代码情景分析-内存管理之用户页面的定期换出

我们已经看到在分配页面时,如果页面数不够,那么会调用page_launder,reclaim_page,__free_page将页面换出,并重新投入分配. 为了避免总是在CPU忙碌的时候,也就是在缺页异常发生的时候,临时再来搜寻可供换出的内存页面并加以换出,Linux内核定期地检查并且预先将若干页面换出,腾出空间,以减轻系统在缺页异常发生时的负担. 为此,在Linux内核中设置了一个专司定期将页面换出的"守护神"kswapd和kreclaimd. static int __init k

Linux内核源代码情景分析-内存管理之用户页面的分配

首先介绍几个重要的数据结构. 1.page typedef struct page { struct list_head list; struct address_space *mapping; unsigned long index; struct page *next_hash; atomic_t count; unsigned long flags; /* atomic flags, some possibly updated asynchronously */ struct list_h

Linux内核源代码情景分析-内存管理之用户堆栈的扩展

在下面几种情况下会发生,页面出错异常(也叫缺页中断): 1.相应的页面目录项或者页面表项为空,也就是该线性地址与物理地址的映射关系尚未建立,或者已经撤销.本文讨论的就是这种情况. 2.相应的物理页面不在内存中. 3.指令中规定的访问方式与页面的权限不符,例如企图写一个"只读"的页面. 首先看下进程地址空间示意图: 假设现在需要调用某个子程序,因此CPU需将返回地址压入堆栈,也就是要将返回地址写入虚拟空间地址为(%esp-4)的地方.可是,在我们这个情景中地址(%esp-4)落入了空洞中

Linux内核源代码情景分析-内存管理之用户页面的换入

在下面几种情况下会发生,页面出错异常(也叫缺页中断): 1.相应的页面目录项或者页面表项为空,也就是该线性地址与物理地址的映射关系尚未建立,或者已经撤销. 2.相应的物理页面不在内存中. 本文讨论的就是这种情况. 3.指令中规定的访问方式与页面的权限不符,例如企图写一个"只读"的页面. 假设已经建立好了映射,但是页表项最后一位P为0,表示页面不在内存中:整个页表项如下图,offset表示页面在一个磁盘设备的位置,也就是磁盘设备的逻辑页面号:而type则是指该页面在哪一个磁盘设备中. 图

Linux内核源代码情景分析-内存管理之恢复映射

refill_inactive_scan和swap_out,把活跃的页面变成不活跃脏的页面.挑选的原则是最近没有被访问,且age小于0. page_launder,把不活跃脏的页面变成不活跃干净的页面. 不活跃脏的页面,有如下特点: 使用计数为1: page->list链入mapping->dirty_pages/clean_pages: page->next_hash和page->pprev_hash链入全局的Hash表: page->lru链入了全局的inactive_d

Linux内核源代码情景分析-共享内存

一.库函数shmget()--共享内存区的创建与寻找 asmlinkage long sys_shmget (key_t key, size_t size, int shmflg) { struct shmid_kernel *shp; int err, id = 0; down(&shm_ids.sem); if (key == IPC_PRIVATE) { err = newseg(key, shmflg, size);//分配一个共享内存区供本进程专用,最后返回的是一体化的标示号 } el

Linux内核源代码情景分析-交换分区

在Linux内核源代码情景分析-共享内存中,共享内存,当内存紧张时是换出到交换分区. 在Linux内核源代码情景分析-mmap后,文件与虚拟区间建立映射中,文件映射的页面,当内存紧张时是换出到硬盘上的文件中. 这里的交换分区,就是是swap分区,记得给电脑安装ubuntu时,就有一项是swap分区. 交换分区和文件的区别是: 文件是在一个具体的文件系统之下的,交换分区没有这个必要,它可能是一个裸分区,不需要文件系统. 需要说明一点,并不是所有从物理内存中交换出来的数据都会被放到Swap中(如果这