Buddy system之alloc_pages

申请pages的基础知识在“Buddy system基础 ”这篇文章中已经讲过了,这里主要分析申请pages的代码。

代码框架


alloc_pages //alloc_pages_node(numa_node_id(), gfp_mask, order)
   |----->return __alloc_pages_node(nid, gfp_mask, order);
   |   |----->return __alloc_pages(gfp_mask, order, node_zonelist(nid, gfp_mask));
   |   |   |----->return __alloc_pages_nodemask(gfp_mask, order, zonelist, NULL);
   |   |   |   |----->.high_zoneidx = gfp_zone(gfp_mask),
   |   |   |   |      根据传递进来的gfp_mask,选择从那个zone开始分配
   |   |   |   |----->.zonelist = zonelist,
   |   |   |   |      设置要从哪个zonelist分配,对于UMA来说,只有一个zonelist
   |   |   |   |----->.nodemask = nodemask,
   |   |   |   |----->.migratetype = gfpflags_to_migratetype(gfp_mask),
   |   |   |   |      根据传递进来的gfp_mask,选择要分配哪种迁移类型的page
   |   |   |   |----->ac.preferred_zoneref = first_zones_zonelist(ac.zonelist,
   |   |   |   |             ac.high_zoneidx, ac.nodemask);
   |   |   |   |      找到最先使用的zone
   |   |   |   |----->page = get_page_from_freelist(alloc_mask, order, alloc_flags, &ac);
   |   |   |   |      遍历所有符合条件的zone,分配pages
   |   |   |   |   |----->page = buffered_rmqueue(ac->preferred_zoneref->zone, zone, order,
   |   |   |   |   |                        gfp_mask, alloc_flags, ac->migratetype);
   |   |   |   |   |   |----->如果order为0,即分配一个page,则走如下逻辑
   |   |   |   |   |   |----->pcp = &this_cpu_ptr(zone->pageset)->pcp;
   |   |   |   |   |   |----->list = &pcp->lists[migratetype];
   |   |   |   |   |   |      获取当前cpu的pageset链表,为该cpu分配的free pages会加入该链表
   |   |   |   |   |   |----->pcp->count += rmqueue_bulk(zone, 0, pcp->batch, list, migratetype, cold);
   |   |   |   |   |   |   |----->struct page *page = __rmqueue(zone, order, migratetype);
   |   |   |   |   |   |   |      从所选的zone,迁移类型为migratetype的free_list中,分配1个pages
   |   |   |   |   |   |   |----->list_add_tail(&page->lru, list);
   |   |   |   |   |   |   |      将分配到的page加入pcp->lists[migratetype]链表
   |   |   |   |   |   |----->page = list_first_entry(list, struct page, lru);
   |   |   |   |   |   |----->list_del(&page->lru);
   |   |   |   |   |   |----->pcp->count--;
   |   |   |   |   |   |      从pcp->lists[migratetype]取出一个page,并将该page从链表中删除
   |   |   |   |   |   |----->如果order不为0,即分配多个page,则走如下逻辑
   |   |   |   |   |   |   |----->page = __rmqueue(zone, order, migratetype);
   |   |   |   |   |   |   |      直接分配
   |   |   |   |   |----->prep_new_page(page, order, gfp_mask, alloc_flags)
   |   |   |   |   |----->return page;

重要函数分析


__rmqueue

源代码:

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

    page = __rmqueue_smallest(zone, order, migratetype);
    if (unlikely(!page)) {
        if (migratetype == MIGRATE_MOVABLE)
            page = __rmqueue_cma_fallback(zone, order);

        if (!page)
            page = __rmqueue_fallback(zone, order, migratetype);
    }

    trace_mm_page_alloc_zone_locked(page, order, migratetype);
    return page;
}

代码分析:
首先调用__rmqueue_smallest 从migratetype对应的链表中分配1<

__rmqueue_smallest

源代码:

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

    /* Find a page of the appropriate size in the preferred list */
    for (current_order = order; current_order < MAX_ORDER; ++current_order) {
        area = &(zone->free_area[current_order]);
        page = list_first_entry_or_null(&area->free_list[migratetype],
                            struct page, lru);
        if (!page)
            continue;
        list_del(&page->lru);
        rmv_page_order(page);
        area->nr_free--;
        expand(zone, page, order, current_order, area, migratetype);
        set_pcppage_migratetype(page, migratetype);
        return page;
    }

    return NULL;
}

代码分析:
假设我们在建立了buddy分配器之后,首次分配order为0的pages,即分配一个page.
我们首先搞清楚buddy分配器刚刚建立完成后,buddy system上下文的状况:

  1. 所有的pages 1024个为一组链入order为10的zone->free_area中
  2. 如果page num是不是1024个整数倍,则会有pages链入其他的order对应的zone->free_area中,我们暂时不考虑这种情况。

该函数会从order对应的zone->free_area分配page,我们假设order为0,则order 0-9对应的zone->free_area都是空的,order 10对应的zone->free_area不为空,所以从order 10对应的zone->free_area分配page,其过程如下:
1. 从migratetype对应的free_list中获取一个element,即1024个pages的头。
2. 将该element从链表中删除
3. 设置page->_mapcount为-1,即将其从buddy分配器中移除
4. 设置分配到的page的迁移类型
5. 从新划分zone->free_area

这里要重点说下第5点,重新划分zone->free_area。我们只需要分配一个page,但是buddy分配器给了我们1024个pages,有1023个多余的page,这1023个多余的page要重新回到buddy分配器,具体实现就是通过调用exand函数。

expand

源代码:

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

    while (high > low) {
        area--;
        high--;
        size >>= 1;
        VM_BUG_ON_PAGE(bad_range(zone, &page[size]), &page[size]);

        /*
         * Mark as guard pages (or page), that will allow to
         * merge back to allocator when buddy will be freed.
         * Corresponding page table entries will not be touched,
         * pages will stay not present in virtual address space
         */
        if (set_page_guard(zone, &page[size], high, migratetype))
            continue;

        list_add(&page[size].lru, &area->free_list[migratetype]);
        area->nr_free++;
        set_page_order(&page[size], high);
    }
}

代码分析:
这个函数其实很简单,我们先来解释一下参数:
zone : 当前内存域
page : 对应order的起始page
low : 需要分配的pages对应的order
high : 实际分配的pages对应的order
area : 实际分配的pages对应的order的链表

该函数很简单,首先将1024个pages一分为二,后半部分加入order为9的链表,前半部分512个pages继续拆分;将512个pages一分为2,后半部分加入order为8的链表,前半部分256个pages继续拆分;最终order为0,1,2,3,4,5,6,7,8,9对应的链表各连入一个成员,总共是1023个pages,而order为10的链表删除一个成员。

我们假设第二次要分配order为3个pages,则会直接从order为3的链表中获取pages。

扫描二维码关注公众号,回复: 2169373 查看本文章

fallbacks数组

源代码:

static int fallbacks[MIGRATE_TYPES][4] = {
    [MIGRATE_UNMOVABLE]   = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE,   MIGRATE_TYPES },
    [MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE,   MIGRATE_MOVABLE,   MIGRATE_TYPES },
    [MIGRATE_MOVABLE]     = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_TYPES },
#ifdef CONFIG_CMA
    [MIGRATE_CMA]         = { MIGRATE_TYPES }, /* Never used */
#endif
#ifdef CONFIG_MEMORY_ISOLATION
    [MIGRATE_ISOLATE]     = { MIGRATE_TYPES }, /* Never used */
#endif
};

代码分析:
mem_init初始化完成后,所有的free memory都在order为10的area为迁移类型为MIGRATE_MOVABLE的list中。如果现在要分配一个其他类型的pages怎么办?很明显,没有其他办法,只能偷取迁移类型为MIGRATE_MOVABLE的pages。

这个数组定义了当某种迁移类型的链表分配不到需要的pages,就会到其备用迁移类型对应的链表分配。比如当所有的page都在MIGRATE_MOVABLE迁移类型对应的链表上时,如果要分配迁移类型为MIGRATE_UNMOVABLE的pages,则最终会在迁移类型为MIGRATE_MOVABLE的链表分配到pages,说白了,就是偷,那么具体是嗯么偷的呢?这就要看_rmqueue_fallbck函数了。

__rmqueue_fallback

源代码:

static inline struct page *
__rmqueue_fallback(struct zone *zone, unsigned int order, int start_migratetype)
{
    struct free_area *area;
    unsigned int current_order;
    struct page *page;
    int fallback_mt;
    bool can_steal;

    /* Find the largest possible block of pages in the other list */
    for (current_order = MAX_ORDER-1;
                current_order >= order && current_order <= MAX_ORDER-1;
                --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)
            continue;

        page = list_first_entry(&area->free_list[fallback_mt],
                        struct page, lru);
        if (can_steal)
            steal_suitable_fallback(zone, page, start_migratetype);

        /* Remove the page from the freelists */
        area->nr_free--;
        list_del(&page->lru);
        rmv_page_order(page);

        expand(zone, page, order, current_order, area,
                    start_migratetype);
        /*
         * The pcppage_migratetype may differ from pageblock's
         * migratetype depending on the decisions in
         * find_suitable_fallback(). This is OK as long as it does not
         * differ for MIGRATE_CMA pageblocks. Those can be used as
         * fallback only via special __rmqueue_cma_fallback() function
         */
        set_pcppage_migratetype(page, start_migratetype);

        trace_mm_page_alloc_extfrag(page, order, current_order,
            start_migratetype, fallback_mt);

        return page;
    }

    return NULL;
}

代码分析:
为了尽可能少的引入碎片,偷取的时候会尽量偷取大的空闲内存,所以从order最大的free_area尝试偷取;偷取的具体实现由steal_suitable_fallback来完成,偷过来之后正常分配即可。

steal_suitable_fallback会将整个pageblock中一起偷取,该pageblock中还没有分配的memory直接偷过来,并修改该pageblock的迁移类型。当属于该pageblock的page被释放时,会根据所属pageblock的类型决定回到哪种迁移类型链表上。

猜你喜欢

转载自blog.csdn.net/liuhangtiant/article/details/81057602