86|0

55

帖子

1

TA的资源

一粒金砂(中级)

楼主
 

《Linux内核深度解析》-伙伴分配器 [复制链接]

判断伙伴分配器源码
前言
        在内存管理这一章中,有一节伙伴分配器引起了我的阅读兴趣,在之前看过的内存管理代码中,一般都是直接简单粗暴的使用一块区域,分配时指定从前往后分配和从后往前分配。很少见到提升内存分配效率的操作。而这一节开篇就提到了伙伴分配器的效果,就是高效地分配内存。
伙伴分配器原理
        确切的说,伙伴分配器地原理并不复杂,就是把页块按照2的n阶为单位存储至对应阶数的内存列表中,当遇到申请内存时,计算申请内存所需要的阶数,然后从对应阶数的内存表中找到空闲内存块并给出。如果当前阶数找不到内存块,则向上找更高阶的内存块,使用逐级二分法的方式分出所需阶数的内存块,而未使用到的部分添加至对应阶数的内存块列表中。
        而之所以叫伙伴分配器,我的理解是,内存释放时需要执行合并操作,这个时候要判断释放的内存和对应阶数的空闲内存表中的内存块是否是连在一起的,如果是连在一起,则合并成更高阶的内存块并添加至更高阶的内存表中。
        为了实现这么个功能,需要对伙伴分配器进行数据定义,其定义如下:
// 最大阶数定义
#ifndef CONFIG_FORCE_MAX_ZONEORDER
#define MAX_ORDER 11
#else
#define MAX_ORDER CONFIG_FORCE_MAX_ZONEORDER
#endif
enum migratetype {
    MIGRATE_UNMOVABLE,
    MIGRATE_MOVABLE,
    MIGRATE_RECLAIMABLE,
    MIGRATE_PCPTYPES,
    MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,
#ifdef CONFIG_CMA
    MIGRATE_CMA,
#endif
#ifdef CONFIG_MEMORY_ISOLATION
    MIGRATE_ISOLATE,
#endif
    MIGRATE_TYPES
};
struct free_area {
    struct list_head    free_list[MIGRATE_TYPES]; // 不同类型的内存块列表
    unsigned long       nr_free;
};
struct zone {
...
    /* 不同阶数的内存数据表 */
    struct free_area    free_area[MAX_ORDER];
...
} ____cacheline_internodealigned_in_smp;

主要入口函数

alloc_pages(gfp_t gfp_mask, unsigned int order)
void free_pages(unsigned long addr, unsigned int order)

函数入口分析

        从入口表中可以发现,申请时需要提供一个gfp_mask,而这个mask,其实大致可以猜出来,应该对应的就是enum migratetype中的各种类型。虽然功能大致是那样的,但实际输入参数并不是这么写的,其写法如下:
#define ___GFP_DMA      0x01u
#define ___GFP_HIGHMEM      0x02u
#define ___GFP_DMA32        0x04u
#define ___GFP_MOVABLE      0x08u
#define __GFP_DMA   ((__force gfp_t)___GFP_DMA)
#define __GFP_HIGHMEM   ((__force gfp_t)___GFP_HIGHMEM)
#define __GFP_DMA32 ((__force gfp_t)___GFP_DMA32)
#define __GFP_MOVABLE   ((__force gfp_t)___GFP_MOVABLE)  /* ZONE_MOVABLE 可以和其他三种共存,但其他三种不能共存 */
#define GFP_ZONEMASK    (__GFP_DMA|__GFP_HIGHMEM|__GFP_DMA32|__GFP_MOVABLE)

alloc_pages

//gfp.h
// 如果是非一致性内存管理架构,则alloc_pages相当于alloc_pages_current函数
// 如果是一致性内存管理架构,则alloc_pages(gfp_mask, order)的实现对应于alloc_pages_node(numa_node_id(), gfp_mask, order)
// 其中numa_node_id()得到的是处理器的编号
#ifdef CONFIG_NUMA
extern struct page *alloc_pages_current(gfp_t gfp_mask, unsigned order);
static inline struct page *
alloc_pages(gfp_t gfp_mask, unsigned int order)
{
    return alloc_pages_current(gfp_mask, order);
}
extern struct page *alloc_pages_vma(gfp_t gfp_mask, int order,
            struct vm_area_struct *vma, unsigned long addr,
            int node, bool hugepage);
#define alloc_hugepage_vma(gfp_mask, vma, addr, order)  \
    alloc_pages_vma(gfp_mask, order, vma, addr, numa_node_id(), true)
#else
#define alloc_pages(gfp_mask, order) \
        alloc_pages_node(numa_node_id(), gfp_mask, order)
#define alloc_pages_vma(gfp_mask, order, vma, addr, node, false)\
    alloc_pages(gfp_mask, order)
#define alloc_hugepage_vma(gfp_mask, vma, addr, order)  \
    alloc_pages(gfp_mask, order)
#endif
由于一致性内存处理架构,从数据结构上看,个人认为会更加简单些,因此看实现就从alloc_pages_node开始往下看
static inline struct page *
__alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
                        struct alloc_context *ac)
{
    bool can_direct_reclaim = gfp_mask & __GFP_DIRECT_RECLAIM;
    const bool costly_order = order > PAGE_ALLOC_COSTLY_ORDER;
    struct page *page = NULL;
    unsigned int alloc_flags;
    unsigned long did_some_progress;
    enum compact_priority compact_priority;
    enum compact_result compact_result;
    int compaction_retries;
    int no_progress_loops;
    unsigned int cpuset_mems_cookie;
    int reserve_flags;
    /*
     * 确保不会同时设置 __GFP_ATOMIC 和 __GFP_DIRECT_RECLAIM,因为这是无效的组合。
     */
    if (WARN_ON_ONCE((gfp_mask & (__GFP_ATOMIC|__GFP_DIRECT_RECLAIM)) ==
                (__GFP_ATOMIC|__GFP_DIRECT_RECLAIM)))
        gfp_mask &= ~__GFP_ATOMIC;
retry_cpuset:
    compaction_retries = 0;
    no_progress_loops = 0;
    compact_priority = DEF_COMPACT_PRIORITY;
    cpuset_mems_cookie = read_mems_allowed_begin();
    /*
     * 将 gfp_mask 转换为 alloc_flags,用于后续的分配操作。
     */
    alloc_flags = gfp_to_alloc_flags(gfp_mask);
    /*
     * 重新计算 zonelist 迭代器起点,确保从正确的节点列表开始迭代。
     */
    ac->preferred_zoneref = first_zones_zonelist(ac->zonelist,
                    ac->high_zoneidx, ac->nodemask);
    if (!ac->preferred_zoneref->zone)
        goto nopage;
/*唤醒页回收线程*/
    if (gfp_mask & __GFP_KSWAPD_RECLAIM)
        wake_all_kswapds(order, ac);
    /*
     * 获取最低水线分配页。
     */
    page = get_page_from_freelist(gfp_mask, order, alloc_flags, ac);
    if (page)
        goto got_pg;
    /*
     * 这个对申请阶数大于0,如果满足以下3个条件。
* 1. 允许直接回收页
* 2. 申请阶数大于3,或者指定迁移类型不是可移动类型。
* 3. 调用者没有承诺“给我少量紧急保留内存使用,我可以释放更多的内存”
* 那么执行异步模式的内存碎片整理
     */
    if (can_direct_reclaim &&
            (costly_order ||
               (order > 0 && ac->migratetype != MIGRATE_MOVABLE))
            && !gfp_pfmemalloc_allowed(gfp_mask)) {
// 执行异步的内存碎片整理
        page = __alloc_pages_direct_compact(gfp_mask, order,
                        alloc_flags, ac,
                        INIT_COMPACT_PRIORITY,
                        &compact_result);
        if (page)
            goto got_pg;
// 如果申请阶数大于3,且调用者要求不要重试
        if (costly_order && (gfp_mask & __GFP_NORETRY)) {
// 同步模式的内存碎片整理失败了,所以内存碎片整理被延迟执行
// 没必要继续尝试分配
            if (compact_result == COMPACT_DEFERRED)
                goto nopage;
// 同步模式代价太大,继续使用异步模式的内存碎片整理
            compact_priority = INIT_COMPACT_PRIORITY;
        }
    }
retry:
// 确保回收线程在循环时不会被意外地睡眠
    if (gfp_mask & __GFP_KSWAPD_RECLAIM)
        wake_all_kswapds(order, ac);
// 入股调用者承诺“给我少量紧急保留内存使用,我可以释放更多地内存”,则忽略水线
    reserve_flags = __gfp_pfmemalloc_flags(gfp_mask);
    if (reserve_flags)
        alloc_flags = reserve_flags;
// 如果调用者没有要求使用cpuset,或者要求忽略水线,那么重新获取区域列表
    if (!(alloc_flags & ALLOC_CPUSET) || reserve_flags) {
        ac->preferred_zoneref = first_zones_zonelist(ac->zonelist,
                    ac->high_zoneidx, ac->nodemask);
    }
    /* 尝试使用可能已调整的区域列表和alloc_flags */
    page = get_page_from_freelist(gfp_mask, order, alloc_flags, ac);
    if (page)
        goto got_pg;
// 调用者不愿意等待,不允许直接回收页,此时放弃
    if (!can_direct_reclaim)
        goto nopage;
// 直接回收页的时候给进程设置了PR_MEMALLOC标志位,在直接回收页的过程中可能申请页,
// 为了防止直接回收递归,这里发现进程设置了PR_MEMALLOC标志位,则立即放弃
    if (current->flags & PF_MEMALLOC)
        goto nopage;
    /* 如果允许直接回收,则尝试回收内存 */
    page = __alloc_pages_direct_reclaim(gfp_mask, order, alloc_flags, ac,
                            &did_some_progress);
    if (page)
        goto got_pg;
    /* 针对阶数大于0,执行同步模式的内存碎片整理 */
    page = __alloc_pages_direct_compact(gfp_mask, order, alloc_flags, ac,
                    compact_priority, &compact_result);
    if (page)
        goto got_pg;
// 如果使用者要求不要重试,则放弃
    if (gfp_mask & __GFP_NORETRY)
        goto nopage;
// 如果申请阶数大于3,并且调用者没有要求重试,那么放弃
    if (costly_order && !(gfp_mask & __GFP_RETRY_MAYFAIL))
        goto nopage;
// 检查是否有必要重新尝试回收页
    if (should_reclaim_retry(gfp_mask, order, ac, alloc_flags,
                 did_some_progress > 0, &no_progress_loops))
        goto retry;
    /*
     * 根据回收进度判断是否需要重试。
     */
    if (did_some_progress > 0 &&
            should_compact_retry(ac, order, alloc_flags,
                compact_result, &compact_priority,
                &compaction_retries))
        goto retry;
    /* 在我们开始OOM杀戮之前,处理可能的cpuset更新 */
    if (check_retry_cpuset(cpuset_mems_cookie, ac))
        goto retry_cpuset;
    /* 如果所有尝试都失败,则启动 OOM 杀手。 */
    page = __alloc_pages_may_oom(gfp_mask, order, ac, &did_some_progress);
    if (page)
        goto got_pg;
    /* 避免无限制的循环 */
    if (tsk_is_oom_victim(current) &&
        (alloc_flags == ALLOC_OOM ||
         (gfp_mask & __GFP_NOMEMALLOC)))
        goto nopage;
    /* 只要OOM杀手正在取得进展,就重试 */
    if (did_some_progress) {
        no_progress_loops = 0;
        goto retry;
    }
/*
* 如果最终失败且设置了 __GFP_NOFAIL,则继续重试。
* 否则,发出警告并返回 NULL。
*/
nopage:
    if (check_retry_cpuset(cpuset_mems_cookie, ac))
        goto retry_cpuset;
    if (gfp_mask & __GFP_NOFAIL) {
        // 特殊处理 __GFP_NOFAIL 请求
        if (WARN_ON_ONCE(!can_direct_reclaim))
            goto fail;
        WARN_ON_ONCE(current->flags & PF_MEMALLOC);
        WARN_ON_ONCE(order > PAGE_ALLOC_COSTLY_ORDER);
// 先使用标志位ALLOC_HARDER|ALLOC_CPUSET尝试分配,
// 如果分配失败,那么使用标志位ALLOC_HARDER尝试分配
        page = __alloc_pages_cpuset_fallback(gfp_mask, order, ALLOC_HARDER, ac);
        if (page)
            goto got_pg;
        cond_resched();
        goto retry;
    }
fail:
    warn_alloc(gfp_mask, ac->nodemask,
            "page allocation failure: order:%u", order);
got_pg:
    return page;
}
static struct page *
get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags,
                        const struct alloc_context *ac)
{
    struct zoneref *z = ac->preferred_zoneref;
    struct zone *zone;
    struct pglist_data *last_pgdat_dirty_limit = NULL;
    /*
     * 扫描区域列表,寻找具有足够空闲空间的区域。
     */
    for_next_zone_zonelist_nodemask(zone, z, ac->zonelist, ac->high_zoneidx,
                                ac->nodemask) {
        struct page *page;
        unsigned long mark;
        if (cpusets_enabled() &&
            (alloc_flags & ALLOC_CPUSET) &&
            !__cpuset_zone_allowed(zone, gfp_mask))
                continue;
        if (ac->spread_dirty_pages) {
            if (last_pgdat_dirty_limit == zone->zone_pgdat)
                continue;
            if (!node_dirty_ok(zone->zone_pgdat)) {
                last_pgdat_dirty_limit = zone->zone_pgdat;
                continue;
            }
        }
        mark = zone->watermark[alloc_flags & ALLOC_WMARK_MASK];
        if (!zone_watermark_fast(zone, order, mark,
                       ac_classzone_idx(ac), alloc_flags)) {
            int ret;
            /* Checked here to keep the fast path fast */
            BUILD_BUG_ON(ALLOC_NO_WATERMARKS < NR_WMARK);
            if (alloc_flags & ALLOC_NO_WATERMARKS)
                goto try_this_zone;
            if (node_reclaim_mode == 0 ||
                !zone_allows_reclaim(ac->preferred_zoneref->zone, zone))
                continue;
            ret = node_reclaim(zone->zone_pgdat, gfp_mask, order);
            switch (ret) {
            case NODE_RECLAIM_NOSCAN:
                /* 未扫描 */
                continue;
            case NODE_RECLAIM_FULL:
                /* 扫描过,但不确定 */
                continue;
            default:
                /* 回收足够了,重新检查水位线 */
                if (zone_watermark_ok(zone, order, mark,
                        ac_classzone_idx(ac), alloc_flags))
                    goto try_this_zone;
                continue;
            }
        }
try_this_zone:
        page = rmqueue(ac->preferred_zoneref->zone, zone, order,
                gfp_mask, alloc_flags, ac->migratetype);
        if (page) {
            prep_new_page(page, order, gfp_mask, alloc_flags);
/*
* 如果这是高阶原子分配,则检查是否应为将来保留该页块
*/
            if (unlikely(order && (alloc_flags & ALLOC_HARDER)))
                reserve_highatomic_pageblock(page, zone, order);
            return page;
        }
    }
    return NULL;
}
struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order, int preferred_nid,
                            nodemask_t *nodemask)
{
    struct page *page;
    unsigned int alloc_flags = ALLOC_WMARK_LOW;
    gfp_t alloc_mask; /* The gfp_t that was actually used for allocation */
    struct alloc_context ac = { };
    /*
     * 如果所申请内存的阶数大于最大阶数,则直接提示异常并返回
     */
    if (unlikely(order >= MAX_ORDER)) {
        WARN_ON_ONCE(!(gfp_mask & __GFP_NOWARN));
        return NULL;
    }
    gfp_mask &= gfp_allowed_mask;
    alloc_mask = gfp_mask;
    if (!prepare_alloc_pages(gfp_mask, order, preferred_nid, nodemask, &ac, &alloc_mask, &alloc_flags))
        return NULL;
    finalise_ac(gfp_mask, order, &ac);
    /* 快速获取内存块 */
    page = get_page_from_freelist(alloc_mask, order, alloc_flags, &ac);
    if (likely(page))
        goto out;
/*
* 应用范围分配约束。这主要与 GFP_NOFS 和 GFP_NOIO 有关,对于来自特定上下文
* 的所有分配请求,必须继承 GFP_NOIO,该上下文已由
* memalloc_no{fs,io}_{save,restore} 标记。
*/
    alloc_mask = current_gfp_context(gfp_mask);
    ac.spread_dirty_pages = false;
/*
* 如果原始节点掩码可能被 &cpuset_current_mems_allowed 替换,则恢复原始节点
* 掩码以优化快速路径尝试。
*/
    if (unlikely(ac.nodemask != nodemask))
        ac.nodemask = nodemask;
/*慢速获取内存块*/
    page = __alloc_pages_slowpath(alloc_mask, order, &ac);
out:
    if (memcg_kmem_enabled() && (gfp_mask & __GFP_ACCOUNT) && page &&
        unlikely(memcg_kmem_charge(page, gfp_mask, order) != 0)) {
        __free_pages(page, order);
        page = NULL;
    }
    trace_mm_page_alloc(page, order, alloc_mask, ac.migratetype);
    return page;
}
// 前面的函数一层一层套,但实际上都是一些条件检测和修复,真正的实现函数是__alloc_pages_nodemask
static inline struct page *
__alloc_pages(gfp_t gfp_mask, unsigned int order, int preferred_nid)
{
    return __alloc_pages_nodemask(gfp_mask, order, preferred_nid, NULL);
}
static inline struct page *
__alloc_pages_node(int nid, gfp_t gfp_mask, unsigned int order)
{
    VM_BUG_ON(nid < 0 || nid >= MAX_NUMNODES);
    VM_WARN_ON(!node_online(nid));
    return __alloc_pages(gfp_mask, order, nid);
}
static inline struct page *alloc_pages_node(int nid, gfp_t gfp_mask,
                        unsigned int order)
{
    if (nid == NUMA_NO_NODE)
        nid = numa_mem_id();
    return __alloc_pages_node(nid, gfp_mask, order);
}

free_pages

static inline void __free_one_page(struct page *page,
        unsigned long pfn,
        struct zone *zone, unsigned int order,
        int migratetype)
{
    unsigned long combined_pfn;
    unsigned long uninitialized_var(buddy_pfn);
    struct page *buddy;
    unsigned int max_order;
    max_order = min_t(unsigned int, MAX_ORDER, pageblock_order + 1);
    VM_BUG_ON(!zone_is_initialized(zone));
    VM_BUG_ON_PAGE(page->flags & PAGE_FLAGS_CHECK_AT_PREP, page);
    VM_BUG_ON(migratetype == -1);
    if (likely(!is_migrate_isolate(migratetype)))
        __mod_zone_freepage_state(zone, 1 << order, migratetype);
    VM_BUG_ON_PAGE(pfn & ((1 << order) - 1), page);
    VM_BUG_ON_PAGE(bad_range(zone, page), page);
continue_merging:
// 如果伙伴是空闲的,则和伙伴合并,重复该动作知道阶数等于(max_forder-1)
    while (order < max_order - 1) {
        buddy_pfn = __find_buddy_pfn(pfn, order);
        buddy = page + (buddy_pfn - pfn);
// buddy的页帧号无效,则说明该步骤处理完毕
        if (!pfn_valid_within(buddy_pfn))
            goto done_merging;
// 判断page和buddy是否是伙伴,是的话就继续合并,否则跳转至合并完成处理
        if (!page_is_buddy(page, buddy, order))
            goto done_merging;
/*
* 我们的伙伴空闲了或者它是 CONFIG_DEBUG_PAGEALLOC 保护页,与其合并并上移一级。
*/
        if (page_is_guard(buddy)) {
            clear_page_guard(zone, buddy, order, migratetype);
        } else { // 伙伴是空闲的,则把伙伴从空闲列表中删除
            list_del(&buddy->lru);
            zone->free_area[order].nr_free--;
            rmv_page_order(buddy);
        }
        combined_pfn = buddy_pfn & pfn;
        page = page + (combined_pfn - pfn);
        pfn = combined_pfn;
        order++;
    }
    if (max_order < MAX_ORDER) {
/* 如果运行到这里,则意味着 order >= pageblock_order。
* 防止隔离页面块和正常页面块上的空闲页面合并
*/
        if (unlikely(has_isolate_pageblock(zone))) {
            int buddy_mt;
            buddy_pfn = __find_buddy_pfn(pfn, order);
            buddy = page + (buddy_pfn - pfn);
            buddy_mt = get_pageblock_migratetype(buddy);
            if (migratetype != buddy_mt
                    && (is_migrate_isolate(migratetype) ||
                        is_migrate_isolate(buddy_mt)))
                goto done_merging;
        }
// 如果两个都是隔离类型的页块,或者都是其他类型的页块,则继续合并
        max_order++;
        goto continue_merging;
    }
done_merging:
    set_page_order(page, order);
/*
* 如果最后的阶数order小于MAX_ORDER-2,则检查order+1是否空闲。
* 若空闲,那么order阶的伙伴可能正在被释放,很快就可以合并成order+2阶的页块。
* 为了防止当前页块被很快的分配出去,因此将该页块添加至页尾
*/
    if ((order < MAX_ORDER-2) && pfn_valid_within(buddy_pfn)) {
        struct page *higher_page, *higher_buddy;
        combined_pfn = buddy_pfn & pfn;
        higher_page = page + (combined_pfn - pfn);
        buddy_pfn = __find_buddy_pfn(combined_pfn, order + 1);
        higher_buddy = higher_page + (buddy_pfn - combined_pfn);
        if (pfn_valid_within(buddy_pfn) &&
            page_is_buddy(higher_page, higher_buddy, order + 1)) {
            list_add_tail(&page->lru,
                &zone->free_area[order].free_list[migratetype]);
            goto out;
        }
    }
/*如果已经到达MAX_ORDR-2阶了,直接添加至lru首部*/
    list_add(&page->lru, &zone->free_area[order].free_list[migratetype]);
out:
    zone->free_area[order].nr_free++;
}
static void free_pcppages_bulk(struct zone *zone, int count,
                    struct per_cpu_pages *pcp)
{
    int migratetype = 0;
    int batch_free = 0;
    bool isolated_pageblocks;
    spin_lock(&zone->lock);
    isolated_pageblocks = has_isolate_pageblock(zone);
    while (count) {
        struct page *page;
        struct list_head *list;
        do {
            batch_free++;
            if (++migratetype == MIGRATE_PCPTYPES)
                migratetype = 0;
            list = &pcp->lists[migratetype];
        } while (list_empty(list));
        if (batch_free == MIGRATE_PCPTYPES)
            batch_free = count;
        do {
            int mt;
            page = list_last_entry(list, struct page, lru);
            list_del(&page->lru);
            mt = get_pcppage_migratetype(page);
            VM_BUG_ON_PAGE(is_migrate_isolate(mt), page);
            if (unlikely(isolated_pageblocks))
                mt = get_pageblock_migratetype(page);
            if (bulkfree_pcp_prepare(page))
                continue;
// 可以发现,其实bulk释放,本质上还是一个一个的释放,
// 只是不是立即释放,而是等链表满了再一起释放
            __free_one_page(page, page_to_pfn(page), zone, 0, mt);
            trace_mm_page_pcpu_drain(page, 0, mt);
        } while (--count && --batch_free && !list_empty(list));
    }
    spin_unlock(&zone->lock);
}
static void free_one_page(struct zone *zone,
                struct page *page, unsigned long pfn,
                unsigned int order,
                int migratetype)
{
    spin_lock(&zone->lock);
    if (unlikely(has_isolate_pageblock(zone) ||
        is_migrate_isolate(migratetype))) {
        migratetype = get_pfnblock_migratetype(page, pfn);
    }
    __free_one_page(page, pfn, zone, order, migratetype);
    spin_unlock(&zone->lock);
}
static void __free_pages_ok(struct page *page, unsigned int order)
{
    unsigned long flags;
    int migratetype;
    unsigned long pfn = page_to_pfn(page);
    if (!free_pages_prepare(page, order, true))
        return;
    migratetype = get_pfnblock_migratetype(page, pfn); // 获取内存迁移类型
    local_irq_save(flags);
    __count_vm_events(PGFREE, 1 << order);
    free_one_page(page_zone(page), page, pfn, order, migratetype);
    local_irq_restore(flags);
}
void free_hot_cold_page(struct page *page, bool cold)
{
    struct zone *zone = page_zone(page);
    struct per_cpu_pages *pcp;
    unsigned long flags;
    unsigned long pfn = page_to_pfn(page);
    int migratetype;
// 准备释放页面,如果准备失败,则返回
    if (!free_pcp_prepare(page))
        return;
// 获取和设置页块迁移类型,并增加一次释放内存计数
    migratetype = get_pfnblock_migratetype(page, pfn);
    set_pcppage_migratetype(page, migratetype);
    local_irq_save(flags);
    __count_vm_event(PGFREE);
// 处理迁移类型,如果是隔离迁移类型,则调用释放page的动作,否则返回
    if (migratetype >= MIGRATE_PCPTYPES) {
        if (unlikely(is_migrate_isolate(migratetype))) {
            free_one_page(zone, page, pfn, 0, migratetype);
            goto out;
        }
        migratetype = MIGRATE_MOVABLE;
    }
// 添加页面到PCP列表,如果是缓存热页,则添加至首部,否则添加至尾部,从__free_pages传参可以了解到,这个是添加到首部的
    pcp = &this_cpu_ptr(zone->pageset)->pcp;
    if (!cold)
        list_add(&page->lru, &pcp->lists[migratetype]);
    else
        list_add_tail(&page->lru, &pcp->lists[migratetype]);
    pcp->count++;
//如果缓存热页水位超过预定值,则执行批量返还给伙伴分配器的动作
    if (pcp->count >= pcp->high) {
        unsigned long batch = READ_ONCE(pcp->batch);
        free_pcppages_bulk(zone, batch, pcp);
        pcp->count -= batch;
    }
out:
    local_irq_restore(flags);
}
void __free_pages(struct page *page, unsigned int order)
{
// 如果释放后没有谁使用该page,则执行释放page操作,核心为判断page->_refcount变量,为0则说明无人使用
    if (put_page_testzero(page)) {
        if (order == 0)
            free_hot_cold_page(page, false); // 如果当前阶数为0阶,则运行此入口,具体功能为将数据添加置缓存热页
        else
            __free_pages_ok(page, order); // 如果当前阶数非0,则运行此入口
    }
}
EXPORT_SYMBOL(__free_pages);
void free_pages(unsigned long addr, unsigned int order)
{
    if (addr != 0) {
        VM_BUG_ON(!virt_addr_valid((void *)addr));
        __free_pages(virt_to_page((void *)addr), order); // pages管理使用的是实际的物理地址,非虚拟地址
    }
}
EXPORT_SYMBOL(free_pages);

总结

        光看书,会有一种错觉,感觉书上伙伴分配器的内容有些跳跃,讲完这又跳到那的。最明显的地方是内存申请这块,没从alloc_pages这个入口开始讲,而是直接跳到__alloc_pages_nodemask,看着感觉有些无厘头。而前面提到的分配标志,也并未说明是在什么场景下用的。这就需要结合源码去阅读了。而事实上,照着书再去阅读源码时,就会发现,这本书和源码刚好是个互补的关系,这点在慢速申请内存函数__alloc_pages_slowpath上体现的尤为清晰,光看这个函数的实现,就会发现里面处理的任务比较多,乍一看不太好厘清,而书籍上刚好把这块代码完全厘清,从软件整体执行思路到每行代码实际功能的讲解,都写得十分清晰,这就能让我们更好地理解源码实现思路了。

点赞 关注

回复
举报
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/8 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表