伙伴系统的核心实现

一、基本数据结构复习

page_alloc.c - mm/page_alloc.c - Linux source code v5.4.285 - Bootlin Elixir Cross Referencer

先复习下伙伴系统的核心数据结构:

二、从伙伴系统申请page(__rmqueue):

取出page的时候,需要通过zone,order,migrate type来索引到具体的链表。也就是__rmqueue函数的前三个形参:

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

retry:
    // 首先进入伙伴系统到指定页面迁移类型的 free_list[migratetype] 获取空闲内存块
    // 这里走的就是上小节中介绍的伙伴系统核心流程
    page = __rmqueue_smallest(zone, order, migratetype);
    if (unlikely(!page)) {

      ..... 当伙伴系统中没有足够指定迁移类型 migratetype 的空闲内存块时,就会进入这个分支 .....

         // 如果迁移类型是 MIGRATE_MOVABLE 则优先 fallback 到 CMA 区中分配内存
        if (migratetype == MIGRATE_MOVABLE)
            page = __rmqueue_cma_fallback(zone, order);
        // 走常规的伙伴系统 fallback 流程,核心原理参见《3.伙伴系统的内存分配原理》小节
        if (!page && __rmqueue_fallback(zone, order, migratetype,
                                alloc_flags))
            goto retry;
    }
    // 内存分配成功
    return page;
}

2.1、__rmqueue_smallest

__rmqueue_smallest 是 Linux 内核伙伴系统中用于从指定迁移类型链表中分配内存的核心函数,其具体流程如下:

1. 遍历指定阶到最大阶的链表

遍历 order 到 MAX_ORDER 的各个阶的伙伴系统链表,直到找到对应迁移类型列表中可满足的

2. 分配页面并更新链表

  • 若链表非空,则从链表头部获取第一个空闲页块(通过 list_entry 提取 struct page 对象)
  • 将该页块从链表中移除(list_del),并更新空闲页计数器(area->nr_free--
  • 调用 rmv_page_order 清除页块的 PG_buddy 标志和私有字段(private),标记其已脱离伙伴系统

3. 拆分剩余内存(expand函数)​

若当前阶 current_order 大于请求的 order,需通过 expand() 函数将大块内存拆分为所需的小块:

  • 从高阶向低阶逐级拆分,例如从 order=4 拆分到 order=2,每次拆分后会将剩余的一半内存插入到对应的低阶链表中
  • 拆分过程中更新伙伴系统的空闲链表和计数器,确保剩余块能被后续分配使用。

4. 返回分配的页面

我们拿之前分析伙伴系统的基本原理的例子,结合代码进行分析。

/*
 * Go through the free lists for the given migratetype and remove
 * the smallest available page from the freelists
 */
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;

    /* 从当前分配阶 order 开始在伙伴系统对应的  free_area[order]  里查找合适尺寸的内存块*/
    for (current_order = order; current_order < MAX_ORDER; ++current_order) {
        // 获取当前 order 在伙伴系统中对应的 free_area[order] 
        // 对应上图 free_area[3]
        area = &(zone->free_area[current_order]);
        // 从 free_area[order] 中对应的 free_list[MIGRATE_TYPE] 链表中获取空闲内存块
        page = get_page_from_free_area(area, migratetype);
        if (!page)
            // 如果当前 free_area[order] 中没有空闲内存块则继续向上查找
            // 对应上图 free_area[0],free_area[1],free_area[2]
            continue;
        // 如果在当前 free_area[order] 中找到空闲内存块,则从 free_list[MIGRATE_TYPE] 链表中摘除
        // 对应上图步骤 1:将内存块从 free_area[3] 中摘除
        del_page_from_free_area(page, area);
        // 将摘下来的内存块进行减半分裂并插入对应的尺寸的 free_area 中
        // 对应上图步骤 [2,3], [4,5], [6,7]
        expand(zone, page, order, current_order, area, migratetype);
        // 设置页面的迁移类型
        set_pcppage_migratetype(page, migratetype);
        // 内存分配成功返回,对应上图步骤 8
        return page;
    }
    // 内存分配失败返回 null
    return NULL;
}

下面我们来看下减半分裂过程的实现,expand 函数中的参数在本节示例中:low = 指定分配阶 order = 0,high = 最后遍历到的分配阶 order = 3。


static inline void expand(struct zone *zone, struct page *page,
    int low, int high, struct free_area *area,
    int migratetype)
{
    // size = 8,表示当前要进行减半分裂的内存块是由 8 个连续 page 组成的。
    // 刚刚从 free_area[3] 上摘下
    unsigned long size = 1 << high;

    // 依次进行减半分裂,直到分裂出指定 order 的内存块出来
    // 对应上图中的步骤 2,4,6
    // 初始 high = 3 ,low = 0 
    while (high > low) {
        // free_area 要降到下一阶,此时变为 free_area[2]
        area--;
        // 分配阶要降级 high = 2
        high--;
        // 内存块尺寸要减半,由 8 变成 4,表示要分裂出由 4 个连续 page 组成的两个内存块。
        // 参考上图中的步骤 2
        size >>= 1;
        // 标记为保护页,当其伙伴被释放时,允许合并,参见 《4.伙伴系统的内存回收原理》小节
        if (set_page_guard(zone, &page[size], high, migratetype))
            continue;
        // 将本次减半分裂出来的第二个内存块插入到对应 free_area[high] 中
        // 参见上图步骤 3,5,7
        add_to_free_area(&page[size], area, migratetype);
        // 设置内存块的分配阶 high
        set_page_order(&page[size], high);

        // 本次分裂出来的第一个内存块继续循环进行减半分裂直到 high = low 
        // 即已经分裂出来了指定 order 尺寸的内存块无需在进行分裂了,直接返回
        // 参见上图步骤 2,4,6
    }
}

2.2、__rmqueue 伙伴系统的 fallback 实现

调用 __rmqueue_smallest 进入伙伴系统分配内存时,发现伙伴系统各个分配阶 free_area[order] 中对应的迁移列表 free_list[MIGRATE_TYPE] 无法满足内存分配需求时,__rmqueue_smallest 函数就会返回 null,伙伴系统内存分配失败。

随后内核就会进入伙伴系统的 fallback 流程,这里对 MIGRATE_MOVABLE 迁移类型做了一下特殊处理,当伙伴系统中 free_list[MIGRATE_MOVABLE] 没有足够空闲内存块时,会优先降级到 CMA 区域内进行分配。

如果我们指定的页面迁移类型并非 MIGRATE_MOVABLE 或者降级 CMA 之后仍然分配失败,内核就会进入 __rmqueue_fallback 走常规的 fallback 流程

在 __rmqueue_fallback 函数中,内核会根据预先定义的相关 fallback 规则开启内存分配的 fallback 流程。fallback 规则在内核中用一个 int 类型的二维数组表示,其中第一维表示需要进行 fallback 的页面迁移类型,第二维表示 fallback 的优先级。后续内核会按照这个优先级 fallback 到具体的 free_list[fallback_migratetype] 中去分配内存。

static int fallbacks[MIGRATE_TYPES][3] = {
    [MIGRATE_UNMOVABLE]   = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE,   MIGRATE_TYPES },
    [MIGRATE_MOVABLE]     = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_TYPES },
    [MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE,   MIGRATE_MOVABLE,   MIGRATE_TYPES },
};

比如:MIGRATE_UNMOVABLE 类型的 free_list 内存不足时,内核会 fallback 到 MIGRATE_RECLAIMABLE 中去获取,如果还是不足,则再次降级到 MIGRATE_MOVABLE 中获取,如果仍然无法满足内存分配,才会失败退出。

static __always_inline bool
__rmqueue_fallback(struct zone *zone, int order, int start_migratetype,
                        unsigned int alloc_flags)
{
    // 最终会 fall back 到伙伴系统的哪个 free_area 中分配内存
    struct free_area *area;
    // fallback 和正常的分配流程正好相反,是从最高阶的free_area[MAX_ORDER - 1] 开始查找空闲内存块
    int current_order;
    // 最初指定的内存分配阶
    int min_order = order;
    struct page *page;
    // 最终计算出 fallback 到哪个页面迁移类型 free_list 上
    int fallback_mt;
    // 是否可以从 free_list[fallback] 中窃取内存块到 free_list[start_migratetype]  中
    // start_migratetype 表示我们最初指定的页面迁移类型
    bool can_steal;
    
    // 如果设置了 ALLOC_NOFRAGMENT 表示不希望引入内存碎片
    // 在这种情况下内核会更加倾向于分配一个尽可能大的内存块,避免向其他链表引入内存碎片
    if (alloc_flags & ALLOC_NOFRAGMENT)
        // pageblock_order 用于定义系统支持的巨型页对应的分配阶
        // 默认为最大分配阶 - 1 = 9
        min_order = pageblock_order;

    // fallback 内存分配流程从最高阶 free_area 开始查找空闲内存块(页面迁移类型为 fallback 类型)
    for (current_order = MAX_ORDER - 1; current_order >= min_order;
                --current_order) {
        // 获取伙伴系统中最高阶的 free_area
        area = &(zone->free_area[current_order]);
        // 按照上述的内存分配 fallback 规则查找最合适的 fallback 迁移类型
        fallback_mt = find_suitable_fallback(area, current_order,
                start_migratetype, false, &can_steal);
        // 如果没有合适的 fallback_mt,则继续降级到下一个分配阶 free_area 中查找
        if (fallback_mt == -1)
            continue;

        // can_steal 会在 find_suitable_fallback 的过程中被设置
        // 当我们指定的页面迁移类型为 MIGRATE_MOVABLE 并且无法从其他 fallback 迁移类型列表中窃取页面 can_steal = false 时
        // 内核会更加倾向于 fallback 分配最小的可用页面,即尺寸和指定order最接近的页面数量而不是尺寸最大的
        // 因为这里的条件是分配可移动的页面类型,天然可以避免永久内存碎片,无需按照最大的尺寸分配
        if (!can_steal && start_migratetype == MIGRATE_MOVABLE
                    && current_order > order)
            goto find_smallest;
        // can_steal = true,则开始从 free_list[fallback] 列表中窃取页面
        goto do_steal;
    }

    return false;

find_smallest:
    // 该分支目的在于寻找尺寸最贴近指定 order 大小的最小可用页面
    // 从指定 order 开始 fallback
    for (current_order = order; current_order < MAX_ORDER;
                            current_order++) {
        area = &(zone->free_area[current_order]);
        fallback_mt = find_suitable_fallback(area, current_order,
                start_migratetype, false, &can_steal);
        if (fallback_mt != -1)
            break;
    }

do_steal:
    // 从上述流程获取到的伙伴系统 free_area 中获取 free_list[fallback_mt]
    page = get_page_from_free_area(area, fallback_mt);
    // 从 free_list[fallback_mt] 中窃取页面到 free_list[start_migratetype] 中
    steal_suitable_fallback(zone, page, alloc_flags, start_migratetype,
                                can_steal);
    // 返回到 __rmqueue 函数中进行 retry 重试流程,此时 free_list[start_migratetype] 中已经有足够的内存页面可供分配了
    return true;

}

从上述内存分配 fallback 源码实现中,我们可以看出内存分配 fallback 流程正好和正常的分配流程相反:

  • 伙伴系统正常内存分配流程先是从低阶到高阶依次查找空闲内存块,然后将高阶中的内存块依次减半分裂到低阶 free_list 链表中。

  • 伙伴系统 fallback 内存分配流程则是先从最高阶开始查找,找到一块空闲内存块之后,先迁移到最初指定的 free_list[start_migratetype] 链表中,然后在指定的 free_list[start_migratetype] 链表中执行减半分裂。

find_suitable_fallback 函数中封装了页面迁移类型整个的 fallback 过程:

  1. fallback 规则定义在 fallbacks[MIGRATE_TYPES][3] 二维数组中,第一维表示要进行 fallback 的页面迁移类型 migratetype。第二维 migratetype 迁移类型可以 fallback 到哪些迁移类型中,这些可以 fallback 的页面迁移类型按照优先级排列。

  2. 该函数的核心逻辑是在 for (i = 0;; i++) 循环中按照 fallbacks[migratetype][i] 数组定义的 fallback 优先级,依次在 free_area[order] 中对应的 free_list[fallback] 列表中查找是否有空闲的内存块。

  3. 如果当前 free_list[fallback] 列表中没有空闲内存块,则继续在 for 循环中降级到下一个 fallback 页面迁移类型中去查找,也就是 for 循环中的 fallbacks[migratetype][i] 。直到找到空闲内存块为止,否则返回 -1。

int find_suitable_fallback(struct free_area *area, unsigned int order,
            int migratetype, bool only_stealable, bool *can_steal)
{
    int i;
    // 最终选取的 fallback 页面迁移类型
    int fallback_mt;
    // 当前 free_area[order] 中以无空闲页面,则返回失败
    if (area->nr_free == 0)
        return -1;

    *can_steal = false;
    // 按照 fallback 优先级,循环在 free_list[fallback] 中查询是否有空闲内存块
    for (i = 0;; i++) {
        // 按照优先级获取 fallback 页面迁移类型
        fallback_mt = fallbacks[migratetype][i];
        if (fallback_mt == MIGRATE_TYPES)
            break;
        // 如果当前 free_list[fallback]  为空则继续循环降级查找
        if (free_area_empty(area, fallback_mt))
            continue;
        // 判断是否可以从 free_list[fallback] 窃取页面到指定 free_list[migratetype] 中
        if (can_steal_fallback(order, migratetype))
            *can_steal = true;

        if (!only_stealable)
            return fallback_mt;

        if (*can_steal)
            return fallback_mt;
    }

    return -1;
}
// 这里窃取页面的目的是从 fallback 类型的 freelist 中拿到一个高阶的大内存块
// 之所以要窃取尽可能大的内存块是为了避免引入内存碎片
// 但 MIGRATE_MOVABLE 类型的页面本身就可以避免永久内存碎片
// 所以 fallback MIGRATE_MOVABLE 类型的页面时,会跳转到 find_smallest 分支只需要选择一个合适的 fallback 内存块即可
static bool can_steal_fallback(unsigned int order, int start_mt)
{
    if (order >= pageblock_order)
        return true;

    if (order >= pageblock_order / 2 ||
        start_mt == MIGRATE_RECLAIMABLE ||
        start_mt == MIGRATE_UNMOVABLE ||
        page_group_by_mobility_disabled)
        return true;
    // 跳转到 find_smallest 分支选择一个合适的 fallback 内存块
    return false;
}

can_steal_fallback 函数中定义了是否可以从 free_list[fallback] 窃取页面到指定 free_list[migratetype] 中,逻辑如下:

  1. 如果我们指定的内存分配阶 order 大于等于 pageblock_order,则返回 true。pageblock_order 表示系统中支持的巨型页对应的分配阶,默认为伙伴系统中的最大分配阶减一,我们可以通过 cat /proc/pagetypeinfo 命令查看。

图片

  1. 如果我们指定的页面迁移类型为 MIGRATE_RECLAIMABLE 或者 MIGRATE_UNMOVABLE,则不管我们要申请的页面尺寸有多大,内核都会允许窃取页面 can_steal = true ,因为它们最终会 fallback 到 MIGRATE_MOVABLE 可移动页面类型中,这样造成内存碎片的情况会少一些。

  2. 当内核全局变量 page_group_by_mobility_disabled 设置为 1 时,则所有物理内存页面都是不可移动的,这时内核也允许窃取页面。

在系统初始化期间,所有页都被标记为 MIGRATE_MOVABLE 可移动的页面类型,其他的页面迁移类型都是后来通过 __rmqueue_fallback 窃取产生的。而是否能够窃取 fallback 迁移类型列表中的页面,就是本小节介绍的内容。

三、向伙伴系统归还page: 

page_alloc.c - mm/page_alloc.c - Linux source code v5.4.285 - Bootlin Elixir Cross Referencer

void __free_pages(struct page *page, unsigned int order)
{
	if (put_page_testzero(page))//通过原子操作减少页描述符 struct page 的引用计数 _count,并返回计数是否为 0
		free_the_page(page, order);
}


static inline void free_the_page(struct page *page, unsigned int order)
{
	if (order == 0)		/* Via pcp? */
		free_unref_page(page);
	else
		__free_pages_ok(page, order);
}

3.1 、单页释放(order == 0)​

  • ​如果释放一页的话,则直接释放到 CPU 高速缓存列表 pcplist 中。从这里我们看到伙伴系统回收内存的流程和伙伴系统分配内存的流程是一样的,在最开始首先都会检查本次释放或者分配的是否只是一个物理内存页(order = 0),如果是则直接释放到 CPU 高速缓存列表 pcplist 中。
    详细见:PCP(Per-CPU Pageset)_linux pcp服务-优快云博客 中的4.3小节。

​​3.2 、多页释放(order > 0)​

  • 调用 __free_pages_ok():进行安全检查后,通过伙伴系统释放连续页

    static void __free_pages_ok(struct page *page, unsigned int order)
    {
        unsigned long flags;
        int migratetype;
        // 获取释放内存页对应的物理页号 pfn
        unsigned long pfn = page_to_pfn(page);
        // 在将内存页回收至伙伴系统之前,需要将内存页 page 相关的无用属性清理一下
        if (!free_pages_prepare(page, order, true))
            return;
        // 获取页面迁移类型,后续会将内存页释放至伙伴系统中的 free_list[migratetype] 中
        migratetype = get_pfnblock_migratetype(page, pfn);
        // 关中断
        local_irq_save(flags);
        // 进入伙伴系统,释放内存
        free_one_page(page_zone(page), page, pfn, order, migratetype);
        // 开中断
        local_irq_restore(flags);
    }

__free_pages_ok 函数的逻辑比较容易理解,核心就是在将内存页回收至伙伴系统之前,需要将这些内存页的 page 结构清理一下,将无用的属性至空,将清理之后干净的 page 结构回收至伙伴系统中。这里大家需要注意的是在伙伴系统回收内存的时候也是不响应中断的。 

static void free_one_page(struct zone *zone,
                struct page *page, unsigned long pfn,
                unsigned int order,
                int migratetype)
{
    // 加锁
    spin_lock(&zone->lock);
    // 正式进入伙伴系统回收内存
    __free_one_page(page, pfn, zone, order, migratetype);
    // 释放锁
    spin_unlock(&zone->lock);
}

__free_one_page是伙伴系统的核心函数,在开始源码分析之前,我们继续使用基本原理分析的示意图辅助:

图片

以下是系统中空闲内存页在当前伙伴系统中的组织视图,现在我们需要将 page10 释放回伙伴系统中:

图片

如果在当前阶 free_area[order] 中找到了伙伴,则将释放的内存块和它的伙伴内存块两两合并成一个新的内存块,随后继续到高阶中去查找新内存块的伙伴,直到没有伙伴可以合并为止。

图片

/*
 * Freeing function for a buddy system allocator.
 */
static inline void __free_one_page(struct page *page,
        unsigned long pfn,
        struct zone *zone, unsigned int order,
        int migratetype)
{
    // 释放内存块与其伙伴内存块合并之后新内存块的 pfn
    unsigned long combined_pfn;
    // 伙伴内存块的 pfn
    unsigned long uninitialized_var(buddy_pfn);
    // 伙伴内存块的首页 page 指针
    struct page *buddy;
    // 伙伴系统中的最大分配阶
    unsigned int max_order;
    
continue_merging:
    // 从释放内存块的当前分配阶开始一直向高阶合并内存块,直到不能合并为止
    // 在本例中当前分配阶 order = 0,我们要释放 page10 
    while (order < max_order - 1) {
        // 在 free_area[order] 中查找伙伴内存块的 pfn
        // 上图步骤一中伙伴的 pfn 为 11
        // 上图步骤二中伙伴的 pfn 为 8
        // 上图步骤三中伙伴的 pfn 为 12
        buddy_pfn = __find_buddy_pfn(pfn, order);
        // 根据偏移 buddy_pfn - pfn 计算伙伴内存块中的首页 page 地址
        // 步骤一伙伴首页为 page11,步骤二伙伴首页为 page8,步骤三伙伴首页为 page12 
        buddy = page + (buddy_pfn - pfn);
        // 检查伙伴 pfn 的有效性
        if (!pfn_valid_within(buddy_pfn))
            // 无效停止合并
            goto done_merging;
        // 按照前面介绍的伙伴定义检查是否为伙伴
        if (!page_is_buddy(page, buddy, order))
            // 不是伙伴停止合并
            goto done_merging;
        // 将伙伴内存块从当前 free_area[order] 列表中摘下,对比步骤一到步骤四
        del_page_from_free_area(buddy, &zone->free_area[order]);
        // 合并后新内存块首页 page 的 pfn
        combined_pfn = buddy_pfn & pfn;
        // 合并后新内存块首页 page 指针
        page = page + (combined_pfn - pfn);
        // 以合并后的新内存块为基础继续向高阶 free_area 合并
        pfn = combined_pfn;
        // 继续向高阶 free_area 合并,直到不能合并为止
        order++;
    }
    
done_merging:
    // 表示在当前伙伴系统 free_area[order] 中没有找到伙伴内存块,停止合并
    // 设置内存块的分配阶 order,存储在第一个 page 结构中的 private 属性中
    set_page_order(page, order);
    // 将最终合并的内存块插入到伙伴系统对应的 free_are[order] 中,上图中步骤五
    add_to_free_area(page, &zone->free_area[order], migratetype);

}

这里笔者想重点来讲的是,内核如何在 free_area 链表中查找伙伴内存块,以及如何判断两个内存块是否为伙伴关系。下面我们来一起看下这部分内容:

图片

3.2.1、 如何查找可能的伙伴块

在伙伴系统中,每个内存块的大小为 2^order 页,且起始地址必须按块大小对齐(例如 order=2 时,块大小为 4 页,起始地址必须是 4 页的整数倍)。
此时,两个互为伙伴的块必须满足以下条件

  • 大小相同(即相同的 order)。
  • 物理地址连续。
  • 合并后形成一个更大的连续块,大小为 2^(order+1 )页;从它们的 PFN 二进制中,第 order 位不同,其他位相同
static inline unsigned long
__find_buddy_pfn(unsigned long page_pfn, unsigned int order)
{
 return page_pfn ^ (1 << order);
}

内核会通过 __find_buddy_pfn 函数根据当前释放内存块的 pfn,以及当前释放内存块的分配阶 order 来确定其伙伴块的 pfn。

首先通过 1 << order 左移操作确定要查找伙伴内存块的分配阶,因为伙伴关系最重要的一点就是它们必须是大小相等的两个内存块。然后巧妙地通过与要释放内存块的 pfn 进行异或操作就得到了伙伴内存块的 pfn 。

假设 pfn = 0x1000(二进制:0001 0000 0000 0000),order = 1(块大小为 2 页):

  1. 计算 1 << order:结果为 2(二进制 10)。
  2. 执行异或操作:0x1000 ^ 0x2 = 0x1002(看起来是向后查找?为什么不向前查找,比如0x1000-2
  3. 如果 0x1002 对应的块也是空闲的,那么这两个块(0x1000 和 0x1002)可以合并为一个更大的块(大小为 4 页)。

在释放内存时,如果能找到伙伴块并合并,就能减少内存碎片,提高大块内存的可用性。__find_buddy_pfn 通过位运算快速定位伙伴位置,无需遍历链表或复杂计算,极大提升了效率

为什么看似“向后查找”?

异或操作的结果可能指向高地址或低地址,具体取决于 pfn 的二进制表示。例如:

  • 若 pfn 的第 order 位为 0,异或后该位变为 1,伙伴块地址比当前块高。
  • 若 pfn 的第 order 位为 1,异或后该位变为 0,伙伴块地址比当前块低。

这种设计没有方向性偏好,伙伴的位置完全由内存块的自然对齐特性决定。看似“向后”或“向前”的观察只是地址二进制的表象。

3.2.2、 如何确定查找的伙伴块是否是“真伙伴”

page_is_buddy 就是相关的内核实现:

  1. 伙伴系统所管理的内存页必须是可用的,Guard Page 通常是一块不可访问的虚拟内存区域,放置在关键内存区域(如栈、堆)的边界。当程序意外访问到 Guard Page 时,会触发页错误(Page Fault),从而防止缓冲区溢出或内存损坏。

  2. 伙伴必须是空闲的内存块,这些内存块必须存在于伙伴系统中,组成内存块的内存页 page 结构中的 flag 标志设置了  PG_buddy 标记。通过 PageBuddy 判断这些内存页是否在伙伴系统中。

  3. 两个互为伙伴的内存块必须拥有相同的分配阶 order,也就是它们之间的大小尺寸必须一致。通过 page_order(buddy) == order 判断

     (1) ​page order 分配时的标记

    当通过alloc_pages分配内存时,伙伴系统会从free_area[order]链表中取出空闲块,并将该块的第一个页面的private字段标记为order值。后续页面通过page->first_page指向首页,从而共享order信息。

    struct page {
        unsigned long private;  // 可能用于存储order值
        // 其他字段如flags、_count等
    };
    (2) ​释放时的推导

    通过page->private或首页的first_page获取原始order值。

    static inline unsigned int page_order(struct page *page)
    {
    	/* PageBuddy() must be checked by the caller */
    	return page_private(page);
    }


    将该内存块重新插入free_area[order]链表,并检查相邻块是否为伙伴(地址连续且同属一个大块),以决定是否合并

  4. 互为伙伴关系的内存块必须处于相同的物理内存区域 zone 中。通过 page_zone_id(page) == page_zone_id(buddy) 判断。

    static inline int page_zone_id(struct page *page)
    {
    	return (page->flags >> ZONEID_PGSHIFT) & ZONEID_MASK;
    }

同时满足上述四点的两个内存块即为伙伴关系,下面是内核中关于判断是否为伙伴关系的源码实现:

static inline int page_is_buddy(struct page *page, struct page *buddy,
       unsigned int order)
{
 if (page_is_guard(buddy) && page_order(buddy) == order) {
  if (page_zone_id(page) != page_zone_id(buddy))
   return 0;

  return 1;
 }

 if (PageBuddy(buddy) && page_order(buddy) == order) {
  if (page_zone_id(page) != page_zone_id(buddy))
   return 0;

  return 1;
 }
 return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值