上一篇<<baddy:初始化内存域>>分析了内存域的初始化过程,及扩展内容cpu热插拔函数的注册及热插拔线程的作用等等。UMA下只有一个pglist_data对象也就是对应一个内存域,而NUMA下最多拥有5个内存域(zone), ZONE_DMA、ZONE_DMA32、ZONE_NORMAL、ZONE_HIGHMEM、ZONE_MOVABLE等等,内存域类型根据具体硬件来决定。
baddy系统的入口(核心)函数为__alloc_pages,经典调用可以追溯到slub分配器的kmalloc函数,参考<<slub构建过程>>。
__alloc_pages
__alloc_pages函数实现在mm/page_alloc.c文件:
struct page *__alloc_pages(gfp_t gfp, unsigned int order, int preferred_nid,
nodemask_t *nodemask)
{
struct page *page;
unsigned int alloc_flags = ALLOC_WMARK_LOW; // 默认低水位线
// #define ALLOC_WMARK_MIN WMARK_MIN // 最低水位线
// #define ALLOC_WMARK_LOW WMARK_LOW // 低水位线
// #define ALLOC_WMARK_HIGH WMARK_HIGH // 高水位线
// #define ALLOC_NO_WATERMARKS 0x04 // 不检查水位线
// enum zone_watermarks {
// WMARK_MIN, // 内存区域的空闲页面最低水位线,说明本区域的内存严重不足
// WMARK_LOW, // 内存区域的空闲页面低水位线,说明本区域的内存轻微不足,默认值为WMARK_MIN的125%
// WMARK_HIGH, // 内存区域的空闲页数高水位线,说明本区域内存充足,默认值为WMARK_MAX的150%
// WMARK_PROMO, // 内存区域的空闲页面大于高水位线;当内存需要回收时,对快速内存节点通过kswapd以回收页面,至空闲页面略高于WMARK_PROMO水位线
// NR_WMARK // unsigned long _watermark[NR_WMARK]; 表示区域(zone)水平线,由WMARK_MIN... WMARK_PROMO索引
// };
gfp_t alloc_gfp; /* The gfp_t that was actually used for allocation */
struct alloc_context ac = { };
/*
* There are several places where we assume that the order value is sane
* so bail out early if the request is out of bound.
*/
if (WARN_ON_ONCE_GFP(order >= MAX_ORDER, gfp)) // 调用dump_stack,打印堆栈信息,指定__GFP_NOWARN标志时,不输出警告信息
return NULL;
gfp &= gfp_allowed_mask;
// 在早期引导期间,gfp_allowed_mask被设置为GFP_BOOT_MASK,
// 以限制在启用中断之前使用的GFP标志
// 一旦启用了中断,在系统运行时将其设置为__GFP_BITS_MASK
// 在休眠期间,PM使用它来避免设备挂起时内存分配期间的I/O
/* 应用作用域分配约束。这主要是关于GFP_NOFS的。
GFP_NOIO必须从一个特定的上下文继承所有的分配请求,
该上下文被标记为memalloc_no{fs,io}_{save,restore}。
PF_MEMALLOC_PIN确保在分配时不使用可移动区域 */
gfp = current_gfp_context(gfp);
alloc_gfp = gfp;
if (!prepare_alloc_pages(gfp, order, preferred_nid, nodemask, &ac,
&alloc_gfp, &alloc_flags))
return NULL;
进入prepare_alloc_pages函数:
static inline bool prepare_alloc_pages(gfp_t gfp_mask, unsigned int order,
int preferred_nid, nodemask_t *nodemask,
struct alloc_context *ac, gfp_t *alloc_gfp,
unsigned int *alloc_flags)
{
ac->highest_zoneidx = gfp_zone(gfp_mask);
// highest_zoneidx表示分配请求的最高可用区域索引
// 由于分区的性质,低于highest_zoneidx的分区上的内存将受到lowmem_reserve[highest_zoneidx]的保护
// 回收/压缩也使用最高的zoneidx来限制目标区域
// 因为高于此索引的区域不能用于此分配请求
ac->zonelist = node_zonelist(preferred_nid, gfp_mask); // 获取区域列表
ac->nodemask = nodemask;
ac->migratetype = gfp_migratetype(gfp_mask); // 将GFP标志转换为其相应的迁移类型
if (cpusets_enabled()) { // cpusets有效
*alloc_gfp |= __GFP_HARDWALL; // __GFP_HARDWALL 强制执行cpuset内存分配策略
/*
* When we are in the interrupt context, it is irrelevant
* to the current task context. It means that any node ok.
*/
if (in_task() && !ac->nodemask) // 在任务上下文中并且nodemask为NULL
ac->nodemask = &cpuset_current_mems_allowed;
// #define cpuset_current_mems_allowed (current->mems_allowed)
else
*alloc_flags |= ALLOC_CPUSET; // 检查cpuset是否正确
}
fs_reclaim_acquire(gfp_mask); // 检查(符合)/增加页面回收独占锁
fs_reclaim_release(gfp_mask); // 释放锁
might_sleep_if(gfp_mask & __GFP_DIRECT_RECLAIM); // 可以回收的情况下,主动触发一次调度
if (should_fail_alloc_page(gfp_mask, order)) // should_fail_alloc_page做一些预检查, 一些无法分配的条件会直接报错