Linux 伙伴系统

前言

本文从伙伴系统的页面分配函数 rmqueue() 开始,此函数作用是,从给定的zone中分配2^order的页面,在5.14.2中,rmqueue() 仅仅被 get_page_from_freelist() 调用,get_page_from_freelist() 更是重量级,由页面分配的核心函数 __alloc_pages() 所调用的。以上及下面所有讲到的函数的定义,均在mm/page_alloc.c 中定义。

而暂时仅仅着眼于伙伴系统的核心分配函数:rmqueue()

一、rmqueue

1.1 流程图

在这里插入图片描述

1.2 函数原型

static inline struct page *rmqueue(struct zone *preferred_zone,
                                   struct zone *zone, unsigned int order,
                                   gfp_t gfp_flags, unsigned int alloc_flags,
                                   int migratetype)

preferred_zone,是根据gfp_flags算出来的,是在核心函数__alloc_pages中确定的,表示本次页面分配的请求,最先考虑从那个zone中进行分配,但是这个参数在本函数中不起关键作用,因为函数rmqueue的作用是从指定的zone中分配page,而这个指定的zone是第二个参数,前面讲过,get_page_from_freelist会调用rmqueue,而get_page_from_freelist作用就是遍历所有可用的zone,而preferred_zone是遍历过程的第一个被选中的zone.

zone, rmqueue正是从改zone的free_list中,选择page进行分配

order,指分配多少阶的page,与伙伴系统息息相关

gfp_flags, alloc_flags 都是分配配置,讲到再说

migratetype,迁移类型

1.3 通过PCP分配

/// 代码中省略了CMA相关的部分 
 if (likely(pcp_allowed_order(order))) {
    page = rmqueue_pcplist(preferred_zone, zone, order, gfp_flags, migratetype,
                           alloc_flags);
    goto out;
  }

首先通过函数pcp_allowed_order检查是否需要通过PCP进行分配,所谓PCP即Per_CPU方式,即内核将变量缓存给每一个CPU上,不同的CPU都保留有自己的副本,这样不同的CPU可以并发的访问自己的这部分变量而无需上锁。

在之前的内核版本上,只有order==0,即分配1个页面是才会采用PCP方式进行分配,现在时代变了,当order<=3时都会使用PCP方式,pcp_allowed_order代码如下:

// 注意,以下代码我删除了关于透明大页的情况
static inline bool pcp_allowed_order(unsigned int order)
{
	if (order <= PAGE_ALLOC_COSTLY_ORDER) // PAGE_ALLOC_COSTLY_ORDER == 3
		return true;
	return false;
}

如果 order<=3 , 那么将调用rmqueue_pcplis,从pcplist中分配page,此函数我们将在下一小节展开.

1.4 大阶页面分配

/// 代码中省略了CMA相关的部分
if (alloc_flags & ALLOC_HARDER) {
  page = __rmqueue_smallest(zone, order, MIGRATE_HIGHATOMIC);
  if (page)
    trace_mm_page_alloc_zone_locked(page, order, migratetype);
}
if (!page)
  page = __rmqueue(zone, order, migratetype, alloc_flags);

ALLOC_HARDER,含义是尽力分配,因为为了保障其不会失败,将优先从MIGRATE_HIGHATOMIC中进行分配,MIGRATE_HIGHATOMIC是一种迁移类型,但是和迁移关系不大,你可以理解为专门为ALLOC_HARDER标志预留的一部分空间,如果失败,则调用__rmqueue函数走传统的路径进行分配。

问题在于,MIGRATE_HIGHATOMIC的page是怎么来的呢?因为在伙伴系统初始化的时候,会将所有的page定义为MIGRATE_MOVABLE,这是通过函数memmap_init_range实现的:

/**
 ...
 * All aligned pageblocks are initialized to the specified migratetype
 * (usually MIGRATE_MOVABLE). Besides setting the migratetype, no related
 * zone stats (e.g., nr_isolate_pageblock) are touched.
 */
void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone,
		unsigned long start_pfn, unsigned long zone_end_pfn,
		enum meminit_context context,
		struct vmem_altmap *altmap, int migratetype)
{

然后MIGRATE_UNMOVABLE,MIGRATE_RECLAIMABLE类型可以通过后备列表的方式从其他由page的list上“偷”page:

static int fallbacks[MIGRATE_TYPES][3] = {
	[MIGRATE_UNMOVABLE]   = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE,   MIGRATE_TYPES },
	[MIGRATE_MOVABLE]     = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_TYPES },
	[MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE,   MIGRATE_MOVABLE,   MIGRATE_TYPES },
};

但是显然不包括MIGRATE_HIGHATOMIC,其实此类型的page是通过函数reserve_highatomic_pageblock进行分配的,系统最多会给每个zone分配(zone_managed_pages(zone) / 100) + pageblock_nr_pages)个page,即zone管理的内存页的百分之一,并且保底是pageblock_nr_pages个,pageblock可以理解为一个4MB的页块,即对应伙伴系统的最大可分配页,page的迁移类型是按页块为单位进行修改的,修改页块迁移类型的函数是set_pageblock_migratetype。
那么reserve_highatomic_pageblock在什么时候被调用呢?是在get_page_from_freelist()函数之后,我在前言中讲过了。

因此逻辑是这样的,一旦有分配请求包含了ALLOC_HARDER,那么将会尝试从MIGRATE_HIGHATOMIC中申请,但是第一次肯定会失败,,于是就会从正常的流程中申请,成功后就会将所得page所在的pageblock整个加入到MIGRATE_HIGHATOMIC类型的列表中,但是并不能每次都能成功加进入,上线就是(zone_managed_pages(zone) / 100) + pageblock_nr_pages)个。

二、__rmqueue

从下面的流程图,可以看到,__rmqueue其实是在循环的执行__rmqueue_smallest,__rmqueue_smallest是真正从伙伴系统中那页面的过程,即对于伙伴系统分割大order,填充给小order过程的实现,也就是伙伴系统的最核心算法。

但是__rmqueue_smallest,只能从指定的迁移类型列表中拿页面,当该迁移类型页面不足时,就需要想其他的迁移类型借,我前面给出的那个二维数组:

static int fallbacks[MIGRATE_TYPES][3] = {
	[MIGRATE_UNMOVABLE]   = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE,   MIGRATE_TYPES },
	[MIGRATE_MOVABLE]     = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_TYPES },
	[MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE,   MIGRATE_MOVABLE,   MIGRATE_TYPES },
};

就表示了,借页面的顺序,如MIGRATE_UNMOVABLE不足时,首先从MIGRATE_RECLAIMABLE中借,然后从MIGRATE_MOVABLE中借。

而__rmqueue_fallback的作用就是,如果能成功接到,就把页面从被借的list放到指定的迁移类型列表中,此时将返回true,然后再执行__rmqueue_smallest,去分割order,分配页面。如果借不到,那么只能返回NULL

2.1 流程图

在这里插入图片描述

三、__rmqueue_pcplist

rmqueue_pcplist主要作用是根据order的大小,选择对应的pcp_list,并在调用__rmqueue_pcplist获取page之前上锁,因此我们直接看__rmqueue_pcplist的实现.

3.1 流程图

在这里插入图片描述
其实逻辑很简单,如果list为空,那么就调用rmqueue_bulk申请一批次的page填充到pcp_list中,主要就是这个逻辑。

而rmqueue_bulk同样也是调用__rmqueue实现的,那我我们现在来分析一下,__rmqueue的两个主要函数__rmqueue_fallback和__rmqueue_smallest

四、__rmqueue_fallback

此函数的作用是,当指定的迁移类型的列中中page不足时,从该迁移类型的后备列表中偷取空闲的page补充到指定的迁移类型列表中。并且系统初始化时,所有的page都是MIGRATE_MOVABLE类型.

而后被列表就是我们两次前面提到的:

/// 需要注意,没考虑CMA的情况
static int fallbacks[MIGRATE_TYPES][3] = {
	[MIGRATE_UNMOVABLE]   = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE,   MIGRATE_TYPES },
	[MIGRATE_MOVABLE]     = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_TYPES },
	[MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE,   MIGRATE_MOVABLE,   MIGRATE_TYPES },
};

可以看到,MIGRATE_UNMOVABLE、MIGRATE_MOVABLE和MIGRATE_RECLAIMABLE是互为后备列表的.

这个函数很长,但是实际上东西不好说,其大原则就是,在偷取page的时候要尽可能把整个pageblock都偷过去,实在买办法也要尽可能偷大的order。

然后要修改整块pageblock的类型,如果不是整块偷的,那么就会出现混合的情况,即同一pageblock的page的位于不同的迁移列表。但是整块的pageblock的类型是统一的,基本上是谁多是谁的类型,比如偷取的部分order>一半,那么就要全部改成对应了的类型。

五、__rmqueue_smallest

5.1 源码

struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,
						int migratetype)
{
	unsigned int current_order;
	struct free_area *area;
	struct page *page;

	/* Find a page of the appropriate size in the preferred list */
	for (current_order = order; current_order < MAX_ORDER; ++current_order) {
		area = &(zone->free_area[current_order]);
		page = get_page_from_free_area(area, migratetype);
		if (!page)
			continue;
		del_page_from_free_list(page, zone, current_order);
		expand(zone, page, order, current_order, migratetype);
		set_pcppage_migratetype(page, migratetype);
		return page;
	}
	return NULL;
}

函数非常的短小,整个循环恰好符合了伙伴系统的核心思想,即一层层的的往下拆分,直到合适的order的page.

5.1.1 get_page_from_free_area

即直接从指定迁移类型的list中找到page,如果失败,则寻找的order++;

5.1.2 del_page_from_free_list

负责将其list中拿走,操作包括list_del,list的计数–等;

5.1.3 expend

expend 是负责拆分的函数,即如果从odrer较大的list中选择了一一组page进行拆分,那么拆分后剩余的page将被添加到较低order的list中,注意,我们只需要将每一组的page的首个page添加到free list中即可,如我要获取order0的page,但是是从order3的块中获取的:
在这里插入图片描述
page指向的是order=3的块,page保存在伙伴系统order=3的free_list上,当我们需要以order=0返回page时,就需要把整个块拆分, 并以此将:

  • page + 2 ^0 的page添加到 order-0 free_list
  • page + 2 ^1 的page添加到 order-1 free_list
  • page + 2 ^2 的page添加到 order-2 free_list
    extend函数,就是做这件事
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值