分享

linux内存管理分析【七】

 老匹夫 2014-09-12

retry_cpuset:

        cpuset_mems_cookie = get_mems_allowed();

        if (pol->mode == MPOL_INTERLEAVE)

函数interleave_nodes根据分配策略返回内存分配的结点

                page = alloc_page_interleave(gfp, order, interleave_nodes(pol));

        else

在内存域备用列表中分配阶为order的页块

                page = __alloc_pages_nodemask(gfp, order,

                                policy_zonelist(gfp, pol, numa_node_id()),

                                policy_nodemask(gfp, pol));

如果分配不成功就重新尝试分配

        if (unlikely(!put_mems_allowed(cpuset_mems_cookie) && !page))

                goto retry_cpuset;

        return page;

}

【alloc_pages--->alloc_pages_current--->__alloc_pages_nodemask】

mm/page_alloc.c

struct page *   __alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,

                        struct zonelist *zonelist, nodemask_t *nodemask)

{                   

enum zone_type high_zoneidx = gfp_zone(gfp_mask);            

......

retry_cpuset:

high_zoneidx是参数中指定内存域,函数first_zones_zonelist就是找到该内存域preferred_zone,但在这个内存域中不一定能成功分配指定内存。程序扫描备用内存域列表zonelist,最后成功分配内存的内存域甚至可能与preferred_zone根本不在一个节点中。程序会根据这些情况来更新NUMA_HIT,NUMA_MISS,NUMA_FOREIGN这几个统计量。

        first_zones_zonelist(zonelist, high_zoneidx,nodemask ? : 

&cpuset_current_mems_allowed,&preferred_zone);

        if (!preferred_zone)

                goto out;

考虑内存限制,指定迁移类型,的情况下快速分配内存

        page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, nodemask, order, zonelist, high_zoneidx, ALLOC_WMARK_LOW|ALLOC_CPUSET,                        preferred_zone, migratetype);

        if (unlikely(!page))

如果分配不成功就降低限制值,启动内存回收机制,然后分配内存

                page = __alloc_pages_slowpath(gfp_mask, order,

                                zonelist, high_zoneidx, nodemask,

                                preferred_zone, migratetype);

        trace_mm_page_alloc(page, order, gfp_mask, migratetype);

out:

        if (unlikely(!put_mems_allowed(cpuset_mems_cookie) && !page))

                goto retry_cpuset;

        return page;

}

快速内存分配

【alloc_pages--->alloc_pages_current--->__alloc_pages_nodemask--->get_page_from_freelist】

mm/page_alloc.c

static struct page *

get_page_from_freelist(gfp_t gfp_mask, nodemask_t *nodemask, unsigned int order,

                struct zonelist *zonelist, int high_zoneidx, int alloc_flags,

                struct zone *preferred_zone, int migratetype)

{

......

获取preferred_zone的内存区域类型,如ZONE_NORMAL……

        classzone_idx = zone_idx(preferred_zone);

zonelist_scan:

遍历类型值小于high_zoneidx的所欲内存域

        for_each_zone_zonelist_nodemask(zone, z, zonelist,

                                                high_zoneidx, nodemask) {

第一次扫描zonelist时zlc_active 为0,表示不需要判断内存域Z是否有可分配内存。

                if (NUMA_BUILD && zlc_active &&

                        !zlc_zone_worth_trying(zonelist, z, allowednodes))

                                continue;

如果设置了标志ALLOC_CPUSET,就判断当前CPU是否允许在内存域zone所在结点中分配内存。

                if ((alloc_flags & ALLOC_CPUSET) &&

                        !cpuset_zone_allowed_softwall(zone, gfp_mask))

                                continue;

如果设置了低水印标志,并且为写分配页,就检查内存域的赃页是否超过其限制值,如果赃页过多就设置该内存域得满标志

                if ((alloc_flags & ALLOC_WMARK_LOW) &&

                    (gfp_mask & __GFP_WRITE) && !zone_dirty_ok(zone))

                        goto this_zone_full;

                BUILD_BUG_ON(ALLOC_NO_WATERMARKS < NR_WMARK);

判断是否检测水印值

                if (!(alloc_flags & ALLOC_NO_WATERMARKS)) {

                        unsigned long mark;

                        int ret;

数组zone->watermark中记录了该内存域高、中、低三种水印值,下面获取指定所采用的水印值。

                        mark = zone->watermark[alloc_flags & ALLOC_WMARK_MASK];

检测内存域中空闲页是否符合水印限制

                        if (zone_watermark_ok(zone, order, mark,

                                    classzone_idx, alloc_flags))

                                goto try_this_zone;

指定内存域中内存不足,准备到其他内存域中分配内存

                        if (NUMA_BUILD && !did_zlc_setup && nr_online_nodes > 1) {

                                allowednodes = zlc_setup(zonelist, alloc_flags);

                                zlc_active = 1;

                                did_zlc_setup = 1;

                        }

                        if (zone_reclaim_mode == 0)

                                goto this_zone_full;

检查当前CPU是否可以在内存域Z所在结点中分配内存,和该内存域是否还有已满。

                        if (NUMA_BUILD && zlc_active &&

                                !zlc_zone_worth_trying(zonelist, z, allowednodes))

                                continue;

启用内存回收机制

                        ret = zone_reclaim(zone, gfp_mask, order);

                        switch (ret) {

                        case ZONE_RECLAIM_NOSCAN:

                                continue;没有进行回收扫描

                        case ZONE_RECLAIM_FULL:

                                continue;扫描了但是没有可回收的页

                        default:回收了部分页,查看是否可以满足分配要求

                                if (!zone_watermark_ok(zone, order, mark,

                                                classzone_idx, alloc_flags))

                                        goto this_zone_full;

                        }

                }

try_this_zone:

从zone中分配order阶的页帧

                page = buffered_rmqueue(preferred_zone, zone, order,

                                                gfp_mask, migratetype);

                if (page)

                        break;

this_zone_full:

                if (NUMA_BUILD)将内存域z标记为满

                        zlc_mark_zone_full(zonelist, z);

        }

        if (unlikely(NUMA_BUILD && page == NULL && zlc_active)) {

                zlc_active = 0;

                goto zonelist_scan;

        }

        return page;

}

【alloc_pages--->alloc_pages_current--->__alloc_pages_nodemask--->get_page_from_freelist--->buffered_rmqueue】

mm/page_alloc.c

static inline struct page *buffered_rmqueue(struct zone *preferred_zone,

                        struct zone *zone, int order, gfp_t gfp_flags,

                        int migratetype)

{

        unsigned long flags;

        struct page *page;

        int cold = !!(gfp_flags & __GFP_COLD);

again:

        if (likely(order == 0)) {

                struct per_cpu_pages *pcp;

                struct list_head *list;

                local_irq_save(flags);

获取每CPU缓存管理结构

                pcp = &this_cpu_ptr(zone->pageset)->pcp;

获取每CPU缓存中指定迁移类型的链表头

                list = &pcp->lists[migratetype];

如果指定迁移类型的链表中没有空闲页帧,就从伙伴系统中分配一批(pcp->batch)页帧到每CPU缓存中来。

                if (list_empty(list)) {

                        pcp->count += rmqueue_bulk(zone, 0,

                                        pcp->batch, list,

                                        migratetype, cold);

                        if (unlikely(list_empty(list)))

                                goto failed;

                }

如果分配的是热页就从链表头取页帧,如果分配的是冷页就从链表尾取页帧 

               if (cold)

                        page = list_entry(list->prev, struct page, lru);

                else

                        page = list_entry(list->next, struct page, lru);

将页帧从每CPU缓存中删除

                list_del(&page->lru);

                pcp->count--;减少每CPU缓存计数

        } else {

                if (unlikely(gfp_flags & __GFP_NOFAIL)) {

                        WARN_ON_ONCE(order > 1);

                }

如果分配的是多页就直接从伙伴系统中分配页帧

                spin_lock_irqsave(&zone->lock, flags);

                page = __rmqueue(zone, order, migratetype);

                spin_unlock(&zone->lock);

                if (!page)

                        goto failed;

                __mod_zone_page_state(zone, NR_FREE_PAGES, -(1 << order));

        }

        __count_zone_vm_events(PGALLOC, zone, 1 << order);

更新统计量

        zone_statistics(preferred_zone, zone, gfp_flags);

......

failed:

        local_irq_restore(flags);

        return NULL;

}

【alloc_pages--->alloc_pages_current--->__alloc_pages_nodemask--->get_page_from_freelist--->buffered_rmqueue--->rmqueue_bulk】

static int rmqueue_bulk(struct zone *zone, unsigned int order,

                        unsigned long count, struct list_head *list,

                        int migratetype, int cold)

{

        int mt = migratetype, i;

        spin_lock(&zone->lock);

从伙伴系统中循环分配指定的页数

        for (i = 0; i < count; ++i) {

从伙伴系统中分配指定迁移类型的页

                struct page *page = __rmqueue(zone, order, migratetype);

                if (unlikely(page == NULL))

                        break;

如果分配热页就将分配到的页链接到链表的头部,如果是冷页就将其链接到链表尾

                if (likely(cold == 0))

                        list_add(&page->lru, list);

                else

                        list_add_tail(&page->lru, list);

                if (IS_ENABLED(CONFIG_CMA)) {

获取页的迁移类型

                        mt = get_pageblock_migratetype(page);

                        if (!is_migrate_cma(mt) && mt != MIGRATE_ISOLATE)

                                mt = migratetype;

                }

设置页的迁移类型

                set_page_private(page, mt);

                list = &page->lru;

        }

更新状态统计信息

        __mod_zone_page_state(zone, NR_FREE_PAGES, -(i << order));

        spin_unlock(&zone->lock);

        return i;

}

【alloc_pages--->alloc_pages_current--->__alloc_pages_nodemask--->get_page_from_freelist--->buffered_rmqueue--->rmqueue_bulk--->__rmqueue】

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

{

        struct page *page;

retry_reserve:

在内存域zone的空闲列表中,扫描order及以上的各阶,分配迁移类型为migratetype阶为order的页。

        page = __rmqueue_smallest(zone, order, migratetype);

如果没有分配到指定的页,就调用函数__rmqueue_fallback从迁移类型备用列表中分配满足要求的页

        if (unlikely(!page) && migratetype != MIGRATE_RESERVE) {

                page = __rmqueue_fallback(zone, order, migratetype);

                if (!page) {

                        migratetype = MIGRATE_RESERVE;

                        goto retry_reserve;

                }

        }

        trace_mm_page_alloc_zone_locked(page, order, migratetype);

        return page;

}

【alloc_pages--->alloc_pages_current--->__alloc_pages_nodemask--->get_page_from_freelist--->buffered_rmqueue--->rmqueue_bulk--->__rmqueue--->__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;

遍历order以上的每一个分配阶,直到找到有迁移类型为migratetype的空闲页块为止。

        for (current_order = order; current_order < MAX_ORDER; ++current_order) {

                area = &(zone->free_area[current_order]);

                if (list_empty(&area->free_list[migratetype]))

                        continue;

                page = list_entry(area->free_list[migratetype].next,

                                                        struct page, lru);

将页从链表中删除

                list_del(&page->lru);

删除伙伴系统标志,将页阶设为0

                rmv_page_order(page);

递减相应链表的空闲页计数

                area->nr_free--;

将高阶页块拆为低阶页块连接到低阶空闲链表中

                expand(zone, page, order, current_order, area, migratetype);

                return page;

        }

        return NULL;

}

【alloc_pages--->alloc_pages_current--->__alloc_pages_nodemask--->get_page_from_freelist--->buffered_rmqueue--->rmqueue_bulk--->__rmqueue--->__rmqueue_smallest--->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;

Low是将要分配的页块的阶,high是找到的有空闲页快的阶。将高阶页块拆分为低阶页块链接到低阶空闲链表中去。

        while (high > low) {

                area--;

                high--;

                size >>= 1;

                list_add(&page[size].lru, &area->free_list[migratetype]);

                area->nr_free++;增加相应阶空闲页计数

                set_page_order(&page[size], high);设置首页阶数

        }

}

【alloc_pages--->alloc_pages_current--->__alloc_pages_nodemask--->get_page_from_freelist--->buffered_rmqueue--->rmqueue_bulk--->__rmqueue--->__rmqueue_fallback】

static inline struct page *__rmqueue_fallback(struct zone *zone, int order, int start_migratetype)

{

        struct free_area * area;

        int current_order;

        struct page *page;

        int migratetype, i;

函数__rmqueue_smallest中没有分配到指定迁移类型的页块。在函数__rmqueue_fallback中就要从其他迁移类型的链表中取来一个页块,转化为指定迁移类型。为了避免引起内存碎片,在转化迁移类型时尽量挑大的内存块。

        for (current_order = MAX_ORDER-1; current_order >= order;

                                                --current_order) {

                for (i = 0;; i++) {

数组Fallbacks[]是一个迁移类型相关的表,它记录了,如果在一种指定迁移类型中没有找到空闲页块,接下来该到那种迁移类型的链表中去分配空闲页块

                        migratetype = fallbacks[start_migratetype][i];

                        if (migratetype == MIGRATE_RESERVE)

                                break;

到对应页阶中去找对应迁移类型的链表。

                        area = &(zone->free_area[current_order]);

                        if (list_empty(&area->free_list[migratetype]))

                                continue;

                        page = list_entry(area->free_list[migratetype].next,

                                        struct page, lru);

                        area->nr_free--;

pageblock_order是在文件include/linux/pageblock-flags.h中定义的一个宏,该宏存储了一个较大的页阶值,一般为MAX_ORDER-1,如果找到的页阶大于pageblock_order的二分之一,就将大小为1UL << pageblock_order的一个页块移动到迁移类型为start_migratetype的链表中。

                        if (!is_migrate_cma(migratetype) &&

                            (unlikely(current_order >= pageblock_order / 2) ||

                             start_migratetype == MIGRATE_RECLAIMABLE ||

                             page_group_by_mobility_disabled)) {

                                int pages;

将一大的页块移到迁移类型为start_migratetype的链表中,返回移动了的页的数目。

                                pages = move_freepages_block(zone, page,

                                                                start_migratetype);

如果移动的页的数目大于(1 << (pageblock_order-1)就将整块页的迁移类型都设为start_migratetype。

                                if (pages >= (1 << (pageblock_order-1)) ||

                                                page_group_by_mobility_disabled)

                                        set_pageblock_migratetype(page,

                                                                start_migratetype);

                                migratetype = start_migratetype;

                        }

                        list_del(&page->lru);

                        rmv_page_order(page);

......

将剩余的部分返还给伙伴系统。

                        expand(zone, page, order, current_order, area,

                               is_migrate_cma(migratetype)

                              migratetype : start_migratetype);

......

                        return page;

                }

        }

        return NULL;

}

慢速分配

【alloc_pages--->alloc_pages_current--->__alloc_pages_nodemask--->__alloc_pages_slowpath】

static inline struct page * __alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,

        struct zonelist *zonelist, enum zone_type high_zoneidx,

        nodemask_t *nodemask, struct zone *preferred_zone,

        int migratetype)

{

        const gfp_t wait = gfp_mask & __GFP_WAIT;

        struct page *page = NULL;

        int alloc_flags;

        unsigned long pages_reclaimed = 0;

        unsigned long did_some_progress;

        bool sync_migration = false;

        bool deferred_compaction = false;

.....

 如果设置了GFP_THISNODE表示不能进行内存回收

        if (NUMA_BUILD && (gfp_mask & GFP_THISNODE) == GFP_THISNODE)

                goto nopage;

restart:如果没有禁止内存回收就唤醒内存回收线程,进行内存回收

        if (!(gfp_mask & __GFP_NO_KSWAPD))

                wake_all_kswapd(order, zonelist, high_zoneidx,

                                                zone_idx(preferred_zone));

分析内存分配标志构建内核内部使用标志

        alloc_flags = gfp_to_alloc_flags(gfp_mask);

找到第一个最佳的内存分配域

        if (!(alloc_flags & ALLOC_CPUSET) && !nodemask)

                first_zones_zonelist(zonelist, high_zoneidx, NULL,

                                        &preferred_zone);

rebalance:

在内存回收之后进行一次内存分配,这里清除无水印标志

        page = get_page_from_freelist(gfp_mask, nodemask, order, zonelist,

                        high_zoneidx, alloc_flags & ~ALLOC_NO_WATERMARKS,

                        preferred_zone, migratetype);

        if (page)

                goto got_pg;

如果指定了无水印分配标志,就再次尝试内存分配,这次不考虑水印限制

        if (alloc_flags & ALLOC_NO_WATERMARKS) {

                page = __alloc_pages_high_priority(gfp_mask, order,

                                zonelist, high_zoneidx, nodemask,

                                preferred_zone, migratetype);

                if (page)

                        goto got_pg;

        }

        if (!wait) 如果是原子分配就不再进行下面的操作

                goto nopage;

 如果调用进程本身就是内存回收进程,为避免递归死锁,内存分配失败

        if (current->flags & PF_MEMALLOC)

                goto nopage;

......

直接进行内存回收然后分配所需内存,此时sync_migration = false,表示非同步调用

        page = __alloc_pages_direct_compact(gfp_mask, order,

                                        zonelist, high_zoneidx,

                                        nodemask,

                                        alloc_flags, preferred_zone,

                                        migratetype, sync_migration,

                                        &deferred_compaction,

                                        &did_some_progress);

        if (page)

                goto got_pg;

        sync_migration = true;

        if (deferred_compaction && (gfp_mask & __GFP_NO_KSWAPD))

                goto nopage;

直接进行内存压缩然后分配所需内存,

        page = __alloc_pages_direct_reclaim(gfp_mask, order,

                                        zonelist, high_zoneidx,

                                        nodemask,

                                        alloc_flags, preferred_zone,

                                        migratetype, &did_some_progress);

        if (page)

                goto got_pg;

如果上面内存回收没有得到任何空余内存,就杀死一些进程,已达到释放一些存储空间的目的。

        if (!did_some_progress) {

                if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) {

                        if (oom_killer_disabled)

                                goto nopage;

                       

                        if ((current->flags & PF_DUMPCORE) &&

                            !(gfp_mask & __GFP_NOFAIL))

                                goto nopage;

                        page = __alloc_pages_may_oom(gfp_mask, order,

                                        zonelist, high_zoneidx,

                                        nodemask, preferred_zone,

                                        migratetype);

                        if (page)

                                goto got_pg;

                        if (!(gfp_mask & __GFP_NOFAIL)) {

                                if (order > PAGE_ALLOC_COSTLY_ORDER)

                                        goto nopage;

                                if (high_zoneidx < ZONE_NORMAL)

                                        goto nopage;

                        }

                        goto restart;

                }

        }

如果内存回收机制回收到一些内存就判断是否需要再次尝试内存回收。

        pages_reclaimed += did_some_progress;

        if (should_alloc_retry(gfp_mask, order, did_some_progress,

                                                pages_reclaimed)) {

                wait_iff_congested(preferred_zone, BLK_RW_ASYNC, HZ/50);

                goto rebalance;

        } else {

再次以同步方式压缩内存,如果还不能分配到内存就放弃。

                page = __alloc_pages_direct_compact(gfp_mask, order,

                                        zonelist, high_zoneidx,

                                        nodemask,

                                        alloc_flags, preferred_zone,

                                        migratetype, sync_migration,

                                        &deferred_compaction,

                                        &did_some_progress);

                if (page)

                        goto got_pg;

        }

nopage:

        warn_alloc_failed(gfp_mask, order, NULL);

        return page;

got_pg:

        if (kmemcheck_enabled)

                kmemcheck_pagealloc_alloc(page, order, gfp_mask);

        return page;

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多