首发于术道经纬
Linux内存调节之lowmem reserve

Linux内存调节之lowmem reserve

说明:本文所使用的图片和示例均来自于这篇文章,虽然图片中有一些韩文,但加上文字说明后,相信不会影响大家的理解。直接浏览图片显示效果不佳,可在图片上点击放大镜按钮获得更清晰的视图。

在Linux的物理内存模型中,一个内存node按照属性被划分为了多个zones,比如ZONE_DMA和ZONE_NORMAL,在32位系统中,还有ZONE_HIGHMEM(参考这篇文章)。如果没有通过GFP标志位做出限定,那么当ZONE_HIGHMEM中内存不足时,可从更低位的ZONE_NORMAL中分配,ZONE_NORMAL中内存不足时,可从更低位的ZONE_DMA中分配。这里的“低位”是指zone的物理内存的地址更小。

这里所谓的“不足”就是当前zone的空余内存低于了本次内存分配的请求大小。除了最高位的ZONE_HIGHMEM,其他zones都会为比它更高位的zone单独划分出一片内存作为预留,这部分内存被称作"lowmem reserve"。如果说上文讲的watermark是zone保有的自留田,那么这个"lowmem reserve"就是给高位zones提供的后花园。

当你查看"/proc/sys/vm/lowmem_reserve_ratio"的值时,会得到一组类似这样的输出:

设ZONE_DMA, ZONE_NORMAL和ZONE_HIGHMEM的内存大小分别是A, B和C,那么在ZONE_DMA中,需要给ZONE_NORMAL和ZONE_HIGHMEM预留的内存大小分别是B/256和(B+C)/256。在ZONE_NORMAL中,需要给ZONE_HIGHMEM预留的内存大小是C/32。分母来自于zone自己的"lowmem_reserve_ratio",而分子则来自于其他的zones。

在Linux的实现中,是以struct zone中的lowmem_reserve[MAX_NR_ZONES]来表示这种预留内存的。同上文讲的watermark一样,也是以page为计算单位的。如果这3个zones的大小分别是16MiB, 784MiB和200MiB,且page的大小是4KiB,根据1MiB包含256个pages,那么这3个zones就分别包含4096, 190464, 51200个pages。

所以,ZONE_DMA给ZONE_NORMAL预留的将是190464/256=784个pages,给ZONE_HIGHMEM预留的将是(190464+51200)/256=984个pages。ZONE_NORMAL给ZONE_HIGHMEM预留的则是51200/32=1600个pages。这种关系可以用一个3x3的矩阵来表示:

可通过"cat /proc/zoneinfo"中的"protection"部分查看各个zones预留pages的数目,这里叫"protection"是由于历史原因,因为早期开发lowmem_reserve功能时用的名称就是"protection"。

设置每个zone的lowmem_reserve[]的函数是setup_per_zone_lowmem_reserve():

static void setup_per_zone_lowmem_reserve(void)
{
    for (j = 0; j < MAX_NR_ZONES; j++) {
	struct zone *zone = pgdat->node_zones + j;
	unsigned long managed_pages = zone_managed_pages(zone);

	idx = j;
	while (idx) {
		struct zone *lower_zone;

		idx--;
		lower_zone = pgdat->node_zones + idx;

		if (sysctl_lowmem_reserve_ratio[idx] < 1) {
		    sysctl_lowmem_reserve_ratio[idx] = 0;
		    lower_zone->lowmem_reserve[j] = 0;
		} else {
		    lower_zone->lowmem_reserve[j] =
		    managed_pages / sysctl_lowmem_reserve_ratio[idx];
		}
		managed_pages += zone_managed_pages(lower_zone);
	    }
	}

	calculate_totalreserve_pages();
}

结合上面的示例,这段代码的逻辑可以用下图表示:

当ZONE_HIGHMEM内存不足,分配fail的时候,就将"fallback"到ZONE_NORMAL,尝试从ZONE_NORMAL给它预留的1600个pages中分配,如果还不够,就要继续"fallback"到ZONE_DMA,从ZONE_DMA给它预留的984个pages中分配。

同理,当ZONE_NORMAL内存不足时,就会"fallback"到ZONE_DMA,尝试从ZONE_DMA给它预留的784个pages中分配。

ZONE_NORMAL和ZONE_DMA都是线性映射,使用方便,ZONE_DMA还可以满足一些特殊的DMA设备对地址的要求,所以我们通常是希望尽量不要动用低位zones的内存的。当不可避免地需要"fallback"时,得看下低位zones本身的空余内存是否充足。

只有一个zone空余内存的值大于它自己的watermark[WMARK_HIGH]值加上给某个高位zone预留的内存值,才算是“充足”的,对应的高位zone才可以从这个低位zone中截取内存。也就是说,低位zone会优先保证自己的内存分配,确实有一定的富余的情况下,才可以提供给更高位的zones。

假设现在系统的内存统计情况如下:

因为1000>100+784,而2000<500+1600,所以此时ZONE_NORMAL到ZONE_DMA的"fallback"是可以成功的,而ZONE_HIGHMEM到ZONE_NORMAL的"fallback"则不可以。

在setup_per_zone_lowmem_reserve()的代码最后,还有个calculate_totalreserve_pages(),这里"totalreserve"是指所有zones的high watermark加上lowmem_reserve[]的值,因为不管是“自留田”还是“后花园”,都是一个zone需要预留的内存。

ZONE_DMA和ZONE_DMA32默认的"lowmem_reserve_ratio"的值是256,其他zones的默认值是32。如果你的系统中低位zones的内存占比较大,那么可以将这个ratio值调小一些,也就是预留更多的内存给高位zones,反之则可以调大一些。


原创文章,转载请注明出处。

编辑于 2019-12-11

文章被以下专栏收录