内存管理-之内核内存管理-基于linux3.10

关于启动过程内存管理见《内存管理-之启动

如果需要,内存管理三篇文章整理成pdf了,下载地址http://download.csdn.net/detail/shichaog/8662135

第四章 物理内存管理

图2.2中(见内存管理-之启动)已经展示了伙伴系统对内存的组织管理,但是并未关联具体的实现代码,Linux内核并未将伙伴系统的管理代码单独列在一个buddy.c的文件里。

4.1 伙伴系统内存组织

由图2.2可知,每一个内存域(zone)的管理代码的组织是伙伴系统,所以其必然有字段来抽象这一管理方法。

<include/linux/mmzone.h>
struct zone {
…
	struct free_area	free_area[MAX_ORDER];
…
}

如果在编译内核时没有强制内存大小,则将是11,即由2^11个页组成一个连续的区域可以被分配使用。伙伴系统的页块组织如图4.1.1。


图4.1.1伙伴系统页块组织

/proc/buddyinfo包含了伙伴系统的相关信息。

对free_area和迁移类型的定义如下:

<include/linux/mmzone.h>
enum {
	MIGRATE_UNMOVABLE,
	MIGRATE_RECLAIMABLE,
	MIGRATE_MOVABLE,
	MIGRATE_PCPTYPES,	/* the number of types on the pcp lists */
	MIGRATE_RESERVE = MIGRATE_PCPTYPES,
#ifdef CONFIG_CMA
	MIGRATE_CMA,
#endif
#ifdef CONFIG_MEMORY_ISOLATION
	MIGRATE_ISOLATE,	/* can't allocate from here */
#endif
	MIGRATE_TYPES
};
struct free_area {
	struct list_head	free_list[MIGRATE_TYPES];
	unsigned long		nr_free; //当前该链表上空闲成员数
};

迁移类型的目标就是反内存碎片,Linux3.10内核有多达7中迁移类型,比较明显的是per-CPU类型的迁移类型的增加。

MIGRATE_UNMOVABLE标识类型的内存在内存中有固定的地址范围且不能够被移动,多数的内核核心代码从此类型内存申请空间。

MIGRATE_RECLAIMABLE,不能够直接移动,但是内存可以被回收,数据结构可以被重建。由文件映射的数据就是这一类型。

MIGRATE_MOVABLE:可以被移动,属于用户空间的页就是这一类型的。

MIGRATE_PCPTYPES:管理per-CPU变量的类型。

MIGRATE_RESERVE:其和MIGRATE_PCPTYPES是一样的类型的,数据为紧急情况预留的类型。

MIGRATE_ISOLATE:是一个特殊的虚拟内存域,用于NUMA情况下夸节点移动物理页,不能从其申请内存。

当一个迁移类型对应的链表无法满足用户申请内存需求时,就可能会到其它迁移类型的链表上申请内存,Linux对去其它迁移类型链表申请内存也给出了一套优先级数组。

mm/page_alloc.c
static int fallbacks[MIGRATE_TYPES][4] = {
	[MIGRATE_UNMOVABLE]   = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE,     MIGRATE_RESERVE },
	[MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE,   MIGRATE_MOVABLE,     MIGRATE_RESERVE },
#ifdef CONFIG_CMA
	[MIGRATE_MOVABLE]     = { MIGRATE_CMA,         MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_RESERVE },
	[MIGRATE_CMA]         = { MIGRATE_RESERVE }, /* Never used */
#else
	[MIGRATE_MOVABLE]     = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE,   MIGRATE_RESERVE },
#endif
	[MIGRATE_RESERVE]     = { MIGRATE_RESERVE }, /* Never used */
#ifdef CONFIG_MEMORY_ISOLATION
	[MIGRATE_ISOLATE]     = { MIGRATE_RESERVE }, /* Never used */
#endif
};

第一个数组元素的意义是,当在MIGRATE_UNMOVABLE迁移类型的链表申请内存不能满足要求时,会依次去MIGRATE_RECLAIMABLE、MIGRATE_MOVABLE、MIGRATE_RESERVE类型的迁移链表上查找内存。页迁移类型的设置接口如下:

mm/page_alloc.c
void set_pageblock_migratetype(struct page *page, int migratetype)
{

	if (unlikely(page_group_by_mobility_disabled))
		migratetype = MIGRATE_UNMOVABLE;

	set_pageblock_flags_group(page, (unsigned long)migratetype,
					PB_migrate, PB_migrate_end);
}

4.2 伙伴系统API

伙伴系统是基于页的思想来管理内存的,所以其API操作的内存都是基于2的指数被进行的。细粒度的分配使用的是slab分配方法。include/linux/gfp.h包含了伙伴系统的API。本节就是对gfp.h文件的剖析。

alloc_pages(mask,order) 分配2的阶次大小的内存,返回struct page类型的实例,其表示的是图4.1.1中链接各个内存块链表头的那一个页,即和箭头有直接关联的页。定义于include/linux/gfp.h

4.2.1 内存申请flag

关于申请内存的flag及其作用部分的定义如下(GFP是Get FreePage缩写):

/* 裸GFP位掩码,API接口类调用并不直接使用这些位掩码,它们的前面下划线有三个 */
#define ___GFP_DMA		0x01u
#define ___GFP_HIGHMEM		0x02u
#define ___GFP_DMA32		0x04u
#define ___GFP_MOVABLE		0x08u
#define ___GFP_WAIT		0x10u
#define ___GFP_HIGH		0x20u
#define ___GFP_IO		0x40u
#define ___GFP_FS		0x80u
#define ___GFP_COLD		0x100u
#define ___GFP_NOWARN		0x200u
#define ___GFP_REPEAT		0x400u
#define ___GFP_NOFAIL		0x800u
#define ___GFP_NORETRY		0x1000u
#define ___GFP_MEMALLOC		0x2000u
#define ___GFP_COMP		0x4000u
#define ___GFP_ZERO		0x8000u
#define ___GFP_NOMEMALLOC	0x10000u
#define ___GFP_HARDWALL		0x20000u
#define ___GFP_THISNODE		0x40000u
#define ___GFP_RECLAIMABLE	0x80000u
#define ___GFP_KMEMCG		0x100000u
#define ___GFP_NOTRACK		0x200000u
#define ___GFP_NO_KSWAPD	0x400000u
#define ___GFP_OTHER_NODE	0x800000u
#define ___GFP_WRITE		0x1000000u
/* 如果修改了上述位掩码,, __GFP_BITS_SHIFT 也许需要修改 */

/*
 * GFP位掩码.
 *
 *域修改符(linux/mmzone.h 文件的低三比特定义了选择的zone类型)
*/
#define __GFP_DMA	((__force gfp_t)___GFP_DMA) ///DMA类型的zone申请页
#define __GFP_HIGHMEM	((__force gfp_t)___GFP_HIGHMEM)
#define __GFP_DMA32	((__force gfp_t)___GFP_DMA32)//x86-64采用该标志
#define __GFP_MOVABLE	((__force gfp_t)___GFP_MOVABLE)  /* Page is movable */
#define GFP_ZONEMASK	(__GFP_DMA|__GFP_HIGHMEM|__GFP_DMA32|__GFP_MOVABLE)
/*
 *分配方式修改符 – 并不改变申请内存的域类型。
 *
 * __GFP_REPEAT: 努力尝试分配内存,但是也可能失败,这取决于VM的实现
* __GFP_NOFAIL: 无休止尝试申请内存,知道成功,目前已遗弃 *
 * __GFP_NORETRY: VM实现不能无休止尝试申请内存。
 *
 * __GFP_MOVABLE: 申请的页类型是可以被迁移类型改变或者能够被回收
*/
#define __GFP_WAIT	((__force gfp_t)___GFP_WAIT)	/*能够等待和调度? */
#define __GFP_HIGH	((__force gfp_t)___GFP_HIGH)	/*是否应该从紧急内存池申请? */
#define __GFP_IO	((__force gfp_t)___GFP_IO)	/* 能够执行物理IO操作? 可能存在换页操作?*/
#define __GFP_FS	((__force gfp_t)___GFP_FS)	/* 能够调用底层文件操作? */
#define __GFP_COLD	((__force gfp_t)___GFP_COLD)	/*需要cache的冷页 */
#define __GFP_NOWARN	((__force gfp_t)___GFP_NOWARN)	/* 禁止内存分配失败警告*/
#define __GFP_REPEAT	((__force gfp_t)___GFP_REPEAT)	/* See above */
#define __GFP_NOFAIL	((__force gfp_t)___GFP_NOFAIL)	/* See above */
#define __GFP_NORETRY	((__force gfp_t)___GFP_NORETRY) /* See above */
#define __GFP_MEMALLOC	((__force gfp_t)___GFP_MEMALLOC)/* 允许从紧急备用内存块申请*/
#define __GFP_COMP	((__force gfp_t)___GFP_COMP)	/* 添加复合页元数据*/
#define __GFP_ZERO	((__force gfp_t)___GFP_ZERO)	/* 成功时页全部被清零了*/
#define __GFP_NOMEMALLOC ((__force gfp_t)___GFP_NOMEMALLOC) /*不要使用紧急备用内存.
#define __GFP_HARDWALL   ((__force gfp_t)___GFP_HARDWALL) /* Enforce hardwall cpuset memory allocs */
#define __GFP_THISNODE	((__force gfp_t)___GFP_THISNODE)/* 只在该node申请*/
#define __GFP_RECLAIMABLE ((__force gfp_t)___GFP_RECLAIMABLE) /* 页可以回收*/
#define __GFP_NOTRACK	((__force gfp_t)___GFP_NOTRACK)  /* Don't track with kmemcheck */

#define __GFP_NO_KSWAPD	((__force gfp_t)___GFP_NO_KSWAPD)
#define __GFP_OTHER_NODE ((__force gfp_t)___GFP_OTHER_NODE) /* On behalf of other node */
#define __GFP_KMEMCG	((__force gfp_t)___GFP_KMEMCG) /* Allocation comes from a memcg-accounted resource */
#define __GFP_WRITE	((__force gfp_t)___GFP_WRITE)	/* Allocator intends to dirty page */

/* 等于0,但是使用常量以防止后续改变*/
#define GFP_NOWAIT	(GFP_ATOMIC & ~__GFP_HIGH)
/* GFP_ATOMIC意味着既不等待 (__GFP_WAIT不设置)并且可以使用紧急备用内存池 */
#define GFP_ATOMIC	(__GFP_HIGH)
#define GFP_NOIO	(__GFP_WAIT)
#define GFP_NOFS	(__GFP_WAIT | __GFP_IO)
#define GFP_KERNEL	(__GFP_WAIT | __GFP_IO | __GFP_FS)
#define GFP_TEMPORARY	(__GFP_WAIT | __GFP_IO | __GFP_FS | \
			 __GFP_RECLAIMABLE)
#define GFP_USER	(__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
#define GFP_HIGHUSER	(__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | \
			 __GFP_HIGHMEM)
#define GFP_HIGHUSER_MOVABLE	(__GFP_WAIT | __GFP_IO | __GFP_FS | \
				 __GFP_HARDWALL | __GFP_HIGHMEM | \
				 __GFP_MOVABLE)
#define GFP_IOFS	(__GFP_IO | __GFP_FS)
#define GFP_TRANSHUGE	(GFP_HIGHUSER_MOVABLE | __GFP_COMP | \
			 __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN | \
			 __GFP_NO_KSWAPD)

#ifdef CONFIG_NUMA
#define GFP_THISNODE	(__GFP_THISNODE | __GFP_NOWARN | __GFP_NORETRY)
#else
#define GFP_THISNODE	((__force gfp_t)0)
#endif

/* This mask makes up all the page movable related flags */
#define GFP_MOVABLE_MASK (__GFP_RECLAIMABLE|__GFP_MOVABLE)

/* Control page allocator reclaim behavior */
#define GFP_RECLAIM_MASK (__GFP_WAIT|__GFP_HIGH|__GFP_IO|__GFP_FS|\
			__GFP_NOWARN|__GFP_REPEAT|__GFP_NOFAIL|\
			__GFP_NORETRY|__GFP_MEMALLOC|__GFP_NOMEMALLOC)

/* Control slab gfp mask during early boot */
#define GFP_BOOT_MASK (__GFP_BITS_MASK & ~(__GFP_WAIT|__GFP_IO|__GFP_FS))

/* 控制分配限制*/
#define GFP_CONSTRAINT_MASK (__GFP_HARDWALL|__GFP_THISNODE)

/* slab分配器不允许使用下面的分配掩码*/
#define GFP_SLAB_BUG_MASK (__GFP_DMA32|__GFP_HIGHMEM|~__GFP_BITS_MASK)

/* Flag - indicates that the buffer will be suitable for DMA.  Ignored on some
   platforms, used as appropriate on others */

#define GFP_DMA		__GFP_DMA

/* 4GB DMA on some platforms */
#define GFP_DMA32	__GFP_DMA32

根据上面定义掩码可以获得迁移类型和zone类型,这通过

static inline enum zone_type gfp_zone(gfp_t flags)
static inline int allocflags_to_migratetype(gfp_t gfp_flags)

实际上这类的操作比较比较比较简单,为节省篇幅,后面对单独获取掩码的位函数将不再列出。

4.2.2 内存申请释放API

Include/linux/gfp.h

  • alloc_pages(gfp_mask, order),根据NUMA启用情况,有两个版本alloc_pages接口,这里的接口是没有使用NUMA的情况,gfp_mask是4.1.1节中申请标志,order是分配的阶,底数是2,对于order等于0的情况则退化为申请一个页。即alloc_pages(gfp_mask, 0),gfp.h文件专门定义了申请一页的接口:

#definealloc_page(gfp_mask) alloc_pages(gfp_mask, 0)

  • get_zeroed_page(gfp_t gfp_mask),用于申请一个页,该页会被0填充。
  • alloc_page_vma(gfp_mask, vma, addr)  ,为VMA申请一页。
  •  __get_dma_pages(gfp_mask, order),从DMA域申请内存。

如果内存分配失败,则返回值是NULL或者0,所以调用者需要检查返回值,这些接口最后会调用核心函数__alloc_pages_nodemask完成实际的内存分配。

图4.2.2伙伴系统内存申请函数

水印影响系统内存分配行为,加上NUMA存储架构,内存分配复杂性大大增加。在zone的定义中:

enum zone_watermarks {
	WMARK_MIN,
	WMARK_LOW,
	WMARK_HIGH,
	NR_WMARK
};
struct zone {
	unsigned long watermark[NR_WMARK];
…
}

WMARK_HIGH:表示的是可用内存如果超过watermark[WMARK_HIGH],则说明可用内存很充裕,在这种情况下,内存分配过程代码相对简单,接着如果超过watermark[WMARK_LOW],则说明内存不是非常充裕,但是也不是处于紧张的状态,这时分配过程相对而言比第一种情况稍微复杂点,如果达到WMARK_MIN状态,在分配内存时会立即唤醒内存回收守护进程kswap,进行换页操作以腾出更多空间,这一过程也是最复杂的。为了让分配的流程变的明晰,下列的分析对一些对分配流程中的一些不太重要的代码(NUMA也被去掉,但并不是不重要)进行了精简。

mm/page_alloc.c
struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,
			struct zonelist *zonelist, nodemask_t *nodemask)
{
	enum zone_type high_zoneidx = gfp_zone(gfp_mask);//获得zone索引,DMA、NORMAL…
	struct zone *preferred_zone;
	struct page *page = NULL;
	int migratetype = allocflags_to_migratetype(gfp_mask);//迁移类型,Movable….
	unsigned int cpuset_mems_cookie;
	int alloc_flags = ALLOC_WMARK_LOW|ALLOC_CPUSET;
	struct mem_cgroup *memcg = NULL;

	gfp_mask &= gfp_allowed_mask;
	might_sleep_if(gfp_mask & __GFP_WAIT); //如果线程设置了TIF_NEED_RESCHED标志,则会执行调度,当前进程休眠
	if (should_fail_alloc_page(gfp_mask, order)) //检查分配阶和掩码的合理性,
		return NULL;
//见查zone是否存在,通常zone是存在的,但是GFP_THISNODE指定了申请内存的node之后就未必了。
	if (unlikely(!zonelist->_zonerefs->zone))
		return NULL;
retry_cpuset:
/* 找到合适的zone,该zone的索引值小于参数high_zoneidx,找到该zone后,将该zone的地址存在参数zone中。*/
	first_zones_zonelist(zonelist, high_zoneidx,
				nodemask ? : &cpuset_current_mems_allowed,
				&preferred_zone);
//如果没有找到合适的zone,则直接返回。
	if (!preferred_zone)
		goto out;
	/* 初次分配内存尝试,buddy系统分配内存核心函数之一,该函数分析见后文*/
	page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, nodemask, order,
			zonelist, high_zoneidx, alloc_flags,
			preferred_zone, migratetype);
//失败情况下,会进一步申请内存
	if (unlikely(!page)) {
		gfp_mask = memalloc_noio_flags(gfp_mask);
		page = __alloc_pages_slowpath(gfp_mask, order,
				zonelist, high_zoneidx, nodemask,
				preferred_zone, migratetype);
	}

out:
	if (unlikely(!put_mems_allowed(cpuset_mems_cookie) && !page))
		goto retry_cpuset;

	return page;
}

伙伴系统的核心分配函数get_page_from_freelist定义如下,该函数会验证空闲页数量和水印的关系,如果满足则会调用buffered_rmqueue从伙伴系统上移除分配的页。

static struct page *
get_page_from_freelist(gfp_t gfp_mask, nodemask_t *nodemask, unsigned int order,
		struct zonelist *zonelist, int high_zoneidx, int alloc_flags,
		struct zone *preferred_zone, int migratetype)
{
	struct zoneref *z;
	struct page *page = NULL;
	int classzone_idx;
	struct zone *zone;
	nodemask_t *allowednodes = NULL;/* zonelist_cache approximation */
	int zlc_active = 0;		/* set if using zonelist_cache */
	int did_zlc_setup = 0;		/* just call zlc_setup() one time */

	classzone_idx = zone_idx(preferred_zone);//保持zone的索引号
zonelist_scan:
	for_each_zone_zonelist_nodemask(zone, z, zonelist,
						high_zoneidx, nodemask) {
		if ((alloc_flags & ALLOC_CPUSET) && //CPU SET用于将CPU资源和进程捆绑。
			!cpuset_zone_allowed_softwall(zone, gfp_mask)) //检查这里的zone是否属于允许运行该进程的CPU上				continue;
	
		//如果没有启用水印检查,则跳过if语句。
		if (!(alloc_flags & ALLOC_NO_WATERMARKS)) {
			unsigned long mark;
			int ret;

			mark = zone->watermark[alloc_flags & ALLOC_WMARK_MASK]; //根据分配flag获得对应内存的水印值。
//根据上述水印值和需要分配内存的阶判断该zone是否能够用来分配内存,如果能够则到try_this_zone标号尝试内存分配。
			if (zone_watermark_ok(zone, order, mark,
				    classzone_idx, alloc_flags))
				goto try_this_zone;
//如果没有启用NUMA,ret的返回值是0,这时需要回收一些内存,
			ret = zone_reclaim(zone, gfp_mask, order);
			switch (ret) {
			case ZONE_RECLAIM_NOSCAN:
				/* did not scan */
				continue;
			case ZONE_RECLAIM_FULL:
				/* scanned but unreclaimable */
				continue;
			default:
				/* 回收可以异步进行,这里判断是否回收足够的内存了 */
				if (zone_watermark_ok(zone, order, mark,
						classzone_idx, alloc_flags))
					goto try_this_zone;
				continue;
			}
		}
//分配函数,和水印判断函数一并在后面。
try_this_zone:
		page = buffered_rmqueue(preferred_zone, zone, order,
						gfp_mask, migratetype);
		if (page)
			break;
	}
	if (page)
		page->pfmemalloc = !!(alloc_flags & ALLOC_NO_WATERMARKS);

	return page;
}

从上面的代码注释可以看出,在未启用NUMA的情况下,get_page_from_freelist的主要功能集中于两个函数:

  •  zone_watermark_ok,判断水印是否满足
  • buffered_rmqueue,执行从伙伴系统将空闲页移除给申请内存的进程

free_pages是NR_FREE_PAGES统计信息得到的当前zone的空闲页数目,

static bool __zone_watermark_ok(struct zone *z, int order, unsigned long mark,
		      int classzone_idx, int alloc_flags, long free_pages)// free_pages存放的是zone当前空闲页
{
//mark值是watermark[WMARK_HIGH],或者watermark[WMARK_LOW]或者watermark[WMARK_MIN],即系统要求的空闲水印值
	long min = mark; 
	long lowmem_reserve = z->lowmem_reserve[classzone_idx]; //该zone保留的页数量,紧急情况会使用这些页
	int o;
//free_pages存放的是zone当前空闲页 - 需要分配出去的页==分配出去以后的空闲页,可能小于0,但没有影响
	free_pages -= (1 << order) - 1;
	if (alloc_flags & ALLOC_HIGH) //如果启用的是ALLOC_HIGH,则将系统水印值除以2
		min -= min / 2;
	if (alloc_flags & ALLOC_HARDER) //HARDER情况继续减去四分之一
		min -= min / 4;

	if (free_pages  <= min + lowmem_reserve)// 如果分配出去需要的阶内存后,不能满足系统水印,则返回错误吧
		return false;
/*上述条件满足,还不能确定一定就能分配成功,for循环将剩余内存总量,分配到各阶上。
* 将系统剩余的内存量减去o阶,nr_free是对应的空闲对象书,左移o阶后,得到的是空闲页数量,
*
	for (o = 0; o < order; o++) {
		/* At the next order, this order's pages become unavailable */
		free_pages -= z->free_area[o].nr_free << o; //减去o阶后,剩下的空闲页数。

		/* 阶越大,需要的空闲页越少,该值记录的是根据内存分配紧张标识调整水印后的需要空闲的页数量 */
		min >>= 1;

		if (free_pages <= min)//比较空闲页数量是否满足水印的要求
			return false;
	}
	return true;
}

4.2.3 移除选定的页

移除页的函数执行流程


图4.2.3 buffered_rmqueue代码调用流程

static inline
struct page *buffered_rmqueue(struct zone *preferred_zone,	struct zone *zone, int order, 
gfp_t gfp_flags, 	int migratetype)
{
	unsigned long flags;
	struct page *page;
	int cold = !!(gfp_flags & __GFP_COLD);

again://阶是0,申请一页的情况,可以从per-CPU冷热页链表上取。
	if (likely(order == 0)) {
/**************************per_cpu_pages的定义如下*******************
**********struct per_cpu_pages {
**********int count;		/* 链表上的页总数*/
**********int high;		/* high watermark */
**********int batch;		/* 伙伴系统添加和移除块大小*/
****	******/* 页链表,每一个迁移类型对应一个pcp链表*/
**********struct list_head lists[MIGRATE_PCPTYPES];
*********};
**********************************************************************/
		struct per_cpu_pages *pcp;
		struct list_head *list;

		local_irq_save(flags);//保持中断标志。
		pcp = &this_cpu_ptr(zone->pageset)->pcp; //获得该CPU对应的pcp链表。
		list = &pcp->lists[migratetype];//获得对应迁移类型的pcp链表
		if (list_empty(list)) {//如果该链表是空,则没法直接从该链表获得
/*从伙伴系统获得指定的元素大小的页,并将它们添加到pcp链表上,返回值是得到的可用的页数。其调用__rmqueue函数完成核心分配,在申请多余一页的情况下也会调用__rmqueue函数。*/
			pcp->count += rmqueue_bulk(zone, 0, 
					pcp->batch, list,
					migratetype, cold);
			if (unlikely(list_empty(list)))
				goto failed;
		}
//根据申请的是冷页还是热(所谓的冷热是相对在不在cache中,热指的是在cache中,)
//页标志,在该链表的首部是热页,尾部是冷页。
		if (cold)
			page = list_entry(list->prev, struct page, lru);
		else
			page = list_entry(list->next, struct page, lru);

		list_del(&page->lru);//将该page从page的管理链表上删除。
		pcp->count--;
	} else {
		spin_lock_irqsave(&zone->lock, flags);
		page = __rmqueue(zone, order, migratetype);//从伙伴系统选择一个合适的页块,失败返回NULL
		spin_unlock(&zone->lock);
		if (!page)
			goto failed;
		__mod_zone_freepage_state(zone, -(1 << order),
					  get_pageblock_migratetype(page));
	}

	__count_zone_vm_events(PGALLOC, zone, 1 << order);
	zone_statistics(preferred_zone, zone, gfp_flags);//跟新zone统计数据
	local_irq_restore(flags);

	VM_BUG_ON(bad_range(zone, page));
//进行一些检查和设置各种flag标志,通常这里不该应有错,如果这里有错,意味着内核其它地方有问题。
	if (prep_new_page(page, order, gfp_flags)) 
		goto again;
	return page;

failed:
	local_irq_restore(flags);
	return NULL;
}

__rmqueue会调用__rmqueue_smallest函数获得申请的内存,但是如果内存域、阶和迁移类型不满足,则会调用__rmqueue_fallback从其它迁移列表获取内存。__rmqueue_smallest的主要核心代码如下:

//遍历每一个order,只有order比当前要求order大才有可能成功申请到内存页。
	for (current_order = order; current_order < MAX_ORDER; ++current_order) {
		area = &(zone->free_area[current_order]);//获得该zone的伙伴管理核心数据结构。
//如果该伙伴对应的迁移类型的链表上没有空闲元素,则遍历下一阶。
		if (list_empty(&area->free_list[migratetype]))
			continue;
//如果有空闲元素,则获得该空闲元素的起始项,在图4.1.1中,可以看到取首页就可以了。
		page = list_entry(area->free_list[migratetype].next,
							struct page, lru);
		list_del(&page->lru);//对应的页已经被分配,从空闲链表删除。
		rmv_page_order(page);//设置页的flag,这是页不在属于buddy了,
		area->nr_free--;
//如果申请的阶大于需要的阶,则需要将高阶的进行拆解并插入低阶buddy链表上,expand完成此工作。
		expand(zone, page, order, current_order, area, migratetype);
		return page;
	}

Expand函数如下,low是要分配的阶,high是找到的当前能够满足分配要求的阶:

static inline void expand(struct zone *zone, struct page *page,
	int low, int high, struct free_area *area,
	int migratetype)
{
	unsigned long size = 1 << high;//得到总页数

	while (high > low) {
		area--;
		high--;
		size >>= 1;//遍历一次,
//size定位拆分后伙伴块的起始页框描述符,并将其作为第一个块添加到下一级order的块链表中。
		list_add(&page[size].lru, &area->free_list[migratetype]);
		area->nr_free++;
		set_page_order(&page[size], high);
	}
}

其工作方式使用一个简单的实例可以看出:预期分配的阶是1,满足索取要求的内存阶3,index设为0,即:expand(zone, page,1,3,area,migratetype);

图4.2.4展示了expand执行的流程,图中1_1,表示的是第一次循环时执行的第一步,1_2是第一次循环时第二步,2_1是第二个循环时执行的第一步,依次类推。

1_1将area由阶三指向阶是2的free_area区,1_2将high值减一,high值变为2,1_3将size除以2,由8变为4,1_4是关键的一步,以size为索引,&page[size]获得后一半(4页)内存区地址,并通过list_add将其添加到阶为后一半大小的area链表上,第二次的循环依次类推,只不过其后一半内存区,变成了前4页的后一半(蓝色框所示)。

图4.2.4 expand函数逻辑

         如果调用__rmqueue_fallback从其它迁移列表获取内存,则说明该迁移类型内存缺少,这是内核倾向于申请一块大的而不是正适合申请内存大小。



static inline struct page *
__rmqueue_fallback(struct zone *zone, int order, int start_migratetype)
{
	struct free_area * area;
	int current_order;
	struct page *page;
	int migratetype, i;

	/* 从高阶开始,尽量获取一个大块内存,因为该迁移类型还要满足后续内存请求*/
	for (current_order = MAX_ORDER-1; current_order >= order;
						--current_order) {
		for (i = 0;; i++) {
			migratetype = fallbacks[start_migratetype][i]; //获得备用的迁移类型。

			/* MIGRATE_RESERVE handled later if necessary */
			if (migratetype == MIGRATE_RESERVE) 
				break;
			//如果该free_area没有空闲块,则遍历更低一阶
			area = &(zone->free_area[current_order]);
			if (list_empty(&area->free_list[migratetype]))
				continue;
	//如果free_area有空闲块,则获得该块的第一个page实例,该页包含了块的管理信息
			page = list_entry(area->free_list[migratetype].next,
					struct page, lru);
			area->nr_free--;

			/*
			is_migrate_cma判断迁移类型是否是MIGRATE_CMA,该迁移类型是contigous Memory  Allocator的缩写。在2011年该类型支持分配大的、物理上连续的内存块,关于该迁移类型的显示解释见http://lwn.net/Articles/486301/,这里假设内核没有使用该迁移类型以简化从备用迁移类型申请内存这一过程。			 
如果申请的是大块内存,则将所有空闲页移动到优先选用的分配列表,如果是可回收类型,则更积极的获得的页所有权。*/
			if (!is_migrate_cma(migratetype) &&
			    (unlikely(current_order >= pageblock_order / 2) ||
			     start_migratetype == MIGRATE_RECLAIMABLE ||
			     page_group_by_mobility_disabled)) {
				int pages;
				pages = move_freepages_block(zone, page,
								start_migratetype);

				/*块有一半是空闲的,则获取整个块*/
				if (pages >= (1 << (pageblock_order-1)) ||
						page_group_by_mobility_disabled)
					set_pageblock_migratetype(page,
								start_migratetype);

				migratetype = start_migratetype;
			}

			/*从空闲列表移除页 */
			list_del(&page->lru);
			rmv_page_order(page);

			/* 获取 orders >= pageblock_order的所有权 */
			if (current_order >= pageblock_order &&
			    !is_migrate_cma(migratetype))
				change_pageblock_range(page, current_order,
							start_migratetype);

			expand(zone, page, order, current_order, area,
			       is_migrate_cma(migratetype)
			     ? migratetype : start_migratetype);

			trace_mm_page_alloc_extfrag(page, order, current_order,
				start_migratetype, migratetype);

			return page;
		}
	}

	return NULL;
}

4.3.4 释放页

对应的释放内存的API有:

void __free_pages(struct page *page, unsigned int order);
void free_pages(unsigned long addr, unsigned int order);
#define __free_page(page) __free_pages((page), 0)
#define free_page(addr) free_pages((addr), 0)

上面调用的核心释放函数是__free_pages,该函数针对一页和多页进行了不同处理,主要体现在是存放在per-CPU变量中,还是释放给buddy系统。

//将page的引用计数(&page->_count)减一,如果为0,则说明没有被使用,则需要进行释放工作。

void __free_pages(struct page *page, unsigned int order)
{
	if (put_page_testzero(page)) {
//如果是一页,则添加到per-CPU缓存中
		if (order == 0)
			free_hot_cold_page(page, 0);
		else
//s释放多个页的情况
			__free_pages_ok(page, order);
	}
}

free_hot_cold_page释放单个页,如果cold是1,则释放的是冷页,是0,则释放的是热页。

void free_hot_cold_page(struct page *page, int cold)
{
	struct zone *zone = page_zone(page); //有page获得其所在的zone
	struct per_cpu_pages *pcp;
	unsigned long flags;
	int migratetype;

	if (!free_pages_prepare(page, 0)) //释放页之前要检查一下,是否真的适合释放。
		return;

	migratetype = get_pageblock_migratetype(page); //获得该页所在块的迁移类型。
	set_freepage_migratetype(page, migratetype);
	local_irq_save(flags);
//获得per-CPU缓存链表
	pcp = &this_cpu_ptr(zone->pageset)->pcp;
	if (cold) //冷页
		list_add_tail(&page->lru, &pcp->lists[migratetype]);
	else //热页
		list_add(&page->lru, &pcp->lists[migratetype]);
	pcp->count++;
//如果per-CPU缓存中页数目超出pcp->high,则需要进行惰性合并,将数量为pcp->batch的页退还给伙伴系统
	if (pcp->count >= pcp->high) {
		free_pcppages_bulk(zone, pcp->batch, pcp);
		pcp->count -= pcp->batch;
	}
out:
	local_irq_restore(flags);
}

当释放多页时,会牵涉到将释放的页退还给buddy管理系统,这一过程到不复杂,不过却有点绕,首先看几个该过程的辅助函数。

//根据给定的页帧号和释放的阶找到buddy系统其另一半,比如,如果page_idx是10,order是0,则,该函数的返回值是11,说明页帧号10、11原本是order是1的一个块。如果order是1,而page_idx仍然是10,那么该函数的返回值是8,说明8、9以及10、11可以组成一个阶为2的块。

static inline unsigned long
__find_buddy_index(unsigned long page_idx, unsigned int order)
{
	return page_idx ^ (1 << order);
}

上述的函数只是说明存在合并的可能,至于能不能合并取决于如下条件:

1、  找到的伙伴页不能是hole,而必须是存在对应物理页的。

2、  需要组合成一个更高阶的两个页块的域号必须一致,不能DMA域和NORMAL域的页合并。

3、  两个要合并的页块它们的阶必须是相同的,不同阶之间的页块不能直接合并

4、  当然该伙伴块还必须属于伙伴系统page_is_guard(buddy)。

5、  另外,该伙伴页块必须空闲,这样才能和正被释放的页块合并。

static inline int page_is_buddy(struct page *page, struct page *buddy,
								int order)
{
	if (!pfn_valid_within(page_to_pfn(buddy)))
		return 0;

	if (page_zone_id(page) != page_zone_id(buddy))
		return 0;

	if (PageBuddy(buddy) && page_order(buddy) == order) {
		VM_BUG_ON(page_count(buddy) != 0);
		return 1;
	}
	return 0;
}

释放页的时候会检查是否需要合并页,这是由函数__free_one_page完成的,为了使这个有点绕的过程不绕人,以一个例子展示这一过程。

释放一个0阶内存块(一页),页帧号是10,页帧号0~15均处于空闲状态且在buddy系统中,页帧号为16的为已经分配出去的页,该页所处的页块不能被合并。

图4.2.5 页合并实例

static inline void __free_one_page(struct page *page,
		struct zone *zone, unsigned int order,
		int migratetype)
{
	unsigned long page_idx;
	unsigned long combined_idx;
	unsigned long uninitialized_var(buddy_idx);
	struct page *buddy;
//获得页帧号
	page_idx = page_to_pfn(page) & ((1 << MAX_ORDER) - 1);
//该while循环完成了图4.2.5的合并过程。
	while (order < MAX_ORDER-1) {
		buddy_idx = __find_buddy_index(page_idx, order); //找到伙伴索引
		buddy = page + (buddy_idx - page_idx);//根据偏移找到伙伴页
		if (!page_is_buddy(page, buddy, order))//见前面
			break;
		//如何可以进行页块合并,则将伙伴从该阶对应的链表上删除
		list_del(&buddy->lru);
		zone->free_area[order].nr_free--; //更新该order对应的空闲块数
//更新该页标志,因要被合并而不再是伙伴页框的第一个单元,清除伙伴标志和阶信息
		rmv_page_order(buddy); 
//找到这两个可以合并页框的起始的那个,之前是由函数计算的,这里用一句代替以前封装的一个函数。
		combined_idx = buddy_idx & page_idx;
		page = page + (combined_idx - page_idx);//合并后块的起始第一个页帧。
		page_idx = combined_idx;//下一次循环准备
		order++;//阶信息递增一个
	}
	set_page_order(page, order);// rmv_page_order(buddy);的逆过程

	/*
如果当前合并后的页块的阶并非最大,则将其放大伙伴链表的末尾,这样其不会很快被使用到,更有可能合并到更高阶。
	 */
	if ((order < MAX_ORDER-2) && pfn_valid_within(page_to_pfn(buddy))) {
		struct page *higher_page, *higher_buddy;
		combined_idx = buddy_idx & page_idx;
		higher_page = page + (combined_idx - page_idx);
		buddy_idx = __find_buddy_index(combined_idx, order + 1);
		higher_buddy = higher_page + (buddy_idx - combined_idx);
		if (page_is_buddy(higher_page, higher_buddy, order + 1)) {
			list_add_tail(&page->lru,
				&zone->free_area[order].free_list[migratetype]);
			goto out;
		}
	}
//如果更高阶已经分配出去,则合并成更高阶的可能性变小,将其放在链表前面
	list_add(&page->lru, &zone->free_area[order].free_list[migratetype]);
out:
	zone->free_area[order].nr_free++;
}

表4-1 图4.2.5合并过程的计算

order

page_idx

buddy_idx - page_idx

combined_idx

0

10

1

10

1

10

-2

8

2

8

4

8

3

8

-8

0

4.4  slab分配器

Slab是内存管理中一个重要部分,以前遇到一个图像卡顿的问题就是通过查看slab信息才定位到问题所在子模块的,slab变动比较小,我接触到的版本2.6.38,3.10,3.13使用的都是2.1版本的slab分配器,这节就来看看slab分配器吧。

和伙伴系统一样是为了管理内存,但是其管理的粒度要比伙伴系统小。查看当前系统的slab信息可以使用cat /proc/slabinfo命令查看。该命令的第一行已经说明了各个字段的意义,第一列的字段(name)是slab的名称,即slab创建函数kmem_cache_create的第一个参数,这在后面分析该函数时可以看到。

# cat /proc/slabinfo 
slabinfo - version: 2.1
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
ubifs_inode_slab      39     40    384   10    1 : tunables   54   27    0 : slabdata      4      4      0
SCTPv6                 1      4    928    4    1 : tunables   54   27    0 : slabdata      1      1      0
SCTP                   0      0    800    5    1 : tunables   54   27    0 : slabdata      0      0      0
sctp_chunk             0      0    192   20    1 : tunables  120   60    0 : slabdata      0      0      0
sctp_bind_bucket       0      0     16  203    1 : tunables  120   60    0 : slabdata      0      0      0
xfrm6_tunnel_spi       0      0     64   59    1 : tunables  120   60    0 : slabdata      0      0      0
fib6_nodes            14    113     32  113    1 : tunables  120   60    0 : slabdata      1      1      0
ip6_dst_cache         15     15    256   15    1 : tunables  120   60    0 : slabdata      1      1      0
ndisc_cache           13     24    160   24    1 : tunables  120   60    0 : slabdata      1      1      0
ip6_mrt_cache          0      0    128   30    1 : tunables  120   60    0 : slabdata      0      0      0
RAWv6                  5      6    640    6    1 : tunables   54   27    0 : slabdata      1      1      0
UDPLITEv6              0      0    640    6    1 : tunables   54   27    0 : slabdata      0      0      0
UDPv6                  6      6    640    6    1 : tunables   54   27    0 : slabdata      1      1      0
tw_sock_TCPv6          0      0    160   24    1 : tunables  120   60    0 : slabdata      0      0      0
request_sock_TCPv6      0      0    128   30    1 : tunables  120   60    0 : slabdata      0      0      0
TCPv6                  5      6   1216    3    1 : tunables   24   12    0 : slabdata      2      2      0
nf_conntrack_expect      0      0    136   29    1 : tunables  120   60    0 : slabdata      0      0      0
nf_conntrack_c04fa208      9     19    208   19    1 : tunables  120   60    0 : slabdata      1      1      0
flow_cache             0      0     80   48    1 : tunables  120   60    0 : slabdata      0      0      0
ubi_wl_entry_slab    256    290     24  145    1 : tunables  120   60    0 : slabdata      2      2      0
cfq_io_context         0      0     64   59    1 : tunables  120   60    0 : slabdata      0      0      0
cfq_queue              0      0    160   24    1 : tunables  120   60    0 : slabdata      0      0      0
mqueue_inode_cache     16     16    480    8    1 : tunables   54   27    0 : slabdata      2      2      0
cifs_small_rq         30     36    448    9    1 : tunables   54   27    0 : slabdata      4      4      0
cifs_request           4      4  16480    1    8 : tunables    8    4    0 : slabdata      4      4      0
cifs_mpx_ids           3     59     64   59    1 : tunables  120   60    0 : slabdata      1      1      0
cifs_inode_cache       0      0    352   11    1 : tunables   54   27    0 : slabdata      0      0      0
nfs_direct_cache       0      0     72   53    1 : tunables  120   60    0 : slabdata      0      0      0
nfs_write_data        72     72    448    9    1 : tunables   54   27    0 : slabdata      8      8      0
nfs_read_data         34     36    416    9    1 : tunables   54   27    0 : slabdata      4      4      0
nfs_inode_cache       13     14    544    7    1 : tunables   54   27    0 : slabdata      2      2      0
nfs_page              35     59     64   59    1 : tunables  120   60    0 : slabdata      1      1      0
fat_inode_cache        0      0    352   11    1 : tunables   54   27    0 : slabdata      0      0      0
fat_cache              0      0     24  145    1 : tunables  120   60    0 : slabdata      0      0      0
ext2_inode_cache     333    333    416    9    1 : tunables   54   27    0 : slabdata     37     37      0
configfs_dir_cache      1     67     56   67    1 : tunables  120   60    0 : slabdata      1      1      0
kioctx                 0      0    192   20    1 : tunables  120   60    0 : slabdata      0      0      0
arp_cache             11     24    160   24    1 : tunables  120   60    0 : slabdata      1      1      0
RAW                    5      8    480    8    1 : tunables   54   27    0 : slabdata      1      1      0
UDP                    8      8    480    8    1 : tunables   54   27    0 : slabdata      1      1      0
tw_sock_TCP           10     30    128   30    1 : tunables  120   60    0 : slabdata      1      1      0
request_sock_TCP       0      0     96   40    1 : tunables  120   60    0 : slabdata      0      0      0
TCP                   14     14   1088    7    2 : tunables   24   12    0 : slabdata      2      2      0
sock_inode_cache      66     66    352   11    1 : tunables   54   27    0 : slabdata      6      6      0
skbuff_fclone_cache     10     10    384   10    1 : tunables   54   27    0 : slabdata      1      1      0
skbuff_head_cache    120    120    192   20    1 : tunables  120   60    0 : slabdata      6      6      0
file_lock_cache        8     40     96   40    1 : tunables  120   60    0 : slabdata      1      1      0
shmem_inode_cache    719    730    392   10    1 : tunables   54   27    0 : slabdata     73     73      0
proc_inode_cache      84     84    320   12    1 : tunables   54   27    0 : slabdata      7      7      0
anon_vma_chain       555    580     24  145    1 : tunables  120   60    0 : slabdata      4      4      0
anon_vma             360    406     16  203    1 : tunables  120   60    0 : slabdata      2      2      0
pid                  177    177     64   59    1 : tunables  120   60    0 : slabdata      3      3      0
radix_tree_node      598    611    296   13    1 : tunables   54   27    0 : slabdata     47     47      0
idr_layer_cache      260    260    152   26    1 : tunables  120   60    0 : slabdata     10     10      0
size-4194304           0      0 4194304    1 1024 : tunables    1    1    0 : slabdata      0      0      0
size-2097152           0      0 2097152    1  512 : tunables    1    1    0 : slabdata      0      0      0
size-1048576           3      3 1048576    1  256 : tunables    1    1    0 : slabdata      3      3      0
size-524288            2      2 524288    1  128 : tunables    1    1    0 : slabdata      2      2      0
size-262144            0      0 262144    1   64 : tunables    1    1    0 : slabdata      0      0      0
size-131072            2      2 131072    1   32 : tunables    8    4    0 : slabdata      2      2      0
size-65536             2      2  65536    1   16 : tunables    8    4    0 : slabdata      2      2      0
size-32768            17     17  32768    1    8 : tunables    8    4    0 : slabdata     17     17      0
size-16384            16     16  16384    1    4 : tunables    8    4    0 : slabdata     16     16      0
size-8192              5      5   8192    1    2 : tunables    8    4    0 : slabdata      5      5      0
size-4096             25     25   4096    1    1 : tunables   24   12    0 : slabdata     25     25      0
size-2048            144    168   2048    2    1 : tunables   24   12    0 : slabdata     78     84      0
size-1024            100    100   1024    4    1 : tunables   54   27    0 : slabdata     25     25      0
size-512             304    304    512    8    1 : tunables   54   27    0 : slabdata     38     38      0
size-256             240    345    256   15    1 : tunables  120   60    0 : slabdata     21     23      0
size-192             300    300    192   20    1 : tunables  120   60    0 : slabdata     15     15      0
size-128             450    450    128   30    1 : tunables  120   60    0 : slabdata     15     15      0
size-96              748    760     96   40    1 : tunables  120   60    0 : slabdata     19     19      0
size-64             1062   1062     64   59    1 : tunables  120   60    0 : slabdata     18     18      0
size-32             3390   3390     32  113    1 : tunables  120   60    0 : slabdata     30     30      0
kmem_cache         124    160     96   40    1 : tunables  120   60    0 : slabdata      4      4      0

slab关系的内存分配函数如下:

l  kmalloc/__kmalloc/kmalloc_node,其申请的内存来源于/proc/slabinfo输出的第一列以size开始的slab类型。

l  kmem_cache_alloc/kmem_cache_alloc_node特定类型的有名称的内存缓存类型,/proc/slabinfo输出的第一列左上部,如skbuff_head_cache是特定于网卡接收数据包头cache,其就是取自于slab分配器。

图4.4.1 linux内核内存管理关系图

4.4.1  slab的实现

图 4.4.2 slab管理结构图

/proc/slabinfo输出的第一列的每种类型的slab都对应一个kmem_cache,每个slab包含2^gfporder个连续的物理内存页,缺省情况下,最多1024个物理页框构成。每个NUMAnode都有三个slab链表,full对应于所有对象均被分配出去,无空闲对象;free链表正好是full的对立面,而partial介于二者之间。

slab缓存由kmem_cache定义,其定义位置由slab.c文件移到了slab_def.h。内核对这一变动的解释是:如果在编译期间kmalloc的调用被赋予了确定的size,那么在编译时就可以通过选择合适的通用cache来对其进行优化。

struct kmem_cache {
/* 1) cache可调参数。被cache_chain_mutex保护*/
	unsigned int batchcount;//当per-CPU缓存列表为空时,从slab获取对象数目,或者从cache_grow一次分配对象数。
	unsigned int limit;//per-CPU 数组中保存的最大对象数
	unsigned int shared;//是否存在共享CPU的高速缓存

	unsigned int size;//slab管理对象的大小(包括padding)
	u32 reciprocal_buffer_size;//buffer_size的倒数,使用乘法代替除法,Newton-Raphson方法。
/* 2) alloc & free 将要会修改的字段*/

	unsigned int flags;		/* constant flags */
	unsigned int num;		/* 每个slab具有的对象数*/

/* 3) cache动态增加或者减少*/
	/* order of pgs per slab (2^n) */
	unsigned int gfporder;

	/* force GFP flags, e.g. GFP_DMA */
	gfp_t allocflags;

	size_t colour;			/* cache colouring range */
	unsigned int colour_off;	/* 着色偏移,不同的颜色代表不同的偏移,不同的偏移意味着映射不到同一个缓存行 */
	struct kmem_cache *slabp_cache;
	unsigned int slab_size;

	/* 构造函数,面向对象中的一种程序设计方法*/
	void (*ctor)(void *obj);

/* 4) cache创建和销毁*/
	const char *name;
	struct list_head list;
	int refcount;
	int object_size;
	int align;
	struct kmem_cache_node **node;
//5)早先的版本,这一项只有NR_CPUS大小,并且在kmem_cache的最开始,
	struct array_cache *array[NR_CPUS + MAX_NUMNODES];
};

描述slab对象的结构体是structslab,其定义如下:

struct slab {
	union {
		struct {
			struct list_head list;//链入slab_full、slab_partial、slab_free之一的链表中。
			unsigned long colouroff;//着色偏移,见图4.2
			void *s_mem;		/* 指向slab地址 */
			unsigned int inuse;	/* num of objs active in slab */
//kmem_bufctl_t数组中第一个空闲object在kmem_bufctl_t数组中的索引号,
			kmem_bufctl_t free; 
			unsigned short nodeid;
		};
		struct slab_rcu __slab_cover_slab_rcu;
	};
};

图4.2中还有一个比较重要的就是per-CPUslab缓存,释放的slab对象以及新分配的slab对象会优先从该表中分配或退还到该表中,如果此列表中没有可用的对象,则从slab链表一次取batchcount个对象。

struct array_cache {
	unsigned int avail;//列表中当前可用的对象数目
	unsigned int limit;//列表中最大的对象数目,通kmem_cache->limit。
	unsigned int batchcount;//通kmem_cache->batchcount
	unsigned int touched;
	spinlock_t lock;
	void *entry[];	//存放被释放的slab对象的数组,放在末尾好对齐
};

4.4.2  slab创建

Slab的创建函数定义如下,这个版本的slab融入了memory cgroup技术,cgroup和namespace的组合得到了轻量级虚拟化技术linux container (LXC),不过这里memory cgroup就不再这里叙述了,其使用可以参考《精通linux内核必会的75个绝技》的第12个绝技<使用Memory Cgroup限制内存使用量>:

mm/slab_comm.c
struct kmem_cache *
kmem_cache_create(const char *name, size_t size, size_t align,
		  unsigned long flags, void (*ctor)(void *))
{
	return kmem_cache_create_memcg(NULL, name, size, align, flags, ctor, NULL);
}

kmem_cache_create_memcg函数参数的意义如下:

name:/proc/slabinfo显示的slab名称字符串

size:该cache要创建的对象大小

align:对象对其

flag: SLAB标志

ctor:对象的构造函数,源于面向对象技术。

返回指向该slab的指针,如果失败返回NULL,中断环境不能调用该函数,但是该函数可以被中断,ctor是当该slab被创建时要调用的函数。

Flag的标志如下:

SLAB_POSION:使用(a5a5a5a5)对slab进行填充,这样在分配slab时,如果上述填充字段发生改变,则可以知道发了非法访问。

SLAB_RED_ZONE:在slab对象的开始和结束处增加额外的区域,这样检查slab使用时缓存溢出问题。

SLAB_HWCACHE_ALIGN:将该cache对象和硬件cache行对齐。

mm/slab.c
struct kmem_cache *
kmem_cache_create_memcg(struct mem_cgroup *memcg, const char *name, size_t size,
			size_t align, unsigned long flags, void (*ctor)(void *),
			struct kmem_cache *parent_cache)
{
	struct kmem_cache *s = NULL;
	int err = 0;

	mutex_lock(&slab_mutex);
//下面一行实际上等价于kmem_cache_alloc(k, flags | __GFP_ZERO),,成功则获得kmem_cache对象,这个函数见后文
	s = kmem_cache_zalloc(kmem_cache, GFP_KERNEL);
	if (s) {
		s->object_size = s->size = size;
		s->align = calculate_alignment(flags, align, size);
		s->ctor = ctor;
//kmem_cache名称字符串赋值
		s->name = kstrdup(name, GFP_KERNEL);

		err = __kmem_cache_create(s, flags);//创建一个slab cache
		s->refcount = 1;//增加引用计数。
//见图4.2,所有slab链接到了slab_caches链表上统一管理
		list_add(&s->list, &slab_caches);
	} else//出错的返回值
		err = -ENOMEM;

out_locked:
	mutex_unlock(&slab_mutex);
	put_online_cpus();

	return s;
}

由上面函数可以看出主要的工作放在了kmem_cache_alloc和__kmem_cache_create这两个函数。kmem_cache_alloc实际上是对slab_alloc的封装,直接来看slab_alloc好了。

mm/slab.c
static __always_inline void *
slab_alloc(struct kmem_cache *cachep, gfp_t flags, unsigned long caller)
{
	unsigned long save_flags;
	void *objp;

	flags &= gfp_allowed_mask;
	local_irq_save(save_flags);
	objp = __do_cache_alloc(cachep, flags);//执行slab分配。
	local_irq_restore(save_flags);
//清零操作。
	if (unlikely((flags & __GFP_ZERO) && objp))
		memset(objp, 0, cachep->object_size);

	return objp;
}

对应NUMA和UMA两种情况,____cache_alloc的定义处理是不一样的,这里简化只看UMA的情况。

mm/slab.c
static inline void *____cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
	void *objp;
	struct array_cache *ac;
	bool force_refill = false;

// cpu_cache_get 在UMA情况下退化为cachep->array[smp_processor_id()],见图4.2。
	ac = cpu_cache_get(cachep);
	if (likely(ac->avail)) {//很可能该slab是有空余的(即avail>=1)
		ac->touched = 1;//存取过了,跟新标志
//这里要获得obj对象。实际上只要这么一句就可以从slab中获取对象了。objp = ac->entry[--ac->avail];
		objp = ac_get_obj(cachep, ac, flags, false); 
		if (objp) {//成功获得,则更新cache分配命中计数直接返回
			STATS_INC_ALLOCHIT(cachep);
			goto out;
		}
		force_refill = true;
	}

	STATS_INC_ALLOCMISS(cachep);//更新cache分配没有命中统计信息
//从原slab中分配batchcount个到per-CPUcache上
	objp = cache_alloc_refill(cachep, flags, force_refill);	/*
///
	ac = cpu_cache_get(cachep);

out:
	if (objp)
		kmemleak_erase(&ac->entry[ac->avail]);
	return objp;
}

在per-CPU缓存中没有cache的对象时,将由cache_alloc_refill对per-CPU缓存进行充填。

mm/slab.c
static void *cache_alloc_refill(struct kmem_cache *cachep, gfp_t flags,
							bool force_refill)
{
…
while (batchcount > 0) {
//选择获取对象slab的链表(首先是slabs_partial,然后是slabs_free)
..
		slabp = list_entry(entry, struct slab, list);
		check_slabp(cachep, slabp);
		check_spinlock_acquired(cachep);

		while (slabp->inuse < cachep->num && batchcount--) {
//下面是对ac->entry[ac->avail++] = objp;的封装,获取对象的指针
			ac_put_obj(cachep, ac, slab_get_obj(cachep, slabp,
									node));
		}
		check_slabp(cachep, slabp);

		/*将slab移动到正确的slab链表上*/
		list_del(&slabp->list);
		if (slabp->free == BUFCTL_END)
			list_add(&slabp->list, &n->slabs_full);
		else
			list_add(&slabp->list, &n->slabs_partial);
	}
}

到这里能获得slab对象(obj)了,不过这一过程还没结束,前面还遗留了slab创建函数__kmem_cache_create。

mm/slab.c
int __kmem_cache_create (struct kmem_cache *cachep, unsigned long flags)
{
	size_t left_over, slab_size, ralign;
	gfp_t gfp;
	int err;
	size_t size = cachep->size;
/*将slab的对象大小按照字方式对其/
	if (size & (BYTES_PER_WORD - 1)) {
		size += (BYTES_PER_WORD - 1);
		size &= ~(BYTES_PER_WORD - 1);
	}
/*危险区和用户存储区需要按字或者更大的方式进行对齐,但是可以被体系结构或者用户强制设置(设置值大于字长度)更改。*/
if (flags & SLAB_STORE_USER)
		ralign = BYTES_PER_WORD;
	if (flags & SLAB_RED_ZONE) {
		ralign = REDZONE_ALIGN;
		size += REDZONE_ALIGN - 1;
		size &= ~(REDZONE_ALIGN - 1);
	}

	/* 3) 调用者强制设置对齐长度 */
	if (ralign < cachep->align) {
		ralign = cachep->align;
//存储对齐值。
cachep->align = ralign;
//下面这行对对cachep->node = (struct kmem_cache_node **)&cachep->array[nr_cpu_ids]的封装,其取CPU后第一个数组指向的成员作为kmem_cache指向的节点。见图4.2。
setup_node_pointer(cachep);
/*如果对象的长度大于页帧的1/8,则将头部管理数据存储在slab之外(即CFLGS_OFF_SLAB),否则存储在slab上,启动阶段并不处理这种情况。*/
		if ((size >= (PAGE_SIZE >> 3)) && !slab_early_init &&
	   	 	!(flags & SLAB_NOLEAKTRACE))
			flags |= CFLGS_OFF_SLAB;
//slab的大小,安装前面要求的方式进行对其。
size = ALIGN(size, cachep->align);
//迭代查找slab的最佳长度值(页的阶),同时也计算每个slab的对象数。其过程见后文
	left_over = calculate_slab_order(cachep, size, cachep->align, flags);
//经过上述函数之后,cachep->num是slab缓存的对象个数,这里将slab的大小按前面计算的对其值对其
	slab_size = ALIGN(cachep->num * sizeof(kmem_bufctl_t)
			  + sizeof(struct slab), cachep->align);

	/*
	 如果设置了CFLGS_OFF_SLAB标志,但是剩余的空闲大于slab大小,则需要将slab控制结构和slab对象放在一起。
	 */
	if (flags & CFLGS_OFF_SLAB && left_over >= slab_size) {
		flags &= ~CFLGS_OFF_SLAB;
		left_over -= slab_size;
	}

	if (flags & CFLGS_OFF_SLAB) {
	/*如果slab控制结构和slab对象确实需要分开存放,则不需要手动设置对其*/
		slab_size =
		    cachep->num * sizeof(kmem_bufctl_t) + sizeof(struct slab);
	}
/*下面这行的意义是将缓存行的大小最为着色偏移,由于slab在内存页的布局非常相似,这就意味着同一种类型的slab对象很可能被映射到同一个缓存行中,这样存在的多个slab对象间因为频繁的刷入缓存和刷出缓存而导致性能降低,通过着色的方式,修改同类型slab对象的页内偏移,可以使它们在被放到缓存中时位于不同的缓存行,这样带来的好处是不需要为映射新的slab对象而刷出先前存放的slab对象。不刷新缓存的话,TLB失效带来的开销也会降低*/
	cachep->colour_off = cache_line_size();
	/* 着色偏移必须是对齐的倍数*/
	if (cachep->colour_off < cachep->align)
		cachep->colour_off = cachep->align;
	cachep->colour = left_over / cachep->colour_off;//这里计算颜色值
	cachep->slab_size = slab_size;//slab对象大小
	cachep->flags = flags;
	cachep->allocflags = 0;
	cachep->size = size;
	cachep->reciprocal_buffer_size = reciprocal_value(size);//获得倒数,以乘法替代除法
//slab对象和slab控制结构不放在一起的slab分配方式。
	if (flags & CFLGS_OFF_SLAB) {
//从kmalloc_caches数组中获取一个slab控制结构
		cachep->slabp_cache = kmalloc_slab(slab_size, 0u);
	}
//c初始化上述申请的slab控制结构
	err = setup_cpu_cache(cachep, gfp);
	return 0;
}

calculate_slab_order的参数意义如下:

cachep将要创建的kmem_cache对象, size:slab对象的大小, align:slab对象要求的对其值,flags:slab分配标志。

mm/slab.c
static size_t calculate_slab_order(struct kmem_cache *cachep,
			size_t size, size_t align, unsigned long flags)
{
	unsigned long offslab_limit;
	size_t left_over = 0;
	int gfporder;

	for (gfporder = 0; gfporder <= KMALLOC_MAX_ORDER; gfporder++) {
		unsigned int num;
		size_t remainder;
//查找最优的过程是在cache_estimate中完成的,num是计算得到的slab对象数,是0则放不下一个slab对象。
		cache_estimate(gfporder, size, align, flags, &remainder, &num);
		if (!num)
			continue;
//slab头和slab对象分开存放时,slab的大小需要减去slab头长度,
		if (flags & CFLGS_OFF_SLAB) {
			offslab_limit = size - sizeof(struct slab);
			offslab_limit /= sizeof(kmem_bufctl_t);
//如果由cache_estimate计算得到的slab对象数大于slab能存放的最大对象数,则返回,不再尝试更大的order。
 			if (num > offslab_limit)
				break;
		}

		/* 保存合适的slab缓存块的相关信息*/
		cachep->num = num;
		cachep->gfporder = gfporder;
		left_over = remainder;

		/*
		 * SLAB_RECLAIM_ACCOUNT表示slab所占用的页面时可回收的,当内核检测是否有足够的页面满足用户态的需求时,此类页面被计算在内。由于可回收性而不需要做碎片检测		 */
		if (flags & SLAB_RECLAIM_ACCOUNT)
			break;

		/*申请slab对象的数据较大是好的,但是太大的slab对于gfp()s之类的函数并不友好,所以这里限制了申请的最高阶为slab_max_order,该值目前设置值是0,以后很可能因为申请高阶页内存变得方便,该值而有所提升。
		 */
		if (gfporder >= slab_max_order)
			break;

		/*
		 * slab所占的内部未用空间小于申请空间1/8,则可接受
		 */
		if (left_over * 8 <= (PAGE_SIZE << gfporder))
			break;
	}
	return left_over;
}

4.4.3 slab对象释放

Slab对象的释放由kmem_cache_free完成,该函数实际上是对__cache_free的封装,释放时分为两种情况,一种是释放的slab对象给per-CPU缓存对象,另一种情况是返回给slab分配器(返回的量是array_cache->batchcount个),如果返回给slab分配器则需要将最不可能未来要被使用到的对象返回给slab分配器,而cache_flusharray正是对slab对象进行排序,将要被返回和slab分配器的slab对象存放在数组的首部,将首部对象(batchcount个)返回,后面的对象向前移。


图4.4.3kmem_cache_free代码流程

mm/slab.c
static inline void __cache_free(struct kmem_cache *cachep, void *objp,
				unsigned long caller)
{
	struct array_cache *ac = cpu_cache_get(cachep);

	if (likely(ac->avail < ac->limit)) {
		STATS_INC_FREEHIT(cachep);//per-CPU数组中有空闲可用,则简单更新计数
	} else {
//per-CPU数组中无空闲可用,则刷新miss计数,同时按先进先出原理排序per-CPU数组
		STATS_INC_FREEMISS(cachep); 
//
		cache_flusharray(cachep, ac);
	}
//下面的函数可以看成是对ac->entry[ac->avail++] = objp;的封装。
	ac_put_obj(cachep, ac, objp);//具体的释放函数。
}


static void cache_flusharray(struct kmem_cache *cachep, struct array_cache *ac)
{
	int batchcount;
	struct kmem_cache_node *n;
	int node = numa_mem_id();

	batchcount = ac->batchcount;
	n = cachep->node[node];
	spin_lock(&n->list_lock);
//判断node share的slab空闲大小,这样可以将属于cpu的slab扔给node share的slab来管理,而不需要实际的回退给kmem_cache
	if (n->shared) {
		struct array_cache *shared_array = n->shared;
		int max = shared_array->limit - shared_array->avail;
		if (max) {
			if (batchcount > max)
				batchcount = max;
//由share的数组来接管per-CPU数组
			memcpy(&(shared_array->entry[shared_array->avail]),
			       ac->entry, sizeof(void *) * batchcount);
//上移avail,间接实现了先进先出
			shared_array->avail += batchcount;
			goto free_done;
		}
	}
//如果到这里说明share数组也没法接管释放的slab,则需要将cpu管理的
	free_block(cachep, ac->entry, batchcount, node);
free_done:
	spin_unlock(&n->list_lock);
	ac->avail -= batchcount;
//将cpu上的slab对象向前移动batchcount个。
	memmove(ac->entry, &(ac->entry[batchcount]), sizeof(void *)*ac->avail);
}



static void free_block(struct kmem_cache *cachep, void **objpp, int nr_objects,
		       int node)
{
	int i;
	struct kmem_cache_node *n;
//遍历objpp中的所有slab对象
	for (i = 0; i < nr_objects; i++) {
		void *objp;
		struct slab *slabp;
		objp = objpp[i];
//将虚拟地址转换成指向struct slab对象的指针,struct page的lru.prev就指向slab。
		slabp = virt_to_slab(objp);
		n = cachep->node[node];//取指定的node。
		list_del(&slabp->list);//将该slab从原链表删除。
//修正slab缓存的bufferctl数组。
		slab_put_obj(cachep, slabp, objp, node);
		n->free_objects++;

		/*修正slab链表*/
		if (slabp->inuse == 0) {
//如果空闲对象数大于大于空闲对象数的界限,则需要进行slab回收。
			if (n->free_objects > n->free_limit) {
//释放slab管理结构体空间以及所有slab对象占用的空间。
				n->free_objects -= cachep->num;
				slab_destroy(cachep, slabp);
			} else {
				list_add(&slabp->list, &n->slabs_free);
			}
		} else {
			/* 无条件将一个slab添加到partial链表的末尾
			 */
			list_add_tail(&slabp->list, &n->slabs_partial);
		}
	}
}

 4.4.4 kmalloc

Kmalloc属于通用的slab缓存申请,是没有名称的,但是和有名称的slab申请过程的差异不大,且主要调用的函数的功能和代码已经在前面有分析了,这里只粘贴其封装的__do_kmalloc函数了。

static __always_inline void *__do_kmalloc(size_t size, gfp_t flags,
					  unsigned long caller)
{
	struct kmem_cache *cachep;
	void *ret;

	cachep = kmalloc_slab(size, flags);
	if (unlikely(ZERO_OR_NULL_PTR(cachep)))
		return cachep;
	ret = slab_alloc(cachep, flags, caller);

	return ret;
}
  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shichaog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值