内存管理(十):伙伴分配器


linux版本:4.14.74
硬件:ARMV8 A53

1、伙伴分配器概述

内核初始化完毕后,使用页分配管理器管理物理页,当前使用的页分配器是伙伴分配器。
页分配管理自然而然,分配的单位是页。并且伙伴分配器只能分配2的n次幂个页,n称为阶(order),2的n次幂个连续页称为n阶页块。满足以下条件的两个n阶页块称为伙伴(buddy)

  • 两个页块的物理地址是连续的
  • 页块的第一页的物理页号必须是2的n次幂的整数倍,如0号页,2号页,4号页,8号页…
  • 如果合并成(n+1)阶页块,第一页的物理页号必须是2的(n+1)次幂的整数倍。如两个2阶页块合并,第一页应是0,4,8,12…页.

2 伙伴分配器的分配和释放过程

2.1 分配过程

分配n阶页块

  1. 查看是否有空间的n阶页块,如果有,直接分配即可
  2. 没有空闲的n阶页块,查看是否有(n+1)阶的页块,如果有,把(n+1)阶页块分裂为两个n阶页块,一个插入n阶页块链表,另一个分配出去。
  3. 没有空闲的n+1阶页块,继续查找更大的n+2阶页块,并将其分裂为n+1阶页块,然后执行上一步,将其中一个n+1阶页块分裂为两个n阶页块。

2.2 释放过程

释放n阶页块,查看它的伙伴是否空闲,如果伙伴不空闲,那么把n阶页块插入到空闲的n阶页块链表;如果伙伴空闲,那么合并为n+1阶页块,接下来释放n+1阶页块。

3 与伙伴分配器相关的数据

struct zone {
...
		struct free_area	free_area[MAX_ORDER];
...
}

在区域定义struct zone中,free_area元素表示是0-MAX_ORDER阶的链表,MAX_ORDER表示的最大阶数,默认值为11
free_area是一个链表结构体

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

MIGRATE_TYPES表示的是可移动类型,内核根据可移动性把物理页分为三种类型

  • 不可移动页:位置必须固定,不能移动,直接映射到内核虚拟地址空间的属于这一类。
  • 可移动页:使用页表映射的页,可移动到其他的位置,然后重新修改页表映射。
  • 可回收页:不能移动,但可以回收,需要数据的时候可以重新从数据源获取。

内核定义了以下迁移类型

enum migratetype {
	MIGRATE_UNMOVABLE,
	MIGRATE_MOVABLE,
	MIGRATE_RECLAIMABLE,
	MIGRATE_PCPTYPES,	/* the number of types on the pcp lists */
	MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,
#ifdef CONFIG_CMA
	MIGRATE_CMA,
#endif
#ifdef CONFIG_MEMORY_ISOLATION
	MIGRATE_ISOLATE,	/* can't allocate from here */
#endif
	MIGRATE_TYPES
};

我们先只关注前三种,后面的先暂时不管。
用下面的一幅图可以来描述free_area
在这里插入图片描述
区域水线

struct zone {
	...
	unsigned long watermark[NR_WMARK];
	...

}
enum zone_watermarks {
	WMARK_MIN,
	WMARK_LOW,
	WMARK_HIGH,
	NR_WMARK
};

每个内存区域有3个水线

  • 高水线:如果内存区域的空闲页数大于高水线,说明该内存区域水线充足。
  • 低水线:空闲页数小数低水线,说明该内存区域的内存轻微不足
  • 最低水线:如果内存区域的空闲页数小于最低水线,说明该内存区域的内存严重不足。

4 核心函数

所有分配页的函数最终都会调用到函数*__alloc_pages_nodemask*


/*
 * This is the 'heart' of the zoned buddy allocator.
 */
struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order, int preferred_nid,
							nodemask_t *nodemask)
{
	struct page *page;
	unsigned int alloc_flags = ALLOC_WMARK_LOW;
	gfp_t alloc_mask; /* The gfp_t that was actually used for allocation */
	struct alloc_context ac = { };

	gfp_mask &= gfp_allowed_mask;
	alloc_mask = gfp_mask;
	if (!prepare_alloc_pages(gfp_mask, order, preferred_nid, nodemask, &ac, &alloc_mask, &alloc_flags))
		return NULL;

	finalise_ac(gfp_mask, order, &ac);

	/* First allocation attempt */
	page = get_page_from_freelist(alloc_mask, order, alloc_flags, &ac);
	if (likely(page))
		goto out;

	/*
	 * Apply scoped allocation constraints. This is mainly about GFP_NOFS
	 * resp. GFP_NOIO which has to be inherited for all allocation requests
	 * from a particular context which has been marked by
	 * memalloc_no{fs,io}_{save,restore}.
	 */
	alloc_mask = current_gfp_context(gfp_mask);
	ac.spread_dirty_pages = false;

	/*
	 * Restore the original nodemask if it was potentially replaced with
	 * &cpuset_current_mems_allowed to optimize the fast-path attempt.
	 */
	if (unlikely(ac.nodemask != nodemask))
		ac.nodemask = nodemask;

	page = __alloc_pages_slowpath(alloc_mask, order, &ac);

out:
	if (memcg_kmem_enabled() && (gfp_mask & __GFP_ACCOUNT) && page &&
	    unlikely(memcg_kmem_charge(page, gfp_mask, order) != 0)) {
		__free_pages(page, order);
		page = NULL;
	}

	trace_mm_page_alloc(page, order, alloc_mask, ac.migratetype);

	return page;
}

分配函数可以分为快速分配get_page_from_freelist和慢速分配*__alloc_pages_slowpath*。申请页时,第一次尝试使用低水线来分配,也就是快速分配,如果分配失败,尝试使用最低水线,也就是慢速分配。
快速分配


/*
 * get_page_from_freelist goes through the zonelist trying to allocate
 * a page.
 */
static struct page *
get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags,
						const struct alloc_context *ac)
{
	struct zoneref *z = ac->preferred_zoneref;
	struct zone *zone;
	struct pglist_data *last_pgdat_dirty_limit = NULL;

/******1********/
	for_next_zone_zonelist_nodemask(zone, z, ac->zonelist, ac->high_zoneidx,
								ac->nodemask) {
		struct page *page;
		unsigned long mark;
/**************2**************/
		if (cpusets_enabled() &&
			(alloc_flags & ALLOC_CPUSET) &&
			!__cpuset_zone_allowed(zone, gfp_mask))
				continue;
/**************3*************/
		if (ac->spread_dirty_pages) {
			if (last_pgdat_dirty_limit == zone->zone_pgdat)
				continue;

			if (!node_dirty_ok(zone->zone_pgdat)) {
				last_pgdat_dirty_limit = zone->zone_pgdat;
				continue;
			}
		}

	/**************4***********/
		mark = zone->watermark[alloc_flags & ALLOC_WMARK_MASK];
		if (!zone_watermark_fast(zone, order, mark,
				       ac_classzone_idx(ac), alloc_flags)) {
			int ret;

			/* Checked here to keep the fast path fast */
			BUILD_BUG_ON(ALLOC_NO_WATERMARKS < NR_WMARK);
			/*********5*********/
			if (alloc_flags & ALLOC_NO_WATERMARKS)
				goto try_this_zone;
			/**************6**************/
			if (node_reclaim_mode == 0 ||
			    !zone_allows_reclaim(ac->preferred_zoneref->zone, zone))
				continue;
			/************7************/
			ret = node_reclaim(zone->zone_pgdat, gfp_mask, order);
			switch (ret) {
			case NODE_RECLAIM_NOSCAN:
				/* did not scan */
				continue;
			case NODE_RECLAIM_FULL:
				/* scanned but unreclaimable */
				continue;
			default:
				/* did we reclaim enough */
				if (zone_watermark_ok(zone, order, mark,
						ac_classzone_idx(ac), alloc_flags))
					goto try_this_zone;

				continue;
			}
		}
/*****************8***********/
try_this_zone:
		page = rmqueue(ac->preferred_zoneref->zone, zone, order,
				gfp_mask, alloc_flags, ac->migratetype);
		if (page) {
			prep_new_page(page, order, gfp_mask, alloc_flags);

			/*
			 * If this is a high-order atomic allocation then check
			 * if the pageblock should be reserved for the future
			 */
			if (unlikely(order && (alloc_flags & ALLOC_HARDER)))
				reserve_highatomic_pageblock(page, zone, order);

			return page;
		}
	}

	return NULL;
}

  1. 扫描所有符合条件的zone
  2. 检查cpuset
  3. 检查内存节点中dirty page数量数量是否超出了限制
  4. 检查水线是否满足要求,满足则跳到 分配内存,不满足继续向下
  5. 不需要检查水线,那就直接跳到 分配内存
  6. 回收功能没有开启,那这个节点无法分配
  7. 回收,然后再根据函数返回值进行再次检查是否符合要求
  8. 开始分配内存

未完待续。。。

参考文献

  1. 《Linux内核深度解析》—余华兵

  2. https://www.cnblogs.com/LoyenWang/p/11626237.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值