在 物理内存管理:请求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