《LINUX3.0内核源代码分析》第四章:内存管理(2)

  1. 云栖社区>
  2. 博客>
  3. 正文

《LINUX3.0内核源代码分析》第四章:内存管理(2)

nothingfinal 2012-06-25 16:24:54 浏览462
展开阅读全文
摘要:本文主要讲述linux如何处理ARM cortex A9多核处理器的内存管理部分。主要包括对页面分配函数的介绍。

法律声明LINUX3.0内核源代码分析》系列文章由谢宝友(scxby@163.com)发表于http://xiebaoyou.blog.chinaunix.net,文章中的LINUX3.0源代码遵循GPL协议。除此以外,文档中的其他内容由作者保留所有版权。谢绝转载。

 

1.1.1      分配页面

不同的上下文,对分配页面有不同的限制。例如:在中断上下文中,不允许睡眠,那么分配内存时,就不能等待换页或者其他可能引起调度的事件。在文件系统代码中,申请页面时,需要使用GFP_NOFS标志,这样在内存不足时,就不能通过文件系统回写文件缓存,以免引起死锁。Linux3.0提供了以下分配标志:

/**

 * 类似于GFP_ATOMIC,但是没有__GFP_HIGH标志。实际上就是0.

 * __GFP_HIGH标志表示需要适当降低水线,以使用紧急内存池。

 */

#define GFP_NOWAIT      (GFP_ATOMIC & ~__GFP_HIGH)

/* GFP_ATOMIC means both !wait (__GFP_WAIT not set) and use emergency pool */

/**

 * 原子分配。不允许阻塞,同时允许使用紧急内存池。

 * 这个标志主要用于中断上下文。

 */

#define GFP_ATOMIC      (__GFP_HIGH)

/**

 * 不允许IO操作。当内存不足时,可能需要将内存换出到外部设备,这需要IO操作。

 * 但是在进行IO操作的过程,需要申请内存时必须指定此标志,以免死锁。

 */

#define GFP_NOIO  (__GFP_WAIT)

/**

 * 不允许文件系统操作。在文件系统代码中,申请内存需要有此标志。也是为了避免死锁。

 */

#define GFP_NOFS   (__GFP_WAIT | __GFP_IO)

/**

 * 当内存不足时,允许通过文件系统、IO操作将页面换出以释放内存空间。

 * 一般的系统调用代码中,都使用此分配标志。

 * 这也是最常见的分配标志。

 */

#define GFP_KERNEL       (__GFP_WAIT | __GFP_IO | __GFP_FS)

/**

 * 类似于GFP_KERNEL,并且在内存不足时,还允许进行内存回收。

 */

#define GFP_TEMPORARY       (__GFP_WAIT | __GFP_IO | __GFP_FS | \

                             __GFP_RECLAIMABLE)

/**

 * 分配用于用户进程的页面。与GFP_KERNEL相比,增加__GFP_HARDWALL

 * __GFP_HARDWALL表示可以在进程能够运行的CPU节点上分配内存。

 */

#define GFP_USER   (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL)

/**

 * 分配用于用户进程的页面,并且可以在高端内存中分配内存。

 */

#define GFP_HIGHUSER  (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | \

                             __GFP_HIGHMEM)

/**

 * 分配用于用户进程的页面,并且可以在高端内存中分配内存,分配的页面可能被迁移以减少内存外碎片。

 */                     

#define GFP_HIGHUSER_MOVABLE        (__GFP_WAIT | __GFP_IO | __GFP_FS | \

                                      __GFP_HARDWALL | __GFP_HIGHMEM | \

                                      __GFP_MOVABLE)

/**

 * 如果内存不足,可以通过文件系统和IO操作将页面换出以增加可用内存。

 */

#define GFP_IOFS    (__GFP_IO | __GFP_FS)

/**

 * 分配的页面用于透明巨页。

 */

#define GFP_TRANSHUGE       (GFP_HIGHUSER_MOVABLE | __GFP_COMP | \

                             __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN | \

                             __GFP_NO_KSWAPD)

 

/**

 * 只允许在当前节点中分配。

 */

#ifdef CONFIG_NUMA

#define GFP_THISNODE  (__GFP_THISNODE | __GFP_NOWARN | __GFP_NORETRY)

#else

#define GFP_THISNODE  ((__force gfp_t)0)

#endif

 

分配页面的函数是alloc_pages,在NUMA系统中,它仅仅是对alloc_pages_current的一个简单封装。

 

/**

 * 分配页面。

 *              gfp:           分配标志,如GFP_ATOMICGFP_KERNEL等等。

 *              order:                要分配的页面数量为2^order,要分配一个页面,指定order0.

 */

struct page *alloc_pages_current(gfp_t gfp, unsigned order)

{

         /**

          * 取当前进程的内存分配策略。

          * 在支持NUMA的系统中,这个策略可以决定在哪些节点中分配内存。

          */

         struct mempolicy *pol = current->mempolicy;

         struct page *page;

 

         /**

          * 如果存在以下情况,就使用系统默认的分配策略:

          *              进程没有指定特定的分配策略。

          *              在中断中,由于中断要求快速执行,因此使用默认策略在当前节点中分配内存。

          *              调用者显示的要求在当前节点中分配内存。

          */

         if (!pol || in_interrupt() || (gfp & __GFP_THISNODE))

                   pol = &default_policy;

 

         /**

          * 在使用进程分配策略前,必须调用get_mems_allowed,以防止系统修改其值。

          */

         get_mems_allowed();

         /*

          * No reference counting needed for current->mempolicy

          * nor system default_policy

          */

         /**

          * numa_memory_policy.txt描述了内存分配策略,我也不清楚。

          * 但是最后都会调用__alloc_pages_nodemask,因此我们接下来分析__alloc_pages_nodemask函数。

          */

         if (pol->mode == MPOL_INTERLEAVE)

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

         else

                   page = __alloc_pages_nodemask(gfp, order,

                                     policy_zonelist(gfp, pol, numa_node_id()),

                                     policy_nodemask(gfp, pol));

         /**

          * 允许进行内存策略的修改。

          */

         put_mems_allowed();

         return page;

}

EXPORT_SYMBOL(alloc_pages_current);

 

页面分配的核心算法由__alloc_pages_nodemask函数实现。

 

/**

 * 页面分配的核心算法。

 */

struct page *

__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,

                            struct zonelist *zonelist, nodemask_t *nodemask)

{

         /**

          * 根据传入的标志,决定从哪个内存区开始分配内存。

          * 例如,如果没有指定高端内存标志,那么就从normal开始,当normal中没有可用内存时,再从DMA32DMA中分配内存。

          */

         enum zone_type high_zoneidx = gfp_zone(gfp_mask);

         struct zone *preferred_zone;

         struct page *page;

         /**

          * 根据传入的标志,决定分配页面的迁移方式。

          * 页面迁移功能可以减少内存外碎片。

          */

         int migratetype = allocflags_to_migratetype(gfp_mask);

 

         /**

          * 在系统运行的各个阶段,某些标志不能使用。

          * 如,没有初始化文件系统时,自然不能使用GFP_FS标志。这里对传入的标志进行整理,以适用系统各个阶段的情况。

          */

         gfp_mask &= gfp_allowed_mask;

 

         /**

          * 调试代码,略过。

          */

         lockdep_trace_alloc(gfp_mask);

 

         /**

          * 如果指定了__GFP_WAIT标志,那么在分配页面的过程中就可能会睡眠。

          * 如果当前上下文不允许进程睡眠,并且指定了__GFP_WAIT标志,则会产生警告。这也是用于调试的代码。

          */

         might_sleep_if(gfp_mask & __GFP_WAIT);

 

         /**

          * 这里是对参数进行进一步的检查,当打开CONFIG_FAIL_PAGE_ALLOC调试配置选项才有用,略过。

          */

         if (should_fail_alloc_page(gfp_mask, order))

                   return NULL;

 

         /*

          * Check the zones suitable for the gfp_mask contain at least one

          * valid zone. It's possible to have an empty zonelist as a result

          * of GFP_THISNODE and a memoryless node

          */

         /**

          * 如果没有任何一个可用管理区,则直接返回NULL

          * 如果指定了GFP_THISNODE标志并且当前节点没有内存,则可能存在这种情况。

          * 这一般要求上层调用者在发现NULL返回值时,指定其他标志。

          */

         if (unlikely(!zonelist->_zonerefs->zone))

                   return NULL;

 

         /**

          * 后续要求调用cpuset_current_mems_allowed,这个宏获得当前进程可用的节点。

          * 调用这个宏需要防止当前进程的内存策略被修改。get_mems_allowed用于此目的。

          */

         get_mems_allowed();

         /* The preferred zone is used for statistics later */

         /**

          * 根据传入的参数,确定优先在哪个管理区中分配内存。

          */

         first_zones_zonelist(zonelist, high_zoneidx,

                                     nodemask ? : &cpuset_current_mems_allowed,

                                     &preferred_zone);

         /**

          * 没有可用的管理区。

          */

         if (!preferred_zone) {

                   put_mems_allowed();/* get_mems_allowed配对调用。 */

                   return NULL;

         }

 

         /* First allocation attempt */

         /**

          * 快速分配路径,在这个分配路径中,指定了__GFP_HARDWALLALLOC_WMARK_LOWALLOC_CPUSET标志。

          * 这样,在分配时要考虑内存的CPU亲和性,并且尽量在较高的水线上分配,防止某些内存区被过早的击穿。

          */

         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);

         /* get_mems_allowed配对调用。 */

         put_mems_allowed();

 

         /* 调试代码。 */

         trace_mm_page_alloc(page, order, gfp_mask, migratetype);

         return page;

}

EXPORT_SYMBOL(__alloc_pages_nodemask);

 

页面分配的主流程在get_page_from_freelist__alloc_pages_slowpath函数中。

 

 

未完待续

网友评论

登录后评论
0/500
评论
nothingfinal
+ 关注