伙伴系统算法

首先总体介绍下linux对内存的划分:

一 内存介绍

1. 节点 pg_data_t

在某些体系架构下,同一cpu访问不同部分的内存地址时间是不一样的,为了解决这个问题,将内存划分为不同的节点。同一cpu访问同一节点下的内存,其时间是一样的。当然,不同cpu访问同一节点下的内存,时间不一定一样。

对于x86体系,cpu访问不同地址的内存,其时间是一样的,因此内存只有一个节点。

2. 管理区 zone

节点下又划分不同的管理区。受限于某些硬件设备,或地址空间的大小,物理内存被划分为三(两)块:DMA区,NORMAL区,HIGHMEM区(只有32位系统有)。节点中会维护着该节点下所有管理区的信息,如下图所示:

图1 

内核从管理区(DMA管理区,NORMAL管理区等)申请内存时,是以页框为单位的,即申请的内存大小为4K的整数倍。试想如果存在以下的情况。其中白色部分为空闲页框,红色部分为已使用页框。在下面的区域中,存在8K的空闲内存,但无法申请连续的8K的内存,即内存存在碎片。即使系统有足够的空闲内存,也有可能出现申请失败的情况、

图2 

下面为看下linux内核是如何处理这种情况的

二 伙伴算法

每个管理区维护了一个free_area结构的数组(结构中包含一个双向链表头),该数据共11个元素,第n个元素链接的是多个页框,每个页框有连续2的n次方个空闲页框,如下图:

图3 

 第0个元素链接的是单个页框;

第1个元素链接的是连续2(2^1)个空闲的页框;

第2个元素链接的是连续4(2^2)个空闲的页框;

第3个元素链接的是连续8(2^3)个空闲的页框;

以此类推。

2.1 链表非空

内核申请内存时,每次申请的都是2的n次方个连续的页框。

比如内存申请1个页框时,需要从数组0的链表中获取。此时链表不空,取出第1个页框后,如下图4所示:

图4 

再获取一个页框后,数组0的链表将成为空链表,如图5所示:

图5 

2.2 链表已空 

再申请一个页框时,此时数组0链表已空,则从数组1链表中获取。由于数组1指向的都是连续2个空闲的页框,将其中一个页框提供给内核,然后将另一个页框放入数组0链表中,如图6所示:

图6

2.3 下级链表已空 

图7 

 图7中,数组0和数组1的链表都已空,此时申请1个页框,此从数组2链表中获取。将1个页框返回给内核,一个页框添加到数组0链表中,另外2个页框添加到数组1链表中。如图8:

图8

2.4 链表中页框地址

第n个数组中的链表中链接的页框,其起始地址为2的n次方的倍数。

如数组1链表中的页框,起始地址为2(2^1)的倍数;

数组3链表中的页框,起始地址为8(2^3)的倍数;

2.4 返还页框

 返还页框,是申请页框的逆向,如向图8返还1个页框后,如图9所示:

图9 

 再返地址为4的页框后,如图10所示:

图10 

右边有两个相临的单个页框(蓝色),其起始地址为 4,符合2.4中所述规则,因此将两个页框视做连续2个空闲的页框,放入数组1链表中,如图11所示:

图11 

 右上角黄色的页框,又可以合并后放入数组2的链表中,如图12所示:

图12 

2.5 不能合并的页框

图 13 

图中连续的两个蓝色页框,其起始地址为1,不是2(2^1)的倍数,因此不能合并后,放入数组1的链表。 

三 内核代码

从管理区的伙伴系统申请页框的方法是__rmqueue,__rmqueue又调用__rmqueue_smallest

static __always_inline struct page *
__rmqueue(struct zone *zone, unsigned int order, int migratetype)
{
	struct page *page;

retry:
	page = __rmqueue_smallest(zone, order, migratetype);
	if (unlikely(!page)) {
		if (migratetype == MIGRATE_MOVABLE)
			page = __rmqueue_cma_fallback(zone, order);

		if (!page && __rmqueue_fallback(zone, order, migratetype))
			goto retry;
	}

	trace_mm_page_alloc_zone_locked(page, order, migratetype);
	return page;
}

/*
zone  目标管理区(DMA, NORMAL, HIGHMEM)
order free_area数组的下标
*/
static __always_inline
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 = list_first_entry_or_null(&area->free_list[migratetype],
							struct page, lru);
		
		// 先从指定的数组中查找,如果没找到,则从下一个数组中找(对应2.2 链表已空)
		if (!page)
			continue;
		list_del(&page->lru); // 将页框从链表中删除
		rmv_page_order(page);
		area->nr_free--;
		// 重新整理链表
		expand(zone, page, order, current_order, area, migratetype);
		set_pcppage_migratetype(page, migratetype);
		return page;
	}

	return NULL;
}

/*
 low		请求的order
 high		实际获取页框的order
*/
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;

	// __rmqueue指定的数组中未找到空闲页框
	while (high > low) {
		area--;
		high--;
		size >>= 1;
		VM_BUG_ON_PAGE(bad_range(zone, &page[size]), &page[size]);

		/*
		 * Mark as guard pages (or page), that will allow to
		 * merge back to allocator when buddy will be freed.
		 * Corresponding page table entries will not be touched,
		 * pages will stay not present in virtual address space
		 */
		if (set_page_guard(zone, &page[size], high, migratetype))
			continue;

		// 插入伙伴,作为链表中第一个元素(将后一半的页框,加到前一个数组中,对应图5到图6)
		list_add(&page[size].lru, &area->free_list[migratetype]);
		area->nr_free++; // 可用数量+1
		set_page_order(&page[size], high);
	}
}

归还页框的函数是free_pages,这里就不赘述了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值