Linux 内核学习笔记系列,内存管理部分,简单介绍伙伴系统。

伙伴系统简介

伙伴(Buddy)系统是为了解决外碎片问题而设计的。

伙伴系统算法把所有的空闲页框分为 11 个块链表,每个块链表分别包含 1、2、4、8、16、32、64、128、256、512 和 1024 个连续的页框。每个块的第一个页框的物理地址是该块大小的整数倍。由于内核页大小是 4 KB,所以最大请求为 4 MB 大小连续的 RAM 块。

在请求块时,算法会先在请求页框大小块的链表中检查是否有一个空闲块,如果没有,就查找下一个更大的块,把这个块分裂为两个小块。如果还是没有找到,就继续查找更大块,直到最大的 1024 个页框的块。如果 1024 个页框的链表还是空的,算法就放弃并发出错误信号。

在释放块时,算法会试图把一对空闲伙伴合并为一个单独的块,且算法是迭代的,即算法会再次试图合成的生成的块。满足以下条件的两个块被称为伙伴:

  • 两个块具有相同的大小,记作 b
  • 它们的物理地址连续。
  • 第一个块的第一个页框的物理地址是 2 * b * 2^12

由于块的大小都是 2 的幂,所以被称为伙伴的块只有一位二进制位不同。对于一个块,只需要与掩码(1 << order)进行异或操作,就能找到它的伙伴。

伙伴系统分配器

数据结构

include/linux/mmzone.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
struct zone {
    ...
    /*
     * free areas of different sizes
     */
    spinlock_t lock;
    struct free_area free_area[MAX_ORDER];
    ...
    /*
     * Discontig memory support fields.
     */
    struct pglist_data *zone_pgdat;
    struct page *zone_mem_map;
    /* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
    unsigned long zone_start_pfn;

    unsigned long spanned_pages; /* total size, including holes */
    unsigned long present_pages; /* amount of memory (excluding holes) */
    ...
}

对每个管理区,Linux 采用不同的伙伴系统,每个伙伴系统使用的主要数据结构如下:

  • mem_map 数组。每个区都关系到该数组的子集,由 zone_mem_map 指定子集的第一个元素。
  • free_area 数组。该数组的元素类型为 free_area,每个元素对应一种块大小。

分配块

mm/page_alloc.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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) { // current_order > order
        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); // 设置 private
    }
    return page;
}

/*
 * Do the hard work of removing an element from the buddy allocator.
 * Call me with the zone->lock already held.
 */
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); // 清除 private
        area->nr_free--;
        zone->free_pages -= 1UL << order;
        return expand(zone, page, order, current_order, area);
    }

    return NULL;
}

释放块

mm/page_alloc.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
static inline void __free_pages_bulk (struct page *page, struct page *base,
        struct zone *zone, unsigned int order)
{
    unsigned long page_idx;
    struct page *coalesced;
    int order_size = 1 << order;

    if (unlikely(order)) // 复合页
        destroy_compound_page(page, order);

    page_idx = page - base; // 当前块的下标

    BUG_ON(page_idx & (order_size - 1));
    BUG_ON(bad_range(zone, page));

    zone->free_pages += order_size;
    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); // 清除 private
        page_idx &= buddy_idx; // 恢复当前块的下标
        order++;
    }
    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++;
}

复合页:将物理上连续的两个或多个页看成一个独立的大页,具体可以参考社区新闻 An introduction to compound pages