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