Kernel中负责分配一个连续内存页块的子系统一般被称为zoned page frame allocator。前面讲了函数 buffered_rmqueue() 是如何从指定zone的buddy system中分配一个连续内存页块的。这个函数貌似完成了内存页块分配相关的所有工作,然而实际上,这个函数只是zone allocator的冰山一角。
记得我刚上大学那会,拥有一个MP3还是一件能够令男生羡慕、令女生着迷的事情。于是我咬咬牙,花了近一个月的生活费买了一个漂亮的MP3,从此过上了非凡的生活。。。
可惜好景不长,没过多久就坏掉了。我拿去维修,只见一个年轻小伙拿着电烙铁点了几下,居然就修好了。给我要很贵的维修费,我就很不情愿,说:“就点了几下,要这么贵啊。” 然后那小伙回答:“点这几下不贵,但是知道往哪里点,这就贵了。” 我深以为然,重重地点了点头,心甘情愿地把钱交了。
同样的道理,buffered_rmqueue()从指定zone的buddy system中分配内存页块,这个不复杂;但是能够确定从哪个zone中分配,以及确定某个zone什么情况下可以分配,这个就复杂多了。而这,正是zone allocator的核心所在。
在正式介绍zone allocator之前,先介绍一些周边事务。
1. zonelist
这个东东在之前的博文里经过,直接copy过来:
node_zonelists: 当我们要在某一个指定的zone中分配内存,而该zone中又没有足够多的空闲内存时,怎么办?正所谓狡兔三窟,我们得给自己留点后路,node_zonelists正是这种情况下的后路。它指定了所有的备选的zones。当然这些备选zones也是有优先顺序的,毕竟只有小三也不能满足需求时,才会再去找小四。
2. zone_watermark_ok()
一个zone在用来分配内存页块之前,得先有人把把关,姓甚名谁,家里几口人,人均几亩地,地里几头牛,总得检查检查。这个函数就是这样的把关函数。
这个函数会用到一些flags。
1112 #define ALLOC_NO_WATERMARKS 0x01 /* don't check watermarks at all */ 1113 #define ALLOC_WMARK_MIN 0x02 /* use pages_min watermark */ 1114 #define ALLOC_WMARK_LOW 0x04 /* use pages_low watermark */ 1115 #define ALLOC_WMARK_HIGH 0x08 /* use pages_high watermark */ 1116 #define ALLOC_HARDER 0x10 /* try to alloc harder */ 1117 #define ALLOC_HIGH 0x20 /* __GFP_HIGH set */ 1118 #define ALLOC_CPUSET 0x40 /* check for correct cpuset */
这些flags用来指定把关时使用哪个watermark值。
1219 int zone_watermark_ok(struct zone *z, int order, unsigned long mark, 1220 int classzone_idx, int alloc_flags) 1221 { 1222 /* free_pages my go negative - that's OK */ 1223 long min = mark; 1224 long free_pages = zone_page_state(z, NR_FREE_PAGES) - (1 << order) + 1; 1225 int o; 1226 1227 if (alloc_flags & ALLOC_HIGH) 1228 min -= min / 2; 1229 if (alloc_flags & ALLOC_HARDER) 1230 min -= min / 4; 1231 1232 if (free_pages <= min + z->lowmem_reserve[classzone_idx]) 1233 return 0; 1234 for (o = 0; o < order; o++) { 1235 /* At the next order, this order's pages become unavailable */ 1236 free_pages -= z->free_area[o].nr_free << o; 1237 1238 /* Require fewer higher order pages to be free */ 1239 min >>= 1; 1240 1241 if (free_pages <= min) 1242 return 0; 1243 } 1244 return 1; 1245 }
该函数首先确定该zone中的空闲页面个数,以及用于threshold的变量min的值。
从 1227 ~ 1230 可以看到,如果设置了ALLOC_HIGH 或 ALLOC_HARDER,min的值会进一步减少,这也就意味着把关条件放松,分配会更加aggressive。
然后开始把关测试,如果满足以下两个条件,则测试通过:
1)除去要分配的页面个数外,该zone中至少要包含这么多空闲页面:
min 个空闲页面
lowmem_reserve 中指定要预留的页面个数
这里lowmem_reserve是Kernel的一个非常聪明的机制,用来防止来自higher zone的分配请求过多的消耗lower zone中的内存。
这个有点不好理解,举个例子,假设zone->lowmem_reserve[ZONE_HIGHMEM] 的值为R,这个值表示的是:对于来自ZONE_HIGHMEM的内存分配请求,若是想从本zone中分配页面,那么你至少要给我保留R个页面。
2)除去要分配的页面个数外,从order k 到 order 10的空闲页面总数,至少得是 min/(2^k)。其中k 取值范围是从1 到 参数指定的order值。
这个更不好理解了,还是举个例子。如果 k=2 的话,则从order 2 到order 10的空闲页面总数,至少得是 min/4。
如果满足以上两点,恭喜你,通过了把关测试。
3. get_page_from_freelist()
有了前面的准备,这个函数的工作就很轻松了。
它会按序遍历zonelist中所有的zone,对于每个zone,先用函数zone_watermark_ok()来把把关,如果把关通过,再用函数buffered_rmqueue()来从buddy system中申请内存页块。
1371 static struct page * 1372 get_page_from_freelist(gfp_t gfp_mask, unsigned int order, 1373 struct zonelist *zonelist, int alloc_flags) 1374 { 1375 struct zone **z; 1376 struct page *page = NULL; 1377 int classzone_idx = zone_idx(zonelist->zones[0]); 1378 struct zone *zone; ... 1383 1384 zonelist_scan: 1385 /* 1386 * Scan zonelist, looking for a zone with enough free. 1387 * See also cpuset_zone_allowed() comment in kernel/cpuset.c. 1388 */ 1389 z = zonelist->zones; 1390 1391 do { ... 1407 zone = *z; 1408 if ((alloc_flags & ALLOC_CPUSET) && 1409 !cpuset_zone_allowed_softwall(zone, gfp_mask)) 1410 goto try_next_zone; 1411 1412 if (!(alloc_flags & ALLOC_NO_WATERMARKS)) { 1413 unsigned long mark; 1414 if (alloc_flags & ALLOC_WMARK_MIN) 1415 mark = zone->pages_min; 1416 else if (alloc_flags & ALLOC_WMARK_LOW) 1417 mark = zone->pages_low; 1418 else 1419 mark = zone->pages_high; 1420 if (!zone_watermark_ok(zone, order, mark, 1421 classzone_idx, alloc_flags)) { 1422 if (!zone_reclaim_mode || 1423 !zone_reclaim(zone, gfp_mask, order)) 1424 goto this_zone_full; 1425 } 1426 }
cpuset_zone_allowed_softwall()用来做cpuset相关检查,暂且忽略。
该函数首先解析ALLOC_* flags,从而确定使用哪个watermark。watermark的高低,直接影响到了把关时是宽松还是严格。
注意,如果设置了ALLOC_NO_WATERMARKS,则跳过把关函数,无条件放行。
然后利用函数zone_watermark_ok()来检查该zone中是否有足够多的空闲页面。
如果把关通过,接下来就是与buddy system打交道了,这个工作由之前讲过的buffered_rmqueue()来完成。
1428 page = buffered_rmqueue(zonelist, zone, order, gfp_mask); 1429 if (page) 1430 break; 1431 this_zone_full: 1432 ... 1434 try_next_zone: ... 1441 } while (*(++z) != NULL); 1442 ... 1448 return page; 1449 }
需要注意的是,即使通过了zone_watermark_ok()的考验,也不一定能够顺利地从buddy system中分配到需要的内存。一个zone有可能空闲页面很多,但是连续的空闲页面不多。