内核中有六个基本的页框分配函数,它们内部经过封装,最终都会调用alloc_pages_node()。这个函数的参数比alloc_pages()多了一个nid,它用来指定节点id,如果nid小于0,则说明在当前节点上分配页框。正确获取到节点id后,接下来调用__alloc_pages()。
static inline struct page *alloc_pages_node(int nid, gfp_t gfp_mask, unsigned int order) { if (nid < 0) nid = numa_node_id(); return __alloc_pages(gfp_mask, order, node_zonelist(nid, gfp_mask)); }
__alloc_pages()第三个参数根据nid和gfp_mask得到适当的zonelist链表,该过程通过node_zonelist()完成。该函数的实现比较简单,其中NODE_DATA()根据nid返回对应的内存节点描述符,而gfp_zonelist()根据flags标志选取对应的内存管理区链表。其实node_zonelist()就是根据flags在相应内存节点的node_zonelists数组中选择一个何时的内存管理区链表zonelist。
static inline int gfp_zonelist(gfp_t flags) { if (NUMA_BUILD && unlikely(flags & __GFP_THISNODE)) return 1; return 0; }
由于node_zonelists数组的元素个数最大为2,因此gfp_zonelist()返回0或者1。如果flags中设置了__GFP_THISNODE并且NUMA被设置,则表明使用当前节点对应的zonelist,返回1。否则使用备用zonelist,也就是说当本地节点中zone不足时,在其他节点中申请页框。
static inline int gfp_zonelist(gfp_t flags) { if (NUMA_BUILD && unlikely(flags & __GFP_THISNODE)) return 1; return 0; }
__alloc_pages()内部再次封装__alloc_pages_nodemask()。
static inline struct page * __alloc_pages(gfp_t gfp_mask, unsigned int order, struct zonelist *zonelist) { return __alloc_pages_nodemask(gfp_mask, order, zonelist, NULL); }
1. 主体分配函数
现在进入__alloc_pages_nodemask(),它作为页框分配函数的核心部分。该函数可以通过get_page_from_freelist()快速分配所请求的内存,但是大多数情况下调用该函数都会失败,因为通常物理内存的使用情况都比较紧张,这一点从其后if语句中的unlikely就可以看出。
struct page * __alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,struct zonelist *zonelist, nodemask_t *nodemask) { enum zone_type high_zoneidx = gfp_zone(gfp_mask); struct zone *preferred_zone; struct page *page; int migratetype = allocflags_to_migratetype(gfp_mask); gfp_mask &= gfp_allowed_mask; lockdep_trace_alloc(gfp_mask); might_sleep_if(gfp_mask & __GFP_WAIT); if (should_fail_alloc_page(gfp_mask, order)) return NULL; if (unlikely(!zonelist->_zonerefs->zone)) return NULL; first_zones_zonelist(zonelist, high_zoneidx, nodemask, &preferred_zone); if (!preferred_zone) return NULL; 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); trace_mm_page_alloc(page, order, gfp_mask, migratetype); return page; }
首先,gfp_zone()根据gfp_mask选取适当类型的zone。在经过几项参数检查后,该函数通过zonelist->_zonerefs->zone判断zonelist是否为空,既至少需要一个zone可用。接着根据一开始选取的zone类型high_zoneidx,通过first_zones_zonelist()确定优先分配内存的内存管理区。
如果一切顺利,将会进入get_page_from_freelist(),这个函数可以看作是伙伴算法的前置函数,它通过分配标志和分配阶判断是否能进行此次内存分配。如果可以分配,则它进行实际的内存分配工作,既利用伙伴算法进行分配内存。否则,进入__alloc_pages_slowpath(),此时内核需要放宽一些分配条件,或回收一些系统的内存,然后再调用几次get_page_from_freelist()以申请所需内存。