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

简介: 摘要:本文主要讲述linux如何处理ARM cortex A9多核处理器的内存管理部分。主要包括对页面分配函数的介绍。 法律声明:《LINUX3.0内核源代码分析》系列文章由谢宝友(scxby@163.com)发表于http://xiebaoyou.blog.chinaunix.net,文章中的LINUX3.0源代码遵循GPL协议。
摘要:本文主要讲述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函数中。

 

 

未完待续

相关文章
|
存储 Java Linux
【深入研究Hotspot源码与Linux内核】
【深入研究Hotspot源码与Linux内核】
117 0
|
2天前
|
算法 Linux 开发者
深入探究Linux内核中的内存管理机制
本文旨在对Linux操作系统的内存管理机制进行深入分析,探讨其如何通过高效的内存分配和回收策略来优化系统性能。文章将详细介绍Linux内核中内存管理的关键技术点,包括物理内存与虚拟内存的映射、页面置换算法、以及内存碎片的处理方法等。通过对这些技术点的解析,本文旨在为读者提供一个清晰的Linux内存管理框架,帮助理解其在现代计算环境中的重要性和应用。
|
5天前
|
缓存 算法 Linux
Linux内核中的内存管理机制深度剖析####
【10月更文挑战第28天】 本文深入探讨了Linux操作系统的心脏——内核,聚焦其内存管理机制的奥秘。不同于传统摘要的概述方式,本文将以一次虚拟的内存分配请求为引子,逐步揭开Linux如何高效、安全地管理着从微小嵌入式设备到庞大数据中心数以千计程序的内存需求。通过这段旅程,读者将直观感受到Linux内存管理的精妙设计与强大能力,以及它是如何在复杂多变的环境中保持系统稳定与性能优化的。 ####
11 0
|
6月前
|
算法 安全 Linux
深度解析:Linux内核内存管理机制
【4月更文挑战第30天】 在操作系统领域,内存管理是核心功能之一,尤其对于多任务操作系统来说更是如此。本文将深入探讨Linux操作系统的内核内存管理机制,包括物理内存的分配与回收、虚拟内存的映射以及页面替换算法等关键技术。通过对这些技术的详细剖析,我们不仅能够理解操作系统如何高效地利用有限的硬件资源,还能领会到系统设计中的性能与复杂度之间的权衡。