物理内存管理:请求PFN函数主体实现(1)
在 物理内存管理:请求PFN函数层次结构分析 这篇文章中,我分析了分配页框的函数结构,其中是上层页框分配的核心,这个函数比起alloc_pages()多一个参数nid,如果传入的nid < 0 ,那么在当前内存节点上分配physical frame。
这里需要阐述的是Linux的内存管理针对的就是NUMA结构,如果当前系统只有一个节点,那么默认调用numa_node_id()返回这个唯一节点。
309 static inline struct page *alloc_pages_node(int nid, gfp_t gfp_mask,
310 unsigned int order)
311 {
312 /* Unknown node is current node */
313 if (nid < 0)
314 nid = numa_node_id();
315
316 return __alloc_pages(gfp_mask, order, node_zonelist(nid, gfp_mask));
317 }
而在__alloc_pages()函数中,根据nid与gfp_mask可以得到一个适当的zonelist链表,我们知道每个内存节点下面都会默认存在三个zonelist区域:ZONE_DMA/ZONE_NORMAL/ZONE_HIGHMEM ,而node_zonelist(nid, gfp_mask)就是选择合适的内存链表区域zonelist。
因为存在三个zonelist区域,联系之前的struct pglist_data结构成员struct zonelist node_zonelists[MAX_ZONELISTS],MAX_ZONELISTS最大值就是2,可以看出分配只能分配当前节点和备用节点。
581 /*
582 * The NUMA zonelists are doubled because we need zonelists that restrict the
583 * allocations to a single node for __GFP_THISNODE.
584 *
585 * [0] : Zonelist with fallback
586 * [1] : No fallback (__GFP_THISNODE)
587 */
588 #define MAX_ZONELISTS 2
而__alloc_pages()函数内部又封装了__alloc_pages_nodemask()函数,这个函数是页框分配的主体[2],
struct page *
2857 __alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,
2858 struct zonelist *zonelist, nodemask_t *nodemask)
2859 {
2860 enum zone_type high_zoneidx = gfp_zone(gfp_mask);
2861 struct zone *preferred_zone;
2862 struct zoneref *preferred_zoneref;
2863 struct page *page = NULL;
2864 int migratetype = gfpflags_to_migratetype(gfp_mask);
2865 unsigned int cpuset_mems_cookie;
2866 int alloc_flags = ALLOC_WMARK_LOW|ALLOC_CPUSET|ALLOC_FAIR;
2867 int classzone_idx;
2868
2869 gfp_mask &= gfp_allowed_mask;
2870
2871 lockdep_trace_alloc(gfp_mask);
2872
2873 might_sleep_if(gfp_mask & __GFP_WAIT);
2874
2875 if (should_fail_alloc_page(gfp_mask, order))
2876 return NULL;
...
2883 if (unlikely(!zonelist->_zonerefs->zone))
2884 return NULL;
....
2889 retry_cpuset:
2890 cpuset_mems_cookie = read_mems_allowed_begin();
2891
2892 /* The preferred zone is used for statistics later */
2893 preferred_zoneref = first_zones_zonelist(zonelist, high_zoneidx,
2894 nodemask ? : &cpuset_current_mems_allowed,
2895 &preferred_zone);
2896 if (!preferred_zone)
2897 goto out;
2898 classzone_idx = zonelist_zone_idx(preferred_zoneref);
2899
2900 /* First allocation attempt */
2901 page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, nodemask, order,
2902 zonelist, high_zoneidx, alloc_flags,
2903 preferred_zone, classzone_idx, migratetype);
2904 if (unlikely(!page)) {
....
2910 gfp_mask = memalloc_noio_flags(gfp_mask);
2911 page = __alloc_pages_slowpath(gfp_mask, order,
2912 zonelist, high_zoneidx, nodemask,
2913 preferred_zone, classzone_idx, migratetype);
2914 }
2915
2916 trace_mm_page_alloc(page, order, gfp_mask, migratetype);
2917
2918 out:
....
2925 if (unlikely(!page && read_mems_allowed_retry(cpuset_mems_cookie)))
2926 goto retry_cpuset;
2927
2928 return page;
2929 }
分析代码,我们可以看到,gfp_zone()根据gfp_mask选取适当类型的zone index。然后经过几项检查,通过zonelist->_zonerefs->zone判断zonelist是否为空,在这里至少要存在一个可用的zone,然后使用上述的zone index,通过first_zones_zonelist()来分配一个内存管理区。
如果前面分配成功,则进入get_page_from_freelist()函数,这个函数可以看成伙伴算法的前置函数,如果伙伴系统存在空位,那么利用伙伴系统进行分配内存,如果分配不成功就进入__alloc_pages_slowpath()慢速分配,这个时候内核要放宽分配的条件,回收系统内存,然后总会分配出一块page。
###这里我们要说明下likely()与unlikely()的用法,这两个宏只是提高代码执行概率,是的gcc在编译时,将哪个代码段提前,哪个代码段推后,从而提高效率,不会对值有修改,例如if (unlikely(!zonelist->_zonerefs->zone))表示的就是当zonelist->_zonerefs->zone为空时,执行return NULL操作[1],虽然这个return不太可能发生。
###在代码中我们还发现了cpuset_mems_cookie = read_mems_allowed_begin();语句,看到名字,我们就知道这个与cgroup有关,也就是说与cpuset子系统相关,cpuset子系统负责cpu节点与内存节点的分配,如果没有指定nodemask,则使用cpuset_current_mems_allowed允许的节点。我们看到在out域下,有一个if (unlikely(!page && read_mems_allowed_retry(cpuset_mems_cookie))) 发现目前kernel对于cgroup机制中出现page分配失败,就会怀疑是否cpuset_mems_cookie出现修改,如果出现修改,则重试。
[1] http://blog.csdn.net/npy_lp/article/details/7175517
[2] http://lxr.free-electrons.com/source/mm/page_alloc.c#L2857