您的位置:首页 > 编程语言 > Go语言

内存管理之伙伴系统算法(The Buddy System Algorithm)

2013-10-14 09:06 459 查看
Linux内核中对于内存分配采用的是伙伴系统算法,该算法主要用于解决外部碎片问题(external fragmentation)。把所有的空闲页框分为11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,512和1024个连续的页框。每个块的第一个页框的物理地址是该块大小的整数倍。例如,大小为16个页框的块,其起始地址是16 x 2^12(2^12=4096,这是一个常规页的大小)的整数倍。one->free_pages += order_size;

接下来举例说明该算法的工作原理:

假设要请求一个256个页框块(即1MB)。算法先在256个页框的链表中检查是否有一个空闲块。如果没有,算法会查找下一个更大的页块,也就是在512个页框的链表中找一个空闲块。如果存在这样的空闲块,内核就把256的页框分为两等份,一半用作满足请求,另一半插入到256个页框的链表中。如果在512个页框的块链表中也没有找到空闲块,就继续找更大的块——1024个页框的块。如果存在,内核就把1024个页框块的256个页框用作请求,然后从剩余的768个页框中拿512个插入到512个页框的链表中,再把最后的256个插入到256个页框的链表中。如果1024个页框的链表还是空,算法就放弃并发出错信号。

以上过程的逆过程就是页框块的释放过程,内核试图把大小为b的一对空闲伙伴块合并为一个大小为2b的单独块。满足一下条件的两个块称为伙伴:

(1)两个块大小相等,记为b。

(2)它们物理地址是连续的。

(3)第一块的第一个页框的物理地址是2 x b x  2^12的倍数。

该算法是迭代的,如果它成功合并所释放的块,它会试图合并2b的块,以再次试图形成更大的块。

1、数据结构

伙伴系统算法使用的主要数据结构为:

(1)mem_map数组,该数组用来存放所有页框的页描述符。每个管理区都关系到mem_map元素的子集。子集中的第一个元素和元素的个数分别由管理区描述符额zone_mem_map和size字段指定。

(2)包含有11个元素、元素类型为free_area的一个数组,每个元素对应一种块大小。该数组存放在管理去描述符的free_area字段中。



2、分配块

内核中页框块分配函数如下:

static struct page *__rmqueue(struct zone *zone, unsigned int order)
{
struct free_area * area;
unsigned int current_order;
struct page *page;

for (current_order = order; current_order < MAX_ORDER; ++current_order) {
area = zone->free_area + current_order;
if (list_empty(&area->free_list))
continue;

page = list_entry(area->free_list.next, struct page, lru);
list_del(&page->lru);
rmv_page_order(page);
area->nr_free--;
zone->free_pages -= 1UL << order;
return expand(zone, page, order, current_order, area);
}

return NULL;
}

其中expand()函数就是处理当页框链表中的块大于所请求的页框块时的情况:

(1)迭代将页框块一分为二,将后面的一半页框块插入到相应的页框块链表中;

(2)检查剩余的前一半,是否current_order>order值。如果是,转到(1)去执行;如果不是,就转到(3);

(3)结束。

expand()函数的相应代码为:

static inline struct page *
expand(struct zone *zone, struct page *page,
int low, int high, struct free_area *area)
{
unsigned long size = 1 << high;

while (high > low) {
area--;
high--;
size >>= 1;
BUG_ON(bad_range(zone, &page[size]));
list_add(&page[size].lru, &area->free_list);
area->nr_free++;
set_page_order(&page[size], high);
}
return page;
}


3、释放块

__free_pages_bulk()函数按照伙伴系统的策略释放页框。它使用3个基本输入参数:

page:被释放块中所包含的第一个页框描述符的地址。

zone:管理区描述符的地址。

order:块大小的对数。

该函数假设调用者已经禁止本地中断并获得了保护伙伴系统数据结构的zone->lock自旋锁。__free_pages_bulk()首先声明和初始化一些局部变量:

struct page * base = zone->zone_mem_map;
unsigned long buddy_idx, page_idx = page - base;
struct page * buddy, * coalesced;
int order_size = 1 << order;

page_index局部变量包含块中第一个页框的下标,这是相对于管理区的第一个页框而言的。

order_size局部变量用于增加管理区中空闲页框的计数器:

zone->free_pages += order_size;

现在函数开始执行循环,最多循环(10-order)次,每次尽量把一个块和它的伙伴进行合并。函数以最小的块开始,然后向上移动到顶部:

while (order < MAX_ORDER-1) {
struct free_area *area;
struct page *buddy;
int buddy_idx;

buddy_idx = (page_idx ^ (1 << order));
buddy = base + buddy_idx;
if (bad_range(zone, buddy))
break;
if (!page_is_buddy(buddy, order))
break;
/* Move the buddy up one level. */
list_del(&buddy->lru);
area = zone->free_area + order;
area->nr_free--;
rmv_page_order(buddy);
page_idx &= buddy_idx;
order++;
}

在循环体中首先要寻找拥有page_index页描述符下标的块的伙伴,而伙伴块的第一个页框号的下标要么是page_index-2^order,要么是page_index+2^order。所以这里巧妙的使用语句buddy_idx = (page_idx ^ (1 << order));来查找伙伴块。

一旦知道伙伴块的下标,就可以通过下式很容易地获得伙伴块的页描述符:buddy = base + buddy_idx;

现在函数调用page_is_buddy()来检查buddy是否是描述了大小为order_size的空闲页框号的第一个页。

static inline int page_is_buddy(struct page *page, int order)
{
if (PagePrivate(page)           &&
(page_order(page) == order) &&
!PageReserved(page)         &&
page_count(page) == 0)
return 1;
return 0;
}

上述函数说明了要成为伙伴块的四个条件:

(1)buddy的第一个页必须为空闲(_count字段等于-1);

(2)它必须属于动态内存(PG_reserved 位清零);

(3)它的private 字段必须有意义(PG_private 位置位);

(4)它的private字段必须存放将要被释放的块的order。

如果所有这些条件都符合,伙伴块就被释放,并且函数将它从以order排序的空闲块链表中删除,并再执行一次循环以寻找两倍大小的伙伴块。

如果上面判断伙伴块中至少有一个条件没有满足,该函数就跳出循环。函数将它插入适当的链表并以块大小的order更新第一个页框的private字段。

coalesced = base + page_idx;
set_page_order(coalesced, order);
list_add(&coalesced->lru, &zone->free_area[order].free_list);
zone->free_area[order].nr_free++;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: