物理内存管理:请求PFN慢速分配实现(2)

物理内存管理:请求PFN慢速分配实现(2)

之前分析了请求Physical Frame Number的主体函数 。当主体函数中的get_page_from_freelist() 分配失败后,自动进入 __alloc_pages_slowpath()进行慢速分配,首先会降低分配物理页框的条件。

检查请求分配的order阶数,如果超过了MAX_ORDER,则说明可能出现错误,返回空值。之后会检查传入慢速分配函数的参数gfp_mask是否指定GFP_THISNODE 和开启了NUMA,这个表示不能进行内存回收。如果成立则直接跳转到nopage。

经过上面的检查,系统开始正式分配空闲page frame,首先kernel通过wake_all_kswapds()唤醒每个zone所属node的kswapd守护进程(在kswapd没有在禁止的情况下),回收不经常使用的page frame。当将不经常使用的page frame回收以后,使用gfp_to_alloc_flags()对分配标志进行调整,稍微降低分配标准,以便再一次使用get_page_from_freelist()函数进行page frame 分配。


__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 classzone_idx, 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;
         enum migrate_mode migration_mode = MIGRATE_ASYNC;
         bool deferred_compaction = false;
         int contended_compaction = COMPACT_CONTENDED_NONE;
 
         if (order >= MAX_ORDER) {
                 WARN_ON_ONCE(!(gfp_mask & __GFP_NOWARN));
                 return NULL;
         }
 
         if (IS_ENABLED(CONFIG_NUMA) &&
             (gfp_mask & GFP_THISNODE) == GFP_THISNODE)
                 goto nopage;
 
retry:
         if (!(gfp_mask & __GFP_NO_KSWAPD))
                 wake_all_kswapds(order, zonelist, high_zoneidx,
                                 preferred_zone, nodemask);
 
         alloc_flags = gfp_to_alloc_flags(gfp_mask);
 
         if (!(alloc_flags & ALLOC_CPUSET) && !nodemask) {
                 struct zoneref *preferred_zoneref;
                 preferred_zoneref = first_zones_zonelist(zonelist, high_zoneidx,
                                 NULL, &preferred_zone);
                 classzone_idx = zonelist_zone_idx(preferred_zoneref);
         }
 
         page = get_page_from_freelist(gfp_mask, nodemask, order, zonelist,
                         high_zoneidx, alloc_flags & ~ALLOC_NO_WATERMARKS,
                         preferred_zone, classzone_idx, migratetype);
         if (page)
                 goto got_pg;
....

如果page不为空,则说明内存申请成功,否则继续进行慢速page frame分配。 如果设置了ALLOC_NO_WATERMARKS标志,那么此时会忽略水印,并此时进入__alloc_pages_high_priority()。这个函数内部会至少会再次调用get_page_from_freelist(),如果设置了__GFP_NOFAIL标志,则不断的循环等待并尝试进行内存分配。

__alloc_pages_high_priority()


if (!wait) {
        WARN_ON_ONCE(gfp_mask & __GFP_NOFAIL);
        goto nopage;
}
 
/* Avoid recursion of direct reclaim */
if (current->flags & PF_MEMALLOC)
        goto nopage;
 
/* Avoid allocations with no watermarks from looping endlessly */
if (test_thread_flag(TIF_MEMDIE) && !(gfp_mask & __GFP_NOFAIL))
        goto nopage;

判断wait,如果调用者希望原子分配内存,则不能等待内存回收,返回NULL,如果当前进程就是内存回收进程(PF_MEMALLOC),则直接跳出,如果当前进程已经die,而且系统没有设置不准失败的位,直接返回nopage。否则如果当前进程设置了不准失败(__GFP_NOFAIL),则死循环继续分配,等待其他线程释放一点点内存。


page = __alloc_pages_direct_compact(gfp_mask, order, zonelist,
                                         high_zoneidx, nodemask, alloc_flags,
                                         preferred_zone,
                                         classzone_idx, migratetype,
                                         migration_mode, &contended_compaction,
                                         &deferred_compaction);
if (page)
        goto got_pg;

kernel尝试压缩内存。这样可以将一些小的外部碎片合并成大页面,这样也许能够满足内存分配要求。内存压缩是通过页面迁移实现的,第一次调用的时候,是非同步的。第二次调用则是同步方式。


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;

上面这个函数是真正的慢速分配的核心,它最终调用try_to_free_pages()回收一些最近很少用的页,然后将其写回磁盘上的交换区,以便在物理内存中腾出更多的空间。最终内核会再次调用get_page_from_freelist()尝试分配内存。

__perform_reclaim()函数中返回一个unsigned long *did_some_progress变量,标示是否成功分配page。如果这个变量进入下面代码


pages_reclaimed += did_some_progress;
         if (should_alloc_retry(gfp_mask, order, did_some_progress,
                                                 pages_reclaimed)) {
 
                 if (!did_some_progress) {
                         page = __alloc_pages_may_oom(gfp_mask, order, zonelist,
                                                 high_zoneidx, nodemask,
                                                 preferred_zone, classzone_idx,
                                                 migratetype,&did_some_progress);
                         if (page)
                                 goto got_pg;
                         if (!did_some_progress)
                                 goto nopage;
                 }
         } else {
                 page = __alloc_pages_direct_compact(gfp_mask, order, zonelist,
                                         high_zoneidx, nodemask, alloc_flags,
                                         preferred_zone,
                                         classzone_idx, migratetype,
                                         migration_mode, &contended_compaction,
                                         &deferred_compaction);
                 if (page)
                         goto got_pg;
         }

上面代码,可以看出系统还在尽量分配代码,判定是否重新执行__alloc_pages_direct_compact(),这次属于同步操作。如果判定不用执行该函数,也就意味着,kernel开始怀疑是否发生了OOM(out of memory)。如果当前请求内存的进程发生了OOM,也就是说该进程试图拥有过多的内存,那么此时内核会调用OOM killer杀死它。并且跳转到restart处,重新进行内存分配。

最后就是两个goto跳转函数:


nopage:
         warn_alloc_failed(gfp_mask, order, NULL);
         return page;
got_pg:
         if (kmemcheck_enabled)
                 kmemcheck_pagealloc_alloc(page, order, gfp_mask);
 
         return page;

nopage顾名思义就是没有足够的page frame 来alloc,got_pg就是系统分配到了page,我们跳转到__alloc_pages_direct_compact()和__alloc_pages_direct_reclaim()中看到都是先进行page的回收,然后再在get_page_from_freelist()中完成的,它根据伙伴算法分配所需大小的页框。

最后留下了代码段,不是特别明确,还需要继续看代码


2755         /* Checks for THP-specific high-order allocations */
2756         if ((gfp_mask & GFP_TRANSHUGE) == GFP_TRANSHUGE) {
...
2791         if ((gfp_mask & GFP_TRANSHUGE) != GFP_TRANSHUGE ||
2792                                                 (current->flags & PF_KTHREAD))
2793                 migration_mode = MIGRATE_SYNC_LIGHT;

参考:

http://lxr.free-electrons.com/source/mm/page_alloc.c#L2639