法律声明:《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_ATOMIC、GFP_KERNEL等等。
* order: 要分配的页面数量为2^order,要分配一个页面,指定order为0.
*/
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中没有可用内存时,再从DMA32、DMA中分配内存。
*/
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_HARDWALL和ALLOC_WMARK_LOW、ALLOC_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函数中。
未完待续