Linux Buddy算法&系统分析

213 篇文章 29 订阅
34 篇文章 2 订阅

证明:任何正整数都可以表示成2^n(n是包括0的自然数)加和的形式

这个就是整数的二进制表示,意义是直观的,Buddy算法用到了这个结论,证明可以参考

如何证明:用各不相同的2^n相加得到的集合,能包含全体正整数元素? - 知乎

内核Buddy系统的数据结构设计:

1.设计分为pgdat->zone->free_area->migration type四个级别。

2.pgdat代表的是一个NUMA内存节点.

3.free_area表示某个order的空闲buddy页面,其中的pfn满足assert(pfn & ((1 << order) - 1) == 0)断言。其中的内存页数量为 free_area[order].nr_free x (1<<order).

4.某个free_area(buddy order)下面在根据内存的迁移类型不同,挂到不同的链表中。

5.如第三条公式所隐含,链进某个order下对应的free_area中的页面,其PFN必须是order对齐的,并且PageBuddy测试为TRUE,且只有此order下面的连续内存的第一个page的PageBuddy测试为TRUE.

6.除了order为0的情况(后面会解释),free_area[order]的链表中只链入了buddy 页面。但是每个buddy页面隐含的却是1<<order个空闲物理页面。

参考内核实现的用户态BUDDY实现代码:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
#include "br_list.h"

#define MAX_ORDER       11
#define DBG(fmt, ...)   do { \
    printf("%s line %d, "fmt, __func__, __LINE__, ##__VA_ARGS__); \
} while (0)

#define TOTAL_PAGES (1024 * 4)
//#define TOTAL_PAGES (1024 * 3)
//#define TOTAL_PAGES (1024 * 2)
//#define TOTAL_PAGES (1024 * 1)

struct page {
	struct br_list_head list;
	int page_idx;
	int order;
	int is_buddy;
};

struct br_list_head slab_list[MAX_ORDER];
struct page page_array[TOTAL_PAGES];
static void init_slab(void)
{
	int i;

	for (i = 0; i < MAX_ORDER; i ++) {
		BR_INIT_LIST_HEAD(&slab_list[i]);
	}
}

static void init_pages(void)
{
	int i;

	for (i = 0; i < TOTAL_PAGES; i ++) {
		BR_INIT_LIST_HEAD(&page_array[i].list);
		page_array[i].page_idx = i;
		page_array[i].order = 0;
		page_array[i].is_buddy = 0;
	}
}

static int pfn_valid_within(int buddy_pfn)
{
	return (buddy_pfn >= 0 && (buddy_pfn < TOTAL_PAGES)) ? 1 : 0;
}

static int page_is_buddy(int buddy, int order)
{
	struct page *tmp;

	br_list_for_each_entry(tmp, &slab_list[order], list) {
		if (tmp->page_idx == buddy)
			return 1;
	}

	return 0;
}

static int del_page_from_free_area(int buddy)
{
	br_list_del(&page_array[buddy].list);
	BR_INIT_LIST_HEAD(&page_array[buddy].list);
}

static unsigned long __find_buddy_pfn(unsigned long page_pfn, unsigned int order)
{
	return page_pfn ^ (1 << order);
}

static void set_page_order(struct page *page, int order)
{
	page->order = order;
}

static void set_page_buddy(struct page *page)
{
	page->is_buddy = 1;
}

static void clr_page_buddy(struct page *page)
{
	if (!page->is_buddy) {
		DBG("error clr buddy but page is not buddy.\n");
		exit(-1);
	}
	page->is_buddy = 0;
}

static void add_to_free_area(struct page *page, int order)
{
	DBG("add area order %d, pfn %d.\n", order, page->page_idx);
	set_page_order(page, order);
	set_page_buddy(page);
	br_list_add(&page->list, &slab_list[order]);
}

static int __free_pages(struct page *page, int order)
{
	int pfn, buddy_pfn, buddy, combined_pfn;

	pfn = page->page_idx;
	if (pfn & ((1 << order) - 1)) {
		DBG("page not order aligned.\n");
		return -1;
	}

	while (order < MAX_ORDER) {
		buddy_pfn = __find_buddy_pfn(pfn, order);
		buddy = pfn + (buddy_pfn - pfn);

		if (!pfn_valid_within(buddy_pfn)) {
			goto merging;
		}

		if (!page_is_buddy(buddy, order)) {
			goto merging;
		}

		if (order == (MAX_ORDER - 1)) {
			goto merging;
		}

		del_page_from_free_area(buddy);
		combined_pfn = buddy_pfn & pfn;
		page = page + (combined_pfn - pfn);
		//DBG("buddy_pfn 0x%x, pfn 0x%x, combined_pfn 0x%x, page->page_idx 0x%x, order %d.\n", buddy_pfn, pfn, combined_pfn, page->page_idx, order);

		pfn = combined_pfn;
		order ++;
	}

merging:
	add_to_free_area(page, order);

	return 0;
}

static int free_one_page(struct page *page)
{
	return __free_pages(page, 0);
}

int pagebuddy(struct page *page)
{
	return page->is_buddy;
}

void list_slab(void)
{
	int i;
	int count = 0;
	struct page *buddy;

	printf("================================================================================\n");
	for (i = 0; i < MAX_ORDER; i ++) {
		if (br_list_empty(&slab_list[i])) {
			DBG("slab order %d free pages is 0.\n", i);
		} else {
			count = 0;

			br_list_for_each_entry(buddy, &slab_list[i], list) {
				DBG("buddy idx %d, order %d.\n", buddy->page_idx, buddy->order);
				if (!pagebuddy(buddy)) {
					DBG("page in buddy without buddy flag.\n");
					exit(-1);
				}
				count ++;
			}
			DBG("slab order %d free pages is %d.\n", i, count * (1 << i));
		}
	}
	printf("================================================================================\n");
}

static int free_pages(void)
{
	int i;

	for (i = 0; i < TOTAL_PAGES; i ++) {
		free_one_page(&page_array[i]);
	}
}

static void expand(struct page *page, int low, int high)
{
	unsigned long size = 1 << high;

	while (high > low) {
		high --;
		size >>= 1;
		add_to_free_area(&page[size], high);
	}
}

static struct page *alloc_pages(int order)
{
	int i;
	struct page *page;

	for (i = order; i < MAX_ORDER; i ++) {
		if (br_list_empty(&slab_list[i]))
			continue;

		page = br_list_entry(slab_list[i].next, struct page, list);
		br_list_del(&page->list);
		expand(page, order, i);
		clr_page_buddy(page);
		return page;
	}

	return NULL;

}

int main(void)
{
	init_slab();
	init_pages();
	free_pages();
	list_slab();

	struct page *pg = alloc_pages(6);

	DBG("pg = %p, index %d.\n", pg, pg->page_idx);

	list_slab();

	__free_pages(pg, 6);

	DBG("run finish!\n");
	return 0;
}

留意__SetPageBuddy这个函数,它在整个内核中只有一处被调用到,位于ORDER链表中的首个PAGE会被设置为Buddy page.下图的堆栈表示设置Page为Buddy头的地方。

             cat-7124    [004] d...   380.942900: set_page_order.isra.66 <-__free_one_page
             cat-7124    [004] d...   380.942906: <stack trace>
 => set_page_order.isra.66
 => __free_one_page
 => free_one_page
 => __free_pages_ok
 => __free_pages
 => free_pages.part.101
 => free_pages
 => buddy_test
 => my_seq_ops_show
 => seq_read
 => proc_seq_read
 => proc_reg_read
 => __vfs_read
 => vfs_read
 => ksys_read
 => __x64_sys_read
 => do_syscall_64
 => entry_SYSCALL_64_after_hwframe

PageBuddy测试:

191号seqfile测试说明:

1.frea_area中分配的物理页面在分配后会清理掉其Buddy属性,从而刚刚从BUDDY中分配出来的热乎页面是无法通过PageBuddy测试的。

2.将page还回去后,如果这个page不被Buddy分配器向上合并,将会被上面的调用堆栈重新设置Buddy标志,此时归还后的PAGE页面能够通过PageBuddy测试。

3.order为0时,每个连接进free_list链表的PAGE都是BUDDY页面.

4.frea_area每个order链表的代表的BUDDY块大小为1<<order,其中第一个PAGE设置了Buddy flag,能够通过PageBuddy测试,其余的不能通过测试。

5.Buddy块中所有的PAGE的refcount都为0,而除了第一个Buddy page之外的所有page,其_mapcount为-1。而第一个PAGE的_mapcount为-129,或许和struct page定义的_mapcount为UNION体有关,UNION体中其他字段的值影响了_mapcount。页面被分配后,也之有Buddy块中的首个页面的refcount被设置为1,其余的PAGE仍然为0,虽然被分配了。

可以通过echo m > /proc/sysrq-trigger来观察buddy状态,与/proc/buddyinfo的信息是一致的.

如何判断一个page是否在Buddy系统中被管理?

Buddy头有PageBuddy标志可以判断此时的状态,但是对于任意一个PAGE,该如何判断它是否属于某个Buddy块,进而确定其属于空闲状态呢?可以参考fs/proc/page.c中的实现,它是/proc/kpageflags文件的内核驱动,用于向用户态暴露所有页面的状态信息。

可以看到,如果一个PAGE为PageBuddy 头,或者属于这个PageBuddy头管理的order范围内的PAGE,则这个PAGE就是空闲的,关于is_free_buddy_page的分析,看下文。

逻辑点

1.寻找到Buddy 地址的算法如下,这个算法不要求物理地址从0开始,仅仅要求物理PFN按照MAX_ORDER对齐即可。

pfn \& \left [ \sim((1 << order) - 1)\right ]^{\widehat{\ \ }}(1 << order).

例如,LINUX内核设置的MAX_ORDER为11,最大有效取值为10.所以 pfn的低10位为0,也就是PFN必须按1<<10=1024对齐,LINUX页大小为4K,也就是说,硬件设计的时候,只要保证物理地址被映射到4M对齐的地址即可满足BUDDY算法管理的要求。也就是说,硬件设计需要保证如下公式成立:

pfn\_start\equiv 0 \ (mod\ [1 << (MAX\_ORDER-1)])

2.buddy分配器分配和释放只记录和修改buddy page的状态,而不会改变此buddy内其余PAGE的状态,那么如果任意给定一个PAGE,该如何判断其是free还是被分配的使用状态呢?参考如下韩函数,得到改PAGE对应的BUDDY域。

找到一个Buddy头,然后被查询的PAGE在这个buddy 头cover的orer个PAGE中,则这个PAGE是FREE的,如果找不到这样的buddy头,则是已经分配出去,没有在buddy中的PAGE。

bool is_free_buddy_page(struct page *page)
{
        struct zone *zone = page_zone(page);
        unsigned long pfn = page_to_pfn(page);
        unsigned long flags;
        unsigned int order;

        spin_lock_irqsave(&zone->lock, flags);
        for (order = 0; order < MAX_ORDER; order++) {
                struct page *page_head = page - (pfn & ((1 << order) - 1));

                if (PageBuddy(page_head) && page_order(page_head) >= order)
                        break;
        }
        spin_unlock_irqrestore(&zone->lock, flags);

        return order < MAX_ORDER;
}

X86架构下如何分配大内存?

内核默认的BUDDY ORDER为11,对应最大的BUDDY 分配块为4M,也就是说,通过BUDDY系统分配的连续物理内存最大为4M,如果场景要求分配大于4M的连续物理内存怎么办?有三种办法:

1。使用CMA/ION内存,在命令行中设置预留内存,预留多大都可以,只要物理内存允许范围之内,由于CMA使用了BITMAP管理,并非BUDDY系统,所以理论上可以分配任意大小的连续物理内存。

2。调整MAX_ORDER,重新编译内核,这样无法精准打击,比如需求是连续的64M内存,调整后,32M,16M,8M也不得不支持。粒度较大。调整方式为配置CONFIG_FORCE_MAX_ZONEORDER。

3。启动阶段调用alloc_bootmem 内存分配器,需要修改内核。

4。使用巨页,2M,4G,512G,。。。,映射给驱动或者外设。

参考文档

64/32位Linux系统的差异(地址空间布局,系统调用)对比分析_linux32位-CSDN博客

深入理解Linux内核——MM | linkthinking


结束

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Linux内核中采用了伙伴算法来管理内存。伙伴算法是一种二叉树算法,它将可用内存块组织成一个二叉树,每个节点表示一块内存区域。当一个内存块被分配时,它会被划分成两个等大小的块,其中一个块被分配给请求者,另一个块成为伙伴块。如果伙伴块也是空闲的,那么这两个伙伴块会被合并成一个更大的块,这个过程会一直持续到不能再合并为止。 伙伴算法的实现主要包括以下几个步骤: 1. 初始化内存池,将整个可用内存块作为一棵二叉树。 2. 当有内存请求时,从根节点开始遍历二叉树,找到第一个大小合适的空闲块。 3. 如果找到的空闲块比请求的内存大,就将它划分成两个等大小的块,并将其中一个块分配给请求者,另一个块成为伙伴块。 4. 如果伙伴块也是空闲的,那么这两个伙伴块会被合并成一个更大的块,这个过程会一直持续到不能再合并为止。 5. 当内存释放时,将该内存块标记为空闲状态,并检查它的伙伴块是否也为空闲状态,如果是,则将这两个伙伴块合并成一个更大的块。 下面是一个简单的伙伴算法的实现示例: ```c struct buddy_node { int size; // 内存块大小 int used; // 是否已被使用 struct buddy_node *next; // 指向下一个空闲块 }; struct buddy_node *buddy_pool; // 内存池 int pool_size; // 内存池大小 void buddy_init(int size) { pool_size = size; buddy_pool = (struct buddy_node *)malloc(sizeof(struct buddy_node) * pool_size); buddy_pool[0].size = pool_size; buddy_pool[0].used = 0; buddy_pool[0].next = NULL; } int buddy_alloc(int size) { int node_size = 1; while (node_size < size) { node_size <<= 1; } for (int i = 0; i < pool_size; i++) { if (buddy_pool[i].size == node_size && !buddy_pool[i].used) { buddy_pool[i].used = 1; return i; } } for (int i = 0; i < pool_size; i++) { if (buddy_pool[i].size > node_size && !buddy_pool[i].used) { int left = i; int right = i + node_size; buddy_pool[left].used = 1; buddy_pool[right].size = buddy_pool[left].size - node_size; buddy_pool[right].used = 0; buddy_pool[right].next = buddy_pool[left].next; buddy_pool[left].next = &buddy_pool[right]; buddy_pool[left].size = node_size; return left; } } return -1; } void buddy_free(int index) { buddy_pool[index].used = 0; struct buddy_node *buddy = &buddy_pool[index ^ buddy_pool[index].size]; while (buddy->next != &buddy_pool[index]) { buddy = buddy->next; } buddy->next = buddy_pool[index].next; buddy_pool[index ^ buddy_pool[index].size].size += buddy_pool[index].size; buddy_pool[index ^ buddy_pool[index].size].used = 0; buddy_pool[index].next = NULL; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

papaofdoudou

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

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

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

打赏作者

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

抵扣说明:

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

余额充值