本文基于linux-3.10的内核
申请内存时需要先确定两个值,分别是high_zoneidx和migratetype。这两个值从哪里获取到呢?都是利用上面传递过来的GFP flag。
对于high_zoneidx是通过gfp_zone函数获取的,而migratetype是通过gfpflags_to_migratetype来获取的。其中gfp_zone是利用的GFP标志位的bit[0-3]这4个bits来确定的,而migratetype是bit[3-4]这两个bits。需要注意他们中间是存在重复的__GFP_MOVABLE标志的,说明它不仅仅用来确定zone也是用来确定migratetype的一个因素。
gfp_zone
#define ___GFP_DMA 0x01u
#define ___GFP_HIGHMEM 0x02u
#define ___GFP_DMA32 0x04u
#define ___GFP_MOVABLE 0x08u
上面是用于寻找zone的4个标志位,每个标志位占用单独一个bit。根据这4个bit相互搭配一个存在16中组合,如下所示:
216 * bit result
217 * =================
218 * 0x0 => NORMAL
219 * 0x1 => DMA or NORMAL
220 * 0x2 => HIGHMEM or NORMAL
221 * 0x3 => BAD (DMA+HIGHMEM)
222 * 0x4 => DMA32 or DMA or NORMAL
223 * 0x5 => BAD (DMA+DMA32)
224 * 0x6 => BAD (HIGHMEM+DMA32)
225 * 0x7 => BAD (HIGHMEM+DMA32+DMA)
226 * 0x8 => NORMAL (MOVABLE+0)
227 * 0x9 => DMA or NORMAL (MOVABLE+DMA)
228 * 0xa => MOVABLE (Movable is valid only if HIGHMEM is set too)
229 * 0xb => BAD (MOVABLE+HIGHMEM+DMA)
230 * 0xc => DMA32 (MOVABLE+HIGHMEM+DMA32)
231 * 0xd => BAD (MOVABLE+DMA32+DMA)
232 * 0xe => BAD (MOVABLE+DMA32+HIGHMEM)
233 * 0xf => BAD (MOVABLE+DMA32+HIGHMEM+DMA)
左边是这些组合得到的值,右边是该组合要申请内存的zone,可以认为上面这个表就是GFP flag到zone的映射表。
内核中规定前三个GFP标志位是互斥的,不能同时设置,而___GFP_MOVABLE则可以搭配设置。因而有一些组合得到的bit值是不符合要求,上表中右边写上BAD的即是这种情况。
GFP和ZONE已然存在上面的这个关系,那么内核的函数实现就是如何快速的查找该表,从GFP组合中快速的寻找ZONE,为了提升性能,内核实现都是利用左移和右移操作来实现,因而构建了一个便于左移和右移查找的一个TABLE:
249 #define GFP_ZONE_TABLE (
250 (ZONE_NORMAL << 0 * GFP_ZONES_SHIFT)
251 | (OPT_ZONE_DMA << ___GFP_DMA * GFP_ZONES_SHIFT)
252 | (OPT_ZONE_HIGHMEM << ___GFP_HIGHMEM * GFP_ZONES_SHIFT)
253 | (OPT_ZONE_DMA32 << ___GFP_DMA32 * GFP_ZONES_SHIFT)
254 | (ZONE_NORMAL << ___GFP_MOVABLE * GFP_ZONES_SHIFT)
255 | (OPT_ZONE_DMA << (___GFP_MOVABLE | ___GFP_DMA) * GFP_ZONES_SHIFT)
256 | (ZONE_MOVABLE << (___GFP_MOVABLE | ___GFP_HIGHMEM) * GFP_ZONES_SHIFT)
257 | (OPT_ZONE_DMA32 << (___GFP_MOVABLE | ___GFP_DMA32) * GFP_ZONES_SHIFT)
258 )
左边是zone的index,右边是匹配的GFP组合值,加入zone index要么为0,要么为1,那么把zone的index值按照GFP组合值大小来进行左移操作即可把它保存到对应的bit上,该bit上的0和1分别表示不同的两个zone。然而现实是zone index它可能是大于1的值,因而需要GFP_ZONES_SHIFT个bits来保存zone的index,所以右边统一乘以GFP_ZONES_SHIFT。
通过这种方式就创建了一个信息查找表,而查找函数就是如下的函数:
277 static inline enum zone_type gfp_zone(gfp_t flags)
278 {
279 enum zone_type z;
280 int bit = (__force int) (flags & GFP_ZONEMASK);
281
282 z = (GFP_ZONE_TABLE >> (bit * GFP_ZONES_SHIFT)) &
283 ((1 << GFP_ZONES_SHIFT) - 1);
284 VM_BUG_ON((GFP_ZONE_BAD >> bit) & 1);
285 return z;
286 }
这个函数就是通过GFP flag利用左移和右移操作快速的查找到对应的ZONE index。
有了这个ZONE index就一定会从这个zone中分配内存了吗?还是想的太天真了,对于内存的申请,内核中存在一个fallback列表,当我们从第一选择zone中去查找空闲内存,如果发现该zone中的空闲内存不足,那么此时就返回失败吗?
当然不是,内核会尝试从fallback zone中去查找空闲内存。比如内核根据GFP flag得到一个zone index,对应的ZONE是Normal Zone,而此时Normal Zone中的空闲内存不足申请失败,但是DMA zone中还存在很多空闲内存,那么此时内核就会从DMA zone中去再次去尝试申请。
这个内存申请的fallback顺序为:
MOVABLE=>HIGHMEM=>NORMAL=>DMA32=>DMA
到这里还涉及到一个问题,那么对于每个Zone来说,如果都可以被其他Zone来申请,大家都可以互相申请,那么区分这些zone还有什么意义呢?
实际上并不是如此,为了保证每个zone中的内存不会被其他zone全部申请走,内核针对每个zone定义了一个保留内存:
zone1 : lowmem_reserve[]: 0 0 126976 126976
它是一个数组,比如上面的数组表示系统存在4个zone,假设为zone1,zone2,zone3,zone4。
当zone3尝试从本zone1中申请时需要考虑的预留内存。也就是说zone3判断zone1中是否存在足够的空闲内存用于申请时,需要把这个预留内存给去除掉。具体的计算方法如下:
2360 static bool __zone_watermark_ok(struct zone *z, int order, unsigned long mark,
2361 int classzone_idx, int alloc_flags, long free_pages)
2362 {
2363 /* free_pages my go negative - that's OK */
2364 long min = mark;
2365 long lowmem_reserve = z->lowmem_reserve[classzone_idx];
2366 int o;
2367 long free_cma = 0;
2368
2369 free_pages -= (1 << order) - 1;
2370 if (alloc_flags & ALLOC_HIGH)
2371 min -= min / 2;
2372 if (alloc_flags & ALLOC_HARDER)
2373 min -= min / 4;
2374 #ifdef CONFIG_CMA
2375 /* If allocation can't use CMA areas don't use free CMA pages */
2376 if (!(alloc_flags & ALLOC_CMA))
2377 free_cma = zone_page_state(z, NR_FREE_CMA_PAGES);
2378 #endif
2379
2380 if (free_pages - free_cma <= min + lowmem_reserve)
2381 return false;
2382 for (o = 0; o < order; o++) {
2383 /* At the next order, this order's pages become unavailable */
2384 free_pages -= z->free_area[o].nr_free << o;
2385
2386 /* Require fewer higher order pages to be free */
2387 min >>= 1;
2388
2389 if (free_pages <= min)
2390 return false;
2391 }
2392 return true;
2393 }
通过这个函数来判断某一个zone中针对该GFP申请的内存,是否存在空闲页面来完成此次申请,可以看到其中就使用了lowmem_reserve来综合考虑作为判断结果。
numa
static struct page *
2580 get_page_from_freelist(gfp_t gfp_mask, nodemask_t *nodemask, unsigned int order,
2581 struct zonelist *zonelist, int high_zoneidx, int alloc_flags,
2582 struct zone *preferred_zone, int migratetype)
2583 {
2584 struct zoneref *z;
2585 struct page *page = NULL;
2586 int classzone_idx;
2587 struct zone *zone;
2588 nodemask_t *allowednodes = NULL;/* zonelist_cache approximation */
2589 int zlc_active = 0; /* set if using zonelist_cache */
2590 int did_zlc_setup = 0; /* just call zlc_setup() one time */
2591
2592 classzone_idx = zone_idx(preferred_zone);
2593 zonelist_scan:
2594 /*
2595 * Scan zonelist, looking for a zone with enough free.
2596 * See also cpuset_zone_allowed() comment in kernel/cpuset.c.
2597 */
2598 for_each_zone_zonelist_nodemask(zone, z, zonelist,
2599 high_zoneidx, nodemask) {
最后会尝试在对应nodemask上的zonelist上查找空闲页面,当nodemask为NULL时会扫描所有的node。
from: https://blog.csdn.net/rikeyone/article/details/109638783