内存管理六 伙伴系统管理内存

一、伙伴系统概序
1、伙伴算法的原理
  为了便于页面的维护,内核将多个页面组成内存块,每个内存块都有 2^order个页(page)。order相同的内存块被组织到一个空闲链表中。伙伴系统基于2的方幂来申请释放内存页。
  当申请内存页时,伙伴系统首先检查与申请大小相同的内存块链表中,检看是否有空闲页,如果有就将其分配出去,并将其从链表中删除,否则就检查上一级,即大小为申请大小的2倍的内存块空闲链表,如果该链表有空闲内存,就将其分配出去,同时将剩余的一部分(即未分配出去的一半)加入到下一级空闲链表中;如果这一级仍没有空闲内存;就检查它的上一级,依次类推,直到分配成功或者彻底失败,在成功时还要按照伙伴系统的要求,将未分配的内存块进行划分并加入到相应的空闲内存块链表。
  在释放内存页时,会检查其伙伴是否也是空闲的,如果是就将它和它的伙伴合并为更大的空闲内存块,该检查会递归进行,直到发现伙伴正在被使用或者已经合并成了最大的内存块。

2、伙伴系统相关的结构
  内核中每个节点node,分为不同的zone来管理内存,每一个zone中的内存,包含不同大小的内存块(2^order大小):
在这里插入图片描述
  不同大小的内存块被加入到不同的链表中,其组织结构如下:
在这里插入图片描述
  系统中的每个物理内存页(页帧)都对应一个struct page数据结构,每个节点都包含了多个zone,每个zone都有struct zone表示,其中保存了用于伙伴系统的数据结构。zone中的:

struct zone {
	unsigned long watermark[NR_WMARK];
		/* free areas of different sizes */
	struct free_area	free_area[MAX_ORDER]; //用于管理该zone的伙伴系统信息
}

伙伴系统将基于这些信息管理该zone的物理内存。该数组中每个数组项用于管理一个空闲内存页块链表,同一个链表中的内存页块的大小相同,并且大小为2^order。MAX_ORDER定义了支持的最大的内存页块大小(11)。
struct free_area的定义如下:

struct free_area {
	struct list_head	free_list[MIGRATE_TYPES];
	unsigned long		nr_free;
};

(1)free_list:是用于连接空闲页的链表. 页链表包含大小相同的连续内存区
(2)nr_free:nr_free表示内存页块的数目,对于0阶的表示以1页为单位计算,对于1阶的以2页为单位计算,n阶的以2的n次方为单位计算。
  zone->free_area[MAX_ORDER]数组中阶作为各个元素的索引, 用于指定对应链表中的连续内存区包含多少个页帧。
   a、数组中第0个元素的order为0, 它的free_list链表域指向具有包含区为单页(2^0=1)的内存页面链表
   b、数组中第1个元素的free_list域管理的内存区为两页(2^1=2)的连续页面
   c、第3个管理的内存区为4页, 依次类推.
   d、直到2^MAXORDER−1个页面大小的块(1024个页面,也就是4MB)。
可以用下图清晰的表明其之间的关系:
在这里插入图片描述
  伙伴系统和当前状态的信息可以在/proc/buddyinfo中获取(从左到右,order依次增加):
在这里插入图片描述

二、内存碎片
1、碎片概念:
  在系统长时间运行后,物理内存会出现很多碎片,如图左图所示,左图中,虽然还有很多内存页可供使用,但连续的页只有一页,对于用户程序来说可能不成问题,因为用户程序通过页表映射,看到都是连续的虚拟内存,但内核部分时候需要连续的物理内存,会导致系统在申请大块的内存的内存时无法申请到,相对于申请大块内存,右图的状态会表现的更好,因为存在大片连续的页可供分配:
在这里插入图片描述
2、避免内存碎片:
  在内核2.6.24开发期间,防止碎片的方法最终加入内核。内核根据页的可移动性将其划分为3种不同的类型:
   - 不可移动的页:在内存中有固定位置,不能移动。分配给核心内核的页大多是此种类型;
   - 可回收的页:不能移动,但是可以删除,其内容可以从某些源重新生成;
   - 可移动的页:可以随意移动,属于用户进程的页属于这种类型,因为它们是通过页表映射的;
   由于页无法移动, 导致在原本几乎全空的内存区中无法进行连续分配. 根据页的可移动性, 将其分配到不同的列表中, 即可防止这种情形。例如, 不可移动的页不能位于可移动内存区的中间, 否则就无法从该内存区分配较大的连续内存块。内核的另一种方法确实将内存分区, 分别用于可移动页和不可移动页的分配。与此思想相关的结构体类型如下:

enum {
	MIGRATE_UNMOVABLE,
	MIGRATE_MOVABLE,
	MIGRATE_RECLAIMABLE,
	MIGRATE_PCPTYPES,
	MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,
	MIGRATE_TYPES
};

对伙伴系统数据结构的主要调整, 是将空闲列表分解为MIGRATE_TYPE个列表,数据结构如下,每一个free_list中都对应有不同类型的页面:

struct free_area {
	struct list_head	free_list[MIGRATE_TYPES];
	unsigned long		nr_free;
};

在这里插入图片描述
3、pageblock相关:
  内存管理中有一个pageblock的概念,一个pageblock的大小是(MAX_ORDER-1)个页面,每个pageblock有一个MIGRATE_TYPES类型,zone的数据结构中有个pageblock_flags的指针,指向用于存放pageblock的MIGRATE_TYPES类型的内存空间,每个pageblock用4个比特位来存放MIGRATE_TYPES的类型:

struct zone {
	/*
	 * Flags for a pageblock_nr_pages block. See pageblock-flags.h.
	 * In SPARSEMEM, this map is stored in struct mem_section
	 */
	unsigned long		*pageblock_flags;
}

内核中有两个函数来管理这些迁移类型:set_pageblock_migratetype和get_pageblock_migratetype:
- set_pageblock_migratetype:用于设置一个以指定的页为起始地址的内存区的迁移类型;
- get_pageblock_migratetype:可用于从struct page中获取页的迁移类型。
内核初始化是所有页面都被标记位可迁移类型(MIGRATE_MOVABLE),在启动期间分配可移动内存区的情况较少, 那么分配器有很高的几率分配长度最大的内存区, 并将其从可移动列表转换到不可移动列表,那么在内核分配不可移动的内存区时,则必须”盗取”MIGRATE_MOVABLE类型的页面。

//./kernel-4.4/mm/page_alloc.c
void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone,
		unsigned long start_pfn, enum memmap_context context)
{
	z = &pgdat->node_zones[zone];
	for (pfn = start_pfn; pfn < end_pfn; pfn++) {
			if (!(pfn & (pageblock_nr_pages - 1))) {
			struct page *page = pfn_to_page(pfn);

			__init_single_page(page, pfn, zone, nid);
			set_pageblock_migratetype(page, MIGRATE_MOVABLE);
		} 
		......
}

三、伙伴系统分配内存
  内存中分配物理页面的接口函数是:alloc_pages(),是伙伴系统分配内存的核心函数,用于分配一个或者多个连续的物理页面,分配页面的大小只能是2^order个页面。alloc_page()函数的定义如下,包含分配掩码gfp_mask和分配阶数order的参数。

//kernel-4.4/include/linux/gfp.h
#define alloc_pages(gfp_mask, order) \
		alloc_pages_node(numa_node_id(), gfp_mask, order)

1、分配掩码gfp_mask介绍:
  gfp_mask是非常重要的参数,也定义与gfp.h问中,包含以下几种类型:
- 区域描叙符zone modifiers:指定总哪个zone中分配页面,内核优先从ZONE_NORMAL开始分配;
- 行为描叙符action modifiers:表示内核应该如何分配所需的内存,在某些特定的情况下,只能使用某些特定的方法分配
内存,例如,中断处理程序就要求内核在分配内存时不能睡眠(因为中断处理程序不能被重新调度);
- 类型描叙符:组合了行为区域描叙符和行为描叙符,将这些可能用到的组合归纳为不同类型;
(1)区域描叙符

#define __GFP_DMA	((__force gfp_t)___GFP_DMA)                             /*从ZONE_DMA中分配内存*/               
#define __GFP_HIGHMEM	((__force gfp_t)___GFP_HIGHMEM)				        /*从ZONE_HIGHMEM活ZONE_NORMAL中分配内存*/
#define __GFP_DMA32	((__force gfp_t)___GFP_DMA32)                           /*从ZONE_DMA32中分配内存*/
#define __GFP_MOVABLE	((__force gfp_t)___GFP_MOVABLE)                     /* 从__GFP_MOVABLE中分配内存 */
#define GFP_ZONEMASK    (__GFP_DMA|__GFP_HIGHMEM|__GFP_DMA32|__GFP_MOVABLE)

(2)行为描叙符:

#define __GFP_RECLAIMABLE ((__force gfp_t)___GFP_RECLAIMABLE)   		/* 页是可回收的 */
#define __GFP_HIGH	((__force gfp_t)___GFP_HIGH)                        /*如果请求非常重要, 则设置__GFP_HIGH,即内核急切地需要内存时。*/       
#define __GFP_IO	((__force gfp_t)___GFP_IO)               		    /*__GFP_IO说明在查找空闲内存期间内核可以进行I/O操作*/    
#define __GFP_FS	((__force gfp_t)___GFP_FS)						    /*分配器可以启动文件系统I/O*/
#define __GFP_COLD	((__force gfp_t)___GFP_COLD)						/*如果需要分配不在CPU高速缓存中的“冷”页时*/
#define __GFP_NOWARN	((__force gfp_t)___GFP_NOWARN)					/*在分配失败时禁止内核故障警告。在极少数场合该标志有用*/
#define __GFP_REPEAT	((__force gfp_t)___GFP_REPEAT)					/*在分配失败后自动重试,但在尝试若干次之后会停止*/
#define __GFP_NOFAIL	((__force gfp_t)___GFP_NOFAIL)					/*在分配失败后一直重试,直至成功*/
#define __GFP_NORETRY	((__force gfp_t)___GFP_NORETRY)					/*在分配失败后不重试,因此可能分配失败*/
#define __GFP_MEMALLOC	((__force gfp_t)___GFP_MEMALLOC)				/*使用紧急分配链表*/
#define __GFP_COMP	((__force gfp_t)___GFP_COMP)						/* 增加复合页元数据 */
#define __GFP_ZERO	((__force gfp_t)___GFP_ZERO)						/*在分配成功时,将返回填充字节0的页*/

(3)类型描叙符

#define __GFP_NOMEMALLOC ((__force gfp_t)___GFP_NOMEMALLOC)			/*不使用紧急分配链表*/
#define __GFP_HARDWALL   ((__force gfp_t)___GFP_HARDWALL)			/* 只能在当前进程可运行的cpu关联的内存节点上分配内存*/
#define __GFP_THISNODE	((__force gfp_t)___GFP_THISNODE)			/* 只能在当前节点上分配内存 */
#define __GFP_ATOMIC	((__force gfp_t)___GFP_ATOMIC)				/* 用于原子分配,在任何情况下都不能中断  */
#define __GFP_NOACCOUNT	((__force gfp_t)___GFP_NOACCOUNT)			
#define __GFP_NOTRACK	((__force gfp_t)___GFP_NOTRACK)			    /* 不对分配的内存进行跟踪 */
#define __GFP_DIRECT_RECLAIM	((__force gfp_t)___GFP_DIRECT_RECLAIM) /* Caller can reclaim */
#define __GFP_OTHER_NODE ((__force gfp_t)___GFP_OTHER_NODE)				 /* On behalf of other node *
#define __GFP_WRITE	((__force gfp_t)___GFP_WRITE)
#define __GFP_KSWAPD_RECLAIM	((__force gfp_t)___GFP_KSWAPD_RECLAIM) /* kswapd can wake */
#define __GFP_NOTRACK_FALSE_POSITIVE (__GFP_NOTRACK)
#define __GFP_RECLAIM ((__force gfp_t)(___GFP_DIRECT_RECLAIM|___GFP_KSWAPD_RECLAIM))  /*可以被回收*/

(4)掩码组:

#define GFP_ATOMIC	(__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)
		/*用于原子分配,在任何情况下都不能中断,这个标志用在中断处理程序, 下半部,持有自旋锁以及其他不能睡眠的地方*/
#define GFP_KERNEL	(__GFP_RECLAIM | __GFP_IO | __GFP_FS)
		/*常规分配方式,可能会阻塞,为了获取调用者所需的内存,内核会尽力而为,这个标志应该是首选标志*/
#define GFP_NOWAIT	(__GFP_KSWAPD_RECLAIM)
		/*与GFP_ATOMIC类似,不同之处在于,调用不会退给紧急内存池, 这就增加了内存分配失败的可能性*/
#define GFP_NOIO	(__GFP_RECLAIM)					/*这种分配可以阻塞, 但不会启动磁盘I/O */
#define GFP_NOFS	(__GFP_RECLAIM | __GFP_IO)		/*这种分配在必要时可以阻塞,可能启动磁盘,但是不会启动文件系统操作*/
#define GFP_TEMPORARY	(__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_RECLAIMABLE)
#define GFP_USER	(__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
		/*常规的分配方式, 可能会阻塞. 这个标志用于为用户空间进程分配内存时使用*/
#define GFP_DMA		__GFP_DMA						/*用于分配适用于DMA的内存*/
#define GFP_DMA32	__GFP_DMA32						/*是GFP_USER的一个扩展,用于用户空间它允许分配无法直接映射的高端内存*/
#define GFP_HIGHUSER	(GFP_USER | __GFP_HIGHMEM)
#define GFP_HIGHUSER_MOVABLE	(GFP_HIGHUSER | __GFP_MOVABLE)
#define GFP_TRANSHUGE	((GFP_HIGHUSER_MOVABLE | __GFP_COMP | \
			 __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN) & \
			 ~__GFP_KSWAPD_RECLAIM)

/* Convert GFP flags to their corresponding migrate type */
#define GFP_MOVABLE_MASK (__GFP_RECLAIMABLE|__GFP_MOVABLE)	/*分配将从虚拟内存域ZONE_MOVABLE进行*/

(5)应用:
  在编写的代码中,使用GFP_KERNEL,和GFP_ATOMIC比较多,当然各个类型标志也均有其应用场景:
  进程上下文, 可以睡眠:GFP_KERNEL
  进程上下文, 不可以睡眠:GFP_KERNEL,在睡眠之前或之后以GFP_KERNEL执行内存分配
  中断处理程序/软中断/tasklet:GFP_ATMOIC
  需要用于DMA的内存, 可以睡眠:GFP_DMA GFP_KERNEL
  需要用于DMA的内存, 不可以睡眠:GFP_DMA GFP_ATOMIC

2、alloc_pages介绍:
  buddy系统分配内存有很多接口,如:alloc_page、get_zeroed_page、__get_free_page、__get_dma_page最后都会调用到alloc_pages去分配内存,只是在alloc_pages做了相关的修饰。下面看alloc_pages的调用,__alloc_pages_nodemask是伙伴系统的心脏函数,如下:

//alloc_pages->alloc_pages_node->__alloc_pages_node->__alloc_pages
static inline struct page *
__alloc_pages(gfp_t gfp_mask, unsigned int order,
		struct zonelist *zonelist)
{
	return __alloc_pages_nodemask(gfp_mask, order, zonelist, NULL);
}

__alloc_pages_nodemask函数主要完成以下工作:
  (1)找到指定的分配管理区,指定分配的类型;
  (2)尝试开始分配内存:get_page_from_freelist,如果成功就返回分配到的页;
  (3)在各个区域都找不到可以满足分配的内存了,那么说明管理区的内存已经确实不够了,于是开始启用一条慢速的途径来分配,包括尝试去唤醒kswapd去回收一些不经常使用的页等等;

struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,
			struct zonelist *zonelist, nodemask_t *nodemask)
{
	/*根据gfp_mask确定分配页所处的管理区*/
	enum zone_type high_zoneidx = gfp_zone(gfp_mask);
	struct zone *preferred_zone;
	struct page *page;
	/*根据gfp_mask得到迁移类分配页的型*/
	int migratetype = allocflags_to_migratetype(gfp_mask);

	gfp_mask &= gfp_allowed_mask;
	lockdep_trace_alloc(gfp_mask);
	might_sleep_if(gfp_mask & __GFP_WAIT);
	if (should_fail_alloc_page(gfp_mask, order))
		return NULL;
 
	/*
	 * Check the zones suitable for the gfp_mask contain at least one
	 * valid zone. It's possible to have an empty zonelist as a result
	 * of GFP_THISNODE and a memoryless node
	 */
	if (unlikely(!zonelist->_zonerefs->zone))
		return NULL;
 
	/* The preferred zone is used for statistics later */
	/*从zonelist中找到zone_idx与high_zoneidx相同的管理区,也就是之前认定的管理区*/
	first_zones_zonelist(zonelist, high_zoneidx, nodemask, &preferred_zone);
	if (!preferred_zone)
		return NULL;
 
	/* First allocation attempt */
	page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, nodemask, order,
			zonelist, high_zoneidx, ALLOC_WMARK_LOW|ALLOC_CPUSET,
			preferred_zone, migratetype);
	if (unlikely(!page))
		/*第一次分配失败的话则会用通过一条低速路径来进行第二次分配,包括唤醒页换出守护进程等等*/
		page = __alloc_pages_slowpath(gfp_mask, order,
				zonelist, high_zoneidx, nodemask,
				preferred_zone, migratetype);
 
	trace_mm_page_alloc(page, order, gfp_mask, migratetype);
	return page;
}

get_page_from_freelist函数主要完成以下工作:
  (1)从指定的管理区开始按照zonelist中定义的顺序来遍历管理区;
  (2)如果该管理区的水位线正常,则调用buffered_rmqueue()在该管理区中分配;
  (3)如果管理区的水位线过低,则在NUMA架构下会申请页面回收;

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);
zonelist_scan:
	/*
	 * Scan zonelist, looking for a zone with enough free.
	 * See also cpuset_zone_allowed() comment in kernel/cpuset.c.
	 */
    /*从认定的管理区开始遍历,直到找到一个拥有足够空间的管理区,
	  例如,如果high_zoneidx对应的ZONE_HIGHMEM,则遍历顺序为HIGHMEM-->NORMAL-->DMA,
	  如果high_zoneidx对应ZONE_NORMAL,则遍历顺序为NORMAL-->DMA*/
	for_each_zone_zonelist_nodemask(zone, z, zonelist,
						high_zoneidx, nodemask) {
		if (NUMA_BUILD && zlc_active &&
			!zlc_zone_worth_trying(zonelist, z, allowednodes))
				continue;
 
		/*检查给定的内存域是否属于该进程允许运行的CPU*/
		if ((alloc_flags & ALLOC_CPUSET) &&
			!cpuset_zone_allowed_softwall(zone, gfp_mask))
				goto try_next_zone;
 
		BUILD_BUG_ON(ALLOC_NO_WATERMARKS < NR_WMARK);
		if (!(alloc_flags & ALLOC_NO_WATERMARKS)) {
			unsigned long mark;
			int ret;
			
            /*通过alloc_flags来确定是使用何种水印,pages_min?pages_low?pages_high?
			  选择了一种水印,就要求分配后的空闲不低于该水印才能进行分配*/
			mark = zone->watermark[alloc_flags & ALLOC_WMARK_MASK];
 
			/*如果管理区的水位线处于正常水平,则在该管理区进行分配*/
			if (zone_watermark_ok(zone, order, mark,
				    classzone_idx, alloc_flags))
				goto try_this_zone;
 
			if (zone_reclaim_mode == 0)
				goto this_zone_full;
 
			/*下面这部分都是针对NUMA架构的申请页面回收*/
			ret = zone_reclaim(zone, gfp_mask, order);
			switch (ret) {
			case ZONE_RECLAIM_NOSCAN:/*没有进行回收*/
				/* did not scan */
				goto try_next_zone;
			case ZONE_RECLAIM_FULL:  /*没有找到可回收的页面*/
				/* scanned but unreclaimable */
				goto this_zone_full;
			default:
				/* did we reclaim enough */
				if (!zone_watermark_ok(zone, order, mark,
						classzone_idx, alloc_flags))
					goto this_zone_full;
			}
		}
 
try_this_zone:/*分配2^order个页*/
		page = buffered_rmqueue(preferred_zone, zone, order,
						gfp_mask, migratetype);
		if (page)
			break;
this_zone_full:
		if (NUMA_BUILD)
			zlc_mark_zone_full(zonelist, z);
try_next_zone:
		if (NUMA_BUILD && !did_zlc_setup && nr_online_nodes > 1) {
			/*
			 * we do zlc_setup after the first zone is tried but only
			 * if there are multiple nodes make it worthwhile
			 */
			allowednodes = zlc_setup(zonelist, alloc_flags);
			zlc_active = 1;
			did_zlc_setup = 1;
		}
	}
 
	if (unlikely(NUMA_BUILD && page == NULL && zlc_active)) {
		/* Disable zlc cache for second zonelist scan */
		zlc_active = 0;
		goto zonelist_scan;
	}
	return page;
}
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);
	int cpu;
 
again:
	cpu  = get_cpu();
	if (likely(order == 0)) {/*order为0,即要求分配一个页*/
		struct per_cpu_pages *pcp;
		struct list_head *list;
 
		pcp = &zone_pcp(zone, cpu)->pcp;/*获取本地CPU对应的pcp*/
		list = &pcp->lists[migratetype];/*获取和迁移类型对应的链表*/
		local_irq_save(flags);
 
		/*如果链表为空,则表示没有可分配的页,需要从伙伴系统中分配2^batch个页给list*/
		if (list_empty(list)) {
			pcp->count += rmqueue_bulk(zone, 0,
					pcp->batch, list,
					migratetype, cold);
			if (unlikely(list_empty(list)))
				goto failed;
		}
 
		if (cold)/*如果是需要冷页,则从链表的尾部获取*/
			page = list_entry(list->prev, struct page, lru);
		else     /*如果是需要热页,则从链表的头部获取*/
			page = list_entry(list->next, struct page, lru);
        
		list_del(&page->lru);
		pcp->count--;
	} else {
		spin_lock_irqsave(&zone->lock, flags);
		/*从管理区的伙伴系统中选择合适的内存块进行分配*/
		page = __rmqueue(zone, order, migratetype);
		spin_unlock(&zone->lock);
		if (!page)
			goto failed;
		__mod_zone_page_state(zone, NR_FREE_PAGES, -(1 << order));
	}
 
	__count_zone_vm_events(PGALLOC, zone, 1 << order);
	zone_statistics(preferred_zone, zone);
	local_irq_restore(flags);
	put_cpu();
 
	VM_BUG_ON(bad_range(zone, page));
	if (prep_new_page(page, order, gfp_flags))
		goto again;
	return page;
}

__rmqueue-> __rmqueue_smallest ->expand,expand实现“切蛋糕”的功能,因为可能摘下来的内存比需要的内存大,需要把切完之后剩下的内存重新放到伙伴系统中:

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;/*order为high的页块对应的页框数*/
 
	/*申请的order为low,实际分配的块对应的order为high
	  如果high大于low则要将大块进行拆分,并且将拆分后的伙伴块添加到下一级order的块链表中去*/
	while (high > low) {
		area--;/*area减1得到下一级order对应的area*/
		high--;/*high减1表明进行了一次拆分*/
		size >>= 1;/*拆分一次size就要除以2*/
		VM_BUG_ON(bad_range(zone, &page[size]));
 
		/*通过size来定位拆分后的伙伴块的起始页框描述符,
		并将其作为第一个块添加到下一级order的块链表中*/
		list_add(&page[size].lru, &area->free_list[migratetype]);
		area->nr_free++;/*该order区域的块数加1*/
		set_page_order(&page[size], high);/*设定private域为high*/
	}
}

四、伙伴系统释放内存
  伙伴系统释放内存的接口为__free_pages,这些接口最终都会调用到__free_one_page来释放内存:

void __free_pages(struct page *page, unsigned int order)
{
	if (put_page_testzero(page)) {
		if (order == 0)
			free_hot_cold_page(page, false);//如果释放的是单页,则调用free_hot_cold_page
		else
			__free_pages_ok(page, order);
	}
}
/*
page:指向释放的页框块的首个page
zone:page所属zone区
order:当前page的order
页面回收是分配的逆过程,首先查找当前要释放的内存块同order是否能跟临近的伙伴合并,
如果能合并的话,再进一步查找order+1伙伴,确认能否合并为一个order+2的内存块,重复
这样的过程,直到不能继续合并;
如果不能合并的话,直接将要释放的pages加入到对应order的伙伴系统中;
注意查找伙伴系统的条件,比如某个order下四个内存区  A -- B -- C -- D 
AB,CD称之为伙伴,BC不能称为伙伴,因此查找伙伴index算法为
page_idx ^ (1 << order);
__find_buddy_index 函数查找对应的伙伴
page_is_buddy 判断两个内存块是否为伙伴,0 is not buddy, 1 is buddy
*/
static inline void __free_one_page(struct page *page,
		struct zone *zone, unsigned int order,
		int migratetype)
{
	//max_order=MAX_ORDER
	max_order = min_t(unsigned int, MAX_ORDER, pageblock_order + 1);
 
	VM_BUG_ON(!zone_is_initialized(zone));
 
	if (unlikely(PageCompound(page)))
		if (unlikely(destroy_compound_page(page, order)))
			return;
 
	VM_BUG_ON(migratetype == -1);
	if (likely(!is_migrate_isolate(migratetype)))
		__mod_zone_freepage_state(zone, 1 << order, migratetype);
		
	//计算page index
	page_idx = page_to_pfn(page) & ((1 << MAX_ORDER) - 1);
 
	VM_BUG_ON(page_idx & ((1 << order) - 1));
	VM_BUG_ON(bad_range(zone, page));
 
continue_merging:
	/* 
	首先从同order查找,能合并的话,进一步往高一阶order查找
	*/
	while (order < max_order - 1) {
		//查找当前要释放的page的伙伴idx,先从同阶找buddy
		buddy_idx = __find_buddy_index(page_idx, order);
		buddy = page + (buddy_idx - page_idx);
		if (!page_is_buddy(page, buddy, order))//page,buddy不能合并,跳转到done_merging
			goto done_merging;
		//当前order能合并,如果有开CONFIG_DEBUG_PAGEALLOC,进入if,否则进入else
		if (page_is_guard(buddy)) {
			clear_page_guard_flag(buddy);
			set_page_private(page, 0);
			__mod_zone_freepage_state(zone, 1 << order,
						  migratetype);
		} else {
		//先将找到的buddy从伙伴系统删除,
			list_del(&buddy->lru);
			zone->free_area[order].nr_free--;
			if (is_migrate_cma(migratetype))
				zone->free_area[order].nr_free_cma--;
			rmv_page_order(buddy);
		}
		//接着将buddy跟page进行合并,计算合并后的page index
		combined_idx = buddy_idx & page_idx;
		page = page + (combined_idx - page_idx);
		page_idx = combined_idx;
		order++;
		//最后order+1后重复这个过程
	}
	if (max_order < MAX_ORDER) {
		if (unlikely(has_isolate_pageblock(zone))) {
			int buddy_mt;
 
			buddy_idx = __find_buddy_index(page_idx, order);
			buddy = page + (buddy_idx - page_idx);
			buddy_mt = get_pageblock_migratetype(buddy);
 
			if (migratetype != buddy_mt
					&& (is_migrate_isolate(migratetype) ||
					is_migrate_isolate(buddy_mt)))
				goto done_merging;
		}
		max_order++;
		goto continue_merging;
	}
 
done_merging:
	//重新设定这块内存order
	set_page_order(page, order);
 
	 //page跟buddy能合并的情况下,检查能否跟更高一阶order合并
	if ((order < MAX_ORDER-2) && pfn_valid_within(page_to_pfn(buddy))) {
		struct page *higher_page, *higher_buddy;
		combined_idx = buddy_idx & page_idx;//合并后的首个page idx
		higher_page = page + (combined_idx - page_idx); //合并后的page
		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)) {
		//这个是将page,buddy加入order free_list尾部,这样跟order+1可以link起来
			list_add_tail(&page->lru,
				&zone->free_area[order].free_list[migratetype]);
			goto out;
		}
	}
	//将page插入到对应order free_list头部
	list_add(&page->lru, &zone->free_area[order].free_list[migratetype]);
out:
	zone->free_area[order].nr_free++;
	if (is_migrate_cma(migratetype))
		zone->free_area[order].nr_free_cma++;
}

作者:frank_zyp
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文无所谓版权,欢迎转载。

猜你喜欢

转载自blog.csdn.net/frank_zyp/article/details/82884112