内存管理-41-highatomic预留内存

基于msm-5.4

一、相关结构体

1. struct zone

struct zone {
    unsigned long nr_reserved_highatomic;
    ...
};

成员介绍:

nr_reserved_highatomic: 记录为高优先级原子分配预留的内存页面数量。


二、赋值逻辑

赋值路径只有两个,一个是对高阶原子分配进行页面预留,另一个是减少高阶原子页面的预留。

1. 增加预留页面

调用路径:

    __alloc_pages_cpuset_fallback //page_alloc.c
    __alloc_pages_may_oom //page_alloc.c
    __alloc_pages_direct_compact //page_alloc.c
    __alloc_pages_direct_reclaim //page_alloc.c
    __alloc_pages_slowpath //page_alloc.c
    __alloc_pages_nodemask //page_alloc.c
        get_page_from_freelist //page_alloc.c
            reserve_highatomic_pageblock //page_alloc.c
                zone->nr_reserved_highatomic += pageblock_nr_pages;


执行逻辑:

1.1 reserve_highatomic_pageblock()

static void reserve_highatomic_pageblock(struct page *page, struct zone *zone,
        unsigned int alloc_order) //page_block.c
{
    int mt;
    unsigned long max_managed, flags;

    /* 将保留的数量限制为 1 个页面块或大约 1% 的区域。检查容易发生竞争,锁外检查无害 */
    max_managed = (zone_managed_pages(zone) / 100) + pageblock_nr_pages;
    if (zone->nr_reserved_highatomic >= max_managed)
        return;

    spin_lock_irqsave(&zone->lock, flags);

    /* 持有zone->lock后再次检查,上面在锁外进行一次检查可以预防卡锁 */
    if (zone->nr_reserved_highatomic >= max_managed)
        goto out_unlock;

    /*
     * 获取参数page所在页面块的迁移特性,如果不是 HIGHATOMIC、ISOLATE、CMA
     * 三种迁移类型,就将page所在页面块设置为高阶原子类型。
     */
    mt = get_pageblock_migratetype(page);
    if (!is_migrate_highatomic(mt) && !is_migrate_isolate(mt) && !is_migrate_cma(mt)) {
        /* 设置页面块的迁移类型为HIGHATOMIC */
        zone->nr_reserved_highatomic += pageblock_nr_pages;
        set_pageblock_migratetype(page, MIGRATE_HIGHATOMIC);
        /* 将page所在的页面块迁移到 HIGHATOMIC 这个迁移类型对应的freelist链表上 */
        move_freepages_block(zone, page, MIGRATE_HIGHATOMIC, NULL);
    }

out_unlock:
    spin_unlock_irqrestore(&zone->lock, flags);
}


1.2 move_freepages_block()

/* reserve_highatomic_pageblock: (zone, page, MIGRATE_HIGHATOMIC, NULL) */
int move_freepages_block(struct zone *zone, struct page *page,
        int migratetype, int *num_movable) //page_alloc.c
{
    unsigned long start_pfn, end_pfn;
    struct page *start_page, *end_page;

    if (num_movable)
        *num_movable = 0;

    /* page所在页面块的start/end pfn和页面块首page结构指针 */
    start_pfn = page_to_pfn(page);
    start_pfn = start_pfn & ~(pageblock_nr_pages-1);
    start_page = pfn_to_page(start_pfn);
    end_page = start_page + pageblock_nr_pages - 1;
    end_pfn = start_pfn + pageblock_nr_pages - 1;

    /* Do not cross zone boundaries */
    /* 若start_pfn不在zone管理的页面之间,则将start_page指向page(而不是页面块的首个page了) */
    if (!zone_spans_pfn(zone, start_pfn))
        start_page = page;
    /* 若end_pfn不在zone管理的页面之间, 则直接返回 */
    if (!zone_spans_pfn(zone, end_pfn))
        return 0;

    /* 将页面块迁移到迁移类型对应的freelist链表上 */
    return move_freepages(zone, start_page, end_page, migratetype, num_movable);
}


1.3 move_freepages()

/*
 * 翻译: 将range内的空闲页面移动到请求类型的空闲列表中。请注意,start_page 和 end_pages 未在页块边界上对齐。
 * 如果需要对齐,请使用 move_freepages_block()
 */
/* reserve_highatomic_pageblock: (zone, start_page, end_page, MIGRATE_HIGHATOMIC, NULL) */
static int move_freepages(struct zone *zone, struct page *start_page, struct page *end_page,
              int migratetype, int *num_movable) //page_alloc.c
{
    struct page *page;
    unsigned int order;
    int pages_moved = 0;

    for (page = start_page; page <= end_page;) {
        /* 检查区间中的每一个页面是否有对应实际的物理内存,是否是valid的 */
        if (!pfn_valid_within(page_to_pfn(page))) {
            page++;
            continue;
        }

        /* 伙伴系统中的空闲页面这个Buddy标志是设置的 */
        if (!PageBuddy(page)) {
            /*
             * 翻译: 我们假设可以隔离并迁移的页面是可移动的。但我们实际上并没有尝试隔离,因为这样做成本太高。
             * 通过参数 *num_movable 返回的是,不是buddy中的页面,但是moveable状态的页面。
             */
            if (num_movable && (PageLRU(page) || __PageMovable(page)))
                (*num_movable)++;

            page++;
            /* 若page不是伙伴系统中内存块的首个页面,这里就continue进行下一轮循环了 */
            continue;
        }

        /* 下面就是page是伙伴系统中空闲页面块的首个页面的逻辑了 */

        /* 确保我们不会无意中更改节点,断言page和zone必须属于同一个node,嵌入式单node恒成立 */
        VM_BUG_ON_PAGE(page_to_nid(page) != zone_to_nid(zone), page);
        VM_BUG_ON_PAGE(page_zone(page) != zone, page); //断言page必须属于这个zone

        order = page_order(page); //page->private 应该只有buddy中的首个页面的private才表示order
        /* 直接调用的是list_move()从原来链表上del下来再add到新链表上去 */
        move_to_free_area(page, &zone->free_area[order], migratetype);
        page += 1 << order;
        pages_moved += 1 << order;
    }

    /* 返回的是成功迁移的页面数量 */
    return pages_moved;
}


2. 减少预留

调用路径:

    __alloc_pages_slowpath //page_alloc.c
        __alloc_pages_direct_reclaim //page_alloc.c 传参: (ac, false)
    __alloc_pages_slowpath //page_alloc.c
        should_reclaim_retry //page_alloc.c 传参: (ac, true)
            unreserve_highatomic_pageblock
                zone->nr_reserved_highatomic -= min(pageblock_nr_pages, zone->nr_reserved_highatomic);


执行逻辑:

2.1 unreserve_highatomic_pageblock()

/*
 * 翻译: 当内存压力导致分配即将失败时使用。这可能会损害高阶分配在内存压力巨大时的可靠性,
 * 但失败的原子分配应该比 OOM 更容易恢复。
 * 如果 @force 为真,则尝试取消保留页面块,即使高原子页面块已耗尽。
 *
 * should_reclaim_retry:(ac, true) 返回迁移到ac->migratetype类型的空闲链表的页面数
 */
static bool unreserve_highatomic_pageblock(const struct alloc_context *ac, bool force)
{
    struct zonelist *zonelist = ac->zonelist;
    unsigned long flags;
    struct zoneref *z;
    struct zone *zone;
    struct page *page;
    int order;
    bool ret;

    /* 遍历系统中所有的zone */
    for_each_zone_zonelist_nodemask(zone, z, zonelist, ac->high_zoneidx, ac->nodemask) {
        /* 除非内存压力真的很高,否则至少保留一个页块,一个页面块也不给highatomic保留了。 */
        if (!force && zone->nr_reserved_highatomic <= pageblock_nr_pages)
            continue;

        spin_lock_irqsave(&zone->lock, flags); //关键部分主要是这个锁保护
        /* 遍历每一个order的HIGHATOMIC链表 */
        for (order = 0; order < MAX_ORDER; order++) {
            struct free_area *area = &(zone->free_area[order]);

            page = get_page_from_free_area(area, MIGRATE_HIGHATOMIC);
            if (!page)
                continue;

            /*
             * 翻译: 在页面释放路径中,migratetype 更改很活跃,因此尽管我们将页面块的迁移类型
             * 从 highatomic 更改为 ac->migratetype,但我们可以在此循环中计算页面块中的多个空
             * 闲页面。因此我们应该调整一次计数。
             *
             * 这里再次确认查询找到的page是 HIGHATOMIC 迁移类型的。
             */
            if (is_migrate_highatomic_page(page)) {
                zone->nr_reserved_highatomic -= min(pageblock_nr_pages, zone->nr_reserved_highatomic);
            }

            /*
             * 将分配出来的页面设置为ac指定的迁移类型,并移动到对应的freelist链表上,若成功移动
             * 了页面(ret!=0), 则返回成功移动的页面数,然后退出遍历。
             */
            set_pageblock_migratetype(page, ac->migratetype);
            ret = move_freepages_block(zone, page, ac->migratetype, NULL);
            if (ret) {
                spin_unlock_irqrestore(&zone->lock, flags);
                return ret;
            }
        }
        spin_unlock_irqrestore(&zone->lock, flags);
    }

    return false;
}


三、总结

1. 为 highatomic 类型分配的预留页面数量是有限制的,最大不能超过 zone->managed_pages/100+4k 个页面,并且在内存紧张时会从 MIGRATE_HIGHATOMIC 类型的freelist链表中释放出来,但会尽量给保留一个pageblock大小的内存页面给highatomic使用。若内存非常紧张时,则一个页面也不会给highatomic保留了,全部会释放出来。

2. 默认没有对 zone->nr_reserved_highatomic 进行任何调试打印。

3. 代码中涉及到的:
PageBuddy(page) 是Linux内核中用于检查一个页面是否处于空闲状态并在伙伴系统中的函数。
PageLRU(page) 宏在Linux内核中用于检查一个页面是否在LRU(Least Recently Used)链表上。

 

posted on 2024-11-09 16:21  Hello-World3  阅读(30)  评论(0编辑  收藏  举报

导航