内存水位watermark

Linux内存管理中,watermark用于表示系统剩余内存状态。high水位表示内存压力小,low水位触发kswapd回收,min水位是紧急状态。当内存低于min时,会触发直接回收或PF_MEMALLOC分配。watermark设定与系统保留内存相关,可通过/proc/sys/vm调整。了解watermark对于理解Linux内存分配至关重要。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

watermark

struct zone {
	/* Read-mostly fields */

/* zone watermarks, access with *_wmark_pages(zone) macros */
unsigned long watermark[NR_WMARK];
long lowmem_reserve[MAX_NR_ZONES];
...

}

每个内存管理区都有一个数组watermark,内核中定义了三个watermark来表示当前系统剩余的空闲内存。

  • high
    当剩余内存在high以上时,系统认为当前内存使用压力不大。
  • low
    当剩余内存降低到low时,系统就认为内存已经不足了,会触发kswapd内核线程进行内存回收处理
  • min
    当剩余内存在min以下时,则系统内存压力非常大。一般情况下min以下的内存是不会被分配的,min以下的内存默认是保留给特殊用途使用,属于保留的页框,用于原子的内存请求操作。
    比如:当我们在中断上下文申请或者在不允许睡眠的地方申请内存时,可以采用标志GFP_ATOMIC来分配内存,此时才会允许我们使用保留在min水位以下的内存。

图片1

对页面回收的影响

  • kswapd周期回收机制
    kswapd是linux中用于页面回收的内核线程。当空闲内存的值低于low时,内存面临着一定的压力,在这次分配结束后kswapd就会被唤醒,这时内核线程kswapd开始进行回收页面,当kswapd回收页面发现此时内存终于达到了high水位,那么系统认为内存已经不再紧张了,所以将会停止进一步的操作。要注意,在这种情况下,内存分配虽然会触发内存回收,但不存在被内存回收所阻塞的问题,两者的执行关系是异步的。这里所说的"空余内存"其实是一个zone总的空余内存减去其lowmem_reserve的值。对于kswapd来说,要回收多少内存才算完成任务呢?只要把空余内存的大小恢复到"high"对应的watermark值就可以了,当然,这取决于当前空余内存和"high"值之间的差距,差距越大,需要回收的内存也就越多。"low"可以被认为是一个警戒水位线,而"high"则是一个安全的水位线。

  • 内存紧缺直接回收机制
    如果内存达到或者低于min时,这是说明现在内存严重不足,会触发内核直接回收操作(direct reclaim),这是一种默认的操作,此时分配器将同步等待内存回收完成,再进行内存分配。还有一种特殊情况,如果内存分配的请求是带了PF_MEMALLOC标志位的,并且现在空余内存的大小可以满足本次内存分配的需求,那么也将是先分配,再回收。

    使用PF_MEMALLOC("PF"表示per-process flag)相当于是忽略了watermark,因此它对应的内存分配的标志是ALLOC_NO_WATERMARK。能够获取"min"值以下的内存,也意味着该process有动用几乎所有内存的权利,因此它也对应GFP的标志__GFP_MEMALLOC。

    if (gfp_mask & __GFP_MEMALLOC)
    	return ALLOC_NO_WATERMARKS;
    
    if (alloc_flags & ALLOC_NO_WATERMARKS)
            set_page_pfmemalloc(page);
    

    可以在内存严重短缺的时候,拥有不等待回收而强行分配内存的权利的进程其实可以想到kswapd,因为kswapd本身就是负责回收内存的,它只需要占用一小部分内存支撑其正常运行,就可以去回收更多的内存。

    虽然kswapd是在"low"到"min"的这段区间被唤醒加入调度队列的,但当它真正执行的时候,空余内存的值可能已经掉到"min"以下了。可见,"min"值存在的一个意义是保证像kswapd这样的特殊任务能够在需要的时候立刻获得所需内存。

水位的设定

以前的32位的系统会用到ZONE_HIGHMEM因为需要在采用动态映射的方法,也就是使用高端内存地址去寻址,地址空间范围内找一段相应大小空闲的逻辑地址空间,借用一会。借用这段逻辑地址空间,建立映射到想访问的那段物理内存(即填充内核PTE页面表),临时用一会,用完后归还。这样别人也可以借用这段地址空间访问其他物理内存,虽然存在效率问题,但是起码实现了使用有限的地址空间,访问所有所有物理内存。现在我们使用的64位的系统地址空间明显扩大了现阶段基本可以访问到所有的物理地址空间,所以不需要有HIGHMEM ZONE。

水位的设定与系统保留内存的数量有关,存放在min_free_kbytes中,它在内核初始化时设置,取决于直接映射区物理内存的数量。

总的"min"值约等于所有zones可用内存的总和乘以16再开平方的大小,可通过"/proc/sys/vm/min_free_kbytes"查看和修改。

image-20210201114903294

每个管理区(zone)中的min watermark是与min_free_kbytes有关的。

/*
 * Initialise min_free_kbytes.
 *
 * For small machines we want it small (128k min).  For large machines
 * we want it large (64MB max).  But it is not linear, because network
 * bandwidth does not increase linearly with machine size.  We use
 *
 *	min_free_kbytes = 4 * sqrt(lowmem_kbytes), for better accuracy:
 *	min_free_kbytes = sqrt(lowmem_kbytes * 16)
 *
 * which yields
 *
 * 16MB:	512k
 * 32MB:	724k
 * 64MB:	1024k
 * 128MB:	1448k
 * 256MB:	2048k
 * 512MB:	2896k
 * 1024MB:	4096k
 * 2048MB:	5792k
 * 4096MB:	8192k
 * 8192MB:	11584k
 * 16384MB:	16384k
 */
int __meminit init_per_zone_wmark_min(void)
{
	unsigned long lowmem_kbytes;
	int new_min_free_kbytes;

	lowmem_kbytes = nr_free_buffer_pages() * (PAGE_SIZE >> 10);
	new_min_free_kbytes = int_sqrt(lowmem_kbytes * 16);//min_free_kbytes设置

	if (new_min_free_kbytes > user_min_free_kbytes) {
		min_free_kbytes = new_min_free_kbytes;
		if (min_free_kbytes < 128)
			min_free_kbytes = 128;//下限128
		if (min_free_kbytes > 65536)
			min_free_kbytes = 65536;//上限65536
	} else {
		pr_warn("min_free_kbytes is not updated to %d because user defined value %d is preferred\n",
				new_min_free_kbytes, user_min_free_kbytes);
	}
	setup_per_zone_wmarks();
	refresh_zone_stat_thresholds();
	setup_per_zone_lowmem_reserve();

#ifdef CONFIG_NUMA
	setup_min_unmapped_ratio();
	setup_min_slab_ratio();
#endif

	return 0;
}
core_initcall(init_per_zone_wmark_min)
#define min_wmark_pages(z) (z->watermark[WMARK_MIN])

static void __setup_per_zone_wmarks(void)
{
	unsigned long pages_min = min_free_kbytes >> (PAGE_SHIFT - 10);
	unsigned long lowmem_pages = 0;
	struct zone *zone;
	unsigned long flags;

	/* Calculate total number of !ZONE_HIGHMEM pages */
	for_each_zone(zone) {
		if (!is_highmem(zone))//不是高端内存就等于全部的内存分配系统管理的页框数
			lowmem_pages += zone->managed_pages;
	}

	for_each_zone(zone) {
		u64 tmp;

		spin_lock_irqsave(&zone->lock, flags);
		tmp = (u64)pages_min * zone->managed_pages;
		do_div(tmp, lowmem_pages);
		if (is_highmem(zone)) {//高端内存区域
			/*
			 * __GFP_HIGH and PF_MEMALLOC allocations usually don't
			 * need highmem pages, so cap pages_min to a small
			 * value here.
			 *
			 * The WMARK_HIGH-WMARK_LOW and (WMARK_LOW-WMARK_MIN)
			 * deltas control asynch page reclaim, and so should
			 * not be capped for highmem.
			 */
			unsigned long min_pages;

			min_pages = zone->managed_pages / 1024;
			min_pages = clamp(min_pages, SWAP_CLUSTER_MAX, 128UL);
			zone->watermark[WMARK_MIN] = min_pages;
		} else {//低端内存区
			/*
			 * If it's a lowmem zone, reserve a number of pages
			 * proportionate to the zone's size.
			 */
			zone->watermark[WMARK_MIN] = tmp;
		}

		/*
		 * Set the kswapd watermarks distance according to the
		 * scale factor in proportion to available memory, but
		 * ensure a minimum size on small systems.
		 */
		tmp = max_t(u64, tmp >> 2,
			    mult_frac(zone->managed_pages,
				      watermark_scale_factor, 10000));//tmp变为原来的4分之1

		zone->watermark[WMARK_LOW]  = min_wmark_pages(zone) + tmp;//low比min多4分之1
		zone->watermark[WMARK_HIGH] = min_wmark_pages(zone) + tmp * 2;//high比min多了2分之1

		spin_unlock_irqrestore(&zone->lock, flags);
	}

	/* update totalreserve_pages */
	calculate_totalreserve_pages();
}

watermark_scale_factor"这个系数,其默认值为10,对应内存占比0.1%(10/10000),可通过”/proc/ sys/vm/watermark_scale_factor"设置,最大为1000。当它的值被设定为1000时,意味着"low"与"min"之间的差值,以及"high"与"low"之间的差值都将是内存大小的10%(1000/10000)。

一个zone的"low"和"high"的值都是根据它的"min"值算出来的,"low"比"min"的值大1/4左右,"high"比"min"的值大1/2左右,三者的比例关系大致是4:5:6。

使用"cat /proc/zoneinfo"可以查看这三个值的大小(注意这里是以page为单位的):

image-20210201171727479

你可以把"/proc/zoneinfo"中所有zones的"min"值加起来乘以4(如果page size是4KiB的话),看一下是不是基本等于"/proc/sys/vm"中的"min_free_kbytes"的值。

image-20210201171645320

(91+11172)*4=45052≈45056

小结

watermark这部分对于内存分配影响是至关重要的,也是理解内存的一个重要的点。

<think>好的,用户问的是Linux内存管理中的三种水位值在哪里定义。首先,我需要回忆一下Linux内核中内存管理相关的数据结构。内存管理涉及到zone的概念,每个内存区域(如ZONE_DMA、ZONE_NORMAL等)都有对应的结构体,应该是struct zone。 接下来,水位值应该是在这个结构体里面定义的。我记得水位值包括min、low、high三个,分别对应不同的内存压力情况。需要确认struct zone的具体定义,查看内核源码中的include/linux/mmzone.h文件。在这个头文件中,struct zone结构体确实包含了watermark数组,通常会有三个元素对应这三个水位。 另外,水位值的计算应该是在内核启动时或者内存初始化阶段进行的,可能是在init_per_zone_wmark_min函数中设置的。这里需要提到水位值如何根据物理内存大小和其他因素计算得出,比如通过calculate_zone_watermarks函数。 用户可能还想知道这些水位值的作用,比如在分配内存时,当可用内存低于low时,kswapd进程会被唤醒回收内存,而低于min时则会直接回收。不过用户的问题主要是关于定义的位置,所以重点放在结构体和头文件上。 需要确保回答准确,给出具体的代码位置和结构体成员名称。同时,要使用中文,结构清晰,分步骤说明,让用户能够理解并找到相关的定义。</think>在Linux内核中,内存管理中的三种水位值(Watermark)定义在内存区域(zone)的数据结构中。以下是具体分析步骤: 1. **数据结构定位** 水位值定义在`struct zone`结构体中,该结构体在内核源码的`include/linux/mmzone.h`文件中。 关键成员如下: ```c struct zone { unsigned long _watermark[NR_WMARK]; // 包含min/low/high三个水位 // 其他成员... }; ``` 其中`NR_WMARK`是枚举类型,明确对应三个水位: ```c enum zone_watermarks { WMARK_MIN, // 最低警戒水位 WMARK_LOW, // 低水位(触发异步回收) WMARK_HIGH, // 高水位内存较充足) NR_WMARK }; ``` 2. **水位值初始化** 水位值在内核启动阶段通过`init_per_zone_wmark_min()`函数计算,代码位于`mm/page_alloc.c`。计算逻辑会考虑以下因素: - 物理内存总量 - `vm.min_free_kbytes`内核参数(可通过`/proc/sys/vm/min_free_kbytes`调整) - 内存区域(zone)的大小比例 3. **水位值的作用逻辑** - 当**空闲内存 < high**:允许普通内存分配 - 当**空闲内存 < low**:唤醒kswapd进程进行异步回收 - 当**空闲内存 < min**:触发直接回收(direct reclaim),分配线程同步阻塞等待内存释放 4. **查看实际水位值** 可通过`/proc/zoneinfo`查看运行时水位值: ``` cat /proc/zoneinfo | grep -E 'Node|min|low|high' ``` 输出示例: ``` Node 0, zone Normal pages free 43912 min 1448 low 1810 high 2172 ``` 总结:三种水位值的核心定义在`struct zone`的`_watermark`数组中,通过内存区域划分和动态计算实现细粒度的内存压力管理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值