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水位以下的内存。
对页面回收的影响
-
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"查看和修改。
每个管理区(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为单位的):
你可以把"/proc/zoneinfo"中所有zones的"min"值加起来乘以4(如果page size是4KiB的话),看一下是不是基本等于"/proc/sys/vm"中的"min_free_kbytes"的值。
(91+11172)*4=45052≈45056
小结
watermark这部分对于内存分配影响是至关重要的,也是理解内存的一个重要的点。