Kernel那些事儿之内存管理(5) --- 衣带渐宽终不悔(上)

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有可能空闲页面很多,但是连续的空闲页面不多。

时间: 2024-12-26 03:41:46

Kernel那些事儿之内存管理(5) --- 衣带渐宽终不悔(上)的相关文章

Kernel那些事儿之内存管理(6) --- 衣带渐宽终不悔(下)

接着上篇写,继续介绍zone allocator.上一篇介绍了周边,现在来看看它的全貌 --- 函数__alloc_pages(). Kernel源代码里是这样注释函数__alloc_pages()的.其重要地位可见一斑. 1451 /* 1452  * This is the 'heart' of the zoned buddy allocator. 1453  */ __alloc_pages()的工作模式很清晰:利用函数get_page_from_freelist()多次遍历zonelis

Kernel那些事儿之内存管理(7) --- Slab(上)

前面讲的buddy system算法,分配内存的最小单位是一个页面(例如 4K).这对于大的内存申请比较适用.可是实际生活中,Kernel经常需要分配小的内存空间,比如几十个字节,这个时候怎么办呢? 不同的人可能会想到不同的解决办法. 对于财大气粗的富人一族,办法很简单:申请一个页面,只用其中的几十字节,剩下的直接丢掉. 对于锱铢必较的穷人一族,就不敢这么挥霍了:申请一个页面,然后记录好页面内哪些内存区用了,哪些还没用,没用的部分可以用来满足其他的内存分配请求.总之务必做到物尽其用. 很不幸,K

Kernel那些事儿之内存管理(10) --- 映射(上)

前面花了好多时间讲了内存管理中 node, zone, page frame, buddy system等.这些都是物理地址空间中的概念.然而,对于一个进程来说,它看到的却是完全不同的地址空间. 我们接下来就来看看这些地址空间,以及它们之间的映射. 1.内存地址 在内存管理中,会涉及到三种内存地址. 逻辑地址:程序的机器语言指令里,指定一条指令或是一个操作数的地址时,所使用的实际上都是逻辑地址.一个逻辑地址由两部分组成:段选择符和段偏移值. 线性地址:逻辑地址经过分段映射后,得到的就是线性地址.

Kernel那些事儿之内存管理(1)

有人的地方就有江湖.要介绍内存管理这个江湖,首先还得从这里面的主要人物讲起. 在NUMA结构中,物理内存首先被分成若干nodes.每一个node进一步被分成若干zones.每一个zone又关联了一个描述page frames的数组,该数组包含了属于该zone的所有page frame的描述符. 不难看出,在这个江湖里主要有三位重要人物:nodes, zones 和 page frames.这三者的关系和地位大体可以用下图来描述(该图取自"Professional Linux Kernel Arc

Kernel那些事儿之内存管理(3) --- 久别重逢

上次我们讲到page frame是物理内存的基本组成单位.那Kernel就必须要有一套机制来管理空闲的page frames.这一点不难理解.每个县长必须要把本县可用的劳动力登记在册,这样哪天皇帝要征兵了,你才不至于手忙脚乱. 这个问题看似简单,实则不然.因为这里面有一个外碎片的问题. 在物理内存中,连续的物理内存页有时是很重要的.例如在DMA操作中,由于大部分DMA处理器都没有分页机制,它们会直接访问物理内存地址,因此DMA 所用的缓冲区在物理地址空间必须连续:再例如,使用连续的物理内存页,可

Kernel那些事儿之内存管理(8) --- Slab(中)

上篇讲了Slab中的数据结构,这篇该讲Slab中的操作了. 既然是内存管理,那操作无非就两点:allocate 和 free. 1. 申请一个object 在Slab中,申请一个object是通过函数 kmem_cache_alloc() 来完成的. 3618 void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags) 3619 { 3620     return __cache_alloc(cachep, flags, __bu

Kernel那些事儿之内存管理(2) --- 百闻不如一见

上次介绍了物理内存管理中三位主要人物中的node 和zone.这两位是当官的,一个是县长,一个是里长,不敢不先介绍啊.接下来出场的就是我们的老百姓了 --- page frame. Page frame是物理内存的基本组成单位,在Kernel中由结构体 struct page 来描述. struct page {     unsigned long flags;          atomic_t _count;          union {         atomic_t _mapcou

Kernel那些事儿之内存管理(13) --- 内核映射(下)

前面讲过,针对于内核地址空间中后面的128MB空间,Kernel提供了三种机制来映射物理内存.之前讲过了两种,即持久内核映射和临时内核映射.这两种机制的目的都是一样的:使Kernel能够访问到高端内存. 今天讲一下第三种机制:非连续内存分配,也就是vmalloc.这个机制同样可以使Kernel能够访问到高端内存,不过这不是该机制的主要目的.该机制的主要目的是:把物理上不连续的页面映射到连续的内核线性地址空间中. 非连续内存区域管理 既然是映射,肯定会涉及到三个元素:集合L,集合P,映射M. 集合

Kernel那些事儿之内存管理(11) --- 内核映射(上)

前面简单地介绍了三种不同的地址空间,接下来重点讲述线性地址空间到物理地址空间的映射. 我们先从32位系统开始. 在32位系统中,线性地址空间的大小为 2^32,即4GB.Kernel一般会按照 3:1 的比例,把线性地址空间分为两部分: 0~3GB 用户地址空间 3GB~4GB 内核地址空间. 用户地址空间的管理和映射是个大的topic.我们后面再详细讲述. 内核地址空间只有1GB大小,最多只能映射1GB的物理内存.那问题来了:1GB之外物理内存怎么办? Kernel 给出的解决办法是,把1GB