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

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函数中。

未完待续

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值