vm内核参数之内存水位min_free_kbytes和保留内存lowmem_reserve_ratio

2024-05-04 08:32

本文主要是介绍vm内核参数之内存水位min_free_kbytes和保留内存lowmem_reserve_ratio,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

注:本文分析基于3.10.0-693.el7内核版本,即CentOS 7.4

1、zone内存水位值

系统内存的每个node上都有不同的zone,每个zone的内存都有对应的水位线,当内存使用达到某个阈值时就会触发相应动作,比如直接回收内存,或者启动kswap进行回收内存。我们可以通过查看/proc/zoneinfo来确认每个zone的min、low、high水位值。

[root@centos7 ~]# cat /proc/zoneinfo | grep -E "Node|min|low|high "
Node 0, zone      DMAmin      92low      115high     138
Node 0, zone    DMA32min      10839low      13548high     16258
Node 1, zone    DMA32min      5706low      7132high     8559
Node 1, zone   Normalmin      5890low      7362high     8835

而这些内存的水位值就由/proc/sys/vm/min_free_kbytes这个参数控制。

2、zone内存保留值

系统在分配内存时,有可能会出现跨zone分配内存的情况,比如,如果我们想要在zone的normal区域分配一个order为6大小的内存块,但是由于normal内存使用紧张,无法提供该大小的内存块,就会往下到DMA32的zone空间去分配。一两次没关系,如果每次都这么操作,那么DMA32的内存就会被耗光;等到某些应用程序需要该zone的内存时无法分配,特别对于某些只能使用特定zone内存的程序。因此对于跨zone使用内存需要控制,因此在考虑是否能进行跨zone分配时,需要参考该zone对应的lowmem_reserve内存。该值就由/proc/sys/vm/lowmem_reserve_ratio控制。

3、内核初始化zone内存水位线

内核在初始化阶段会调用init_per_zone_wmark_min来进行每个zone的内存水位线初始化,同时也会设置每个zone的lowmem_reserve值。

/** 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)*/
int __meminit init_per_zone_wmark_min(void)
{unsigned long lowmem_kbytes;int new_min_free_kbytes;//获取系统空闲内存值,扣除每个zone的high水位值后的总和lowmem_kbytes = nr_free_buffer_pages() * (PAGE_SIZE >> 10);//根据上述公式计算new_min_free_kbytes值new_min_free_kbytes = int_sqrt(lowmem_kbytes * 16);if (new_min_free_kbytes > user_min_free_kbytes) {min_free_kbytes = new_min_free_kbytes;//最小128kif (min_free_kbytes < 128)min_free_kbytes = 128;//最大65M,但是这只是系统初始化的值,可以通过proc接口设置范围外的值if (min_free_kbytes > 65536)min_free_kbytes = 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);}//设置每个zone的min low high水位值setup_per_zone_wmarks();refresh_zone_stat_thresholds();//设置每个zone为其他zone的保留内存setup_per_zone_lowmem_reserve();setup_per_zone_inactive_ratio();return 0;
}

系统初始化里min_free_kbytes的值介于128k~65M之间,但是通过proc接口设置就没这个限制,

int min_free_kbytes_sysctl_handler(ctl_table *table, int write, void __user *buffer, size_t *length, loff_t *ppos)
{int rc;rc = proc_dointvec_minmax(table, write, buffer, length, ppos);if (rc)return rc;if (write) {user_min_free_kbytes = min_free_kbytes;//直接根据用户的值设置min、low、high水位值setup_per_zone_wmarks();}return 0;
}

然后就是进入setup_per_zone_wmarks计算每个zone的min、low、high水位线,因为需要考虑多个zone,因此这个min_free_kbytes需要按比例分配给各个zone。

void setup_per_zone_wmarks(void)
{mutex_lock(&zonelists_mutex);__setup_per_zone_wmarks();mutex_unlock(&zonelists_mutex);
}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;//统计非ZONE_HIGHMEM的内存总量for_each_zone(zone) {if (!is_highmem(zone))lowmem_pages += zone->managed_pages;}//针对每个zone设置min low high水位线for_each_zone(zone) {u64 tmp;spin_lock_irqsave(&zone->lock, flags);//以下两个语句就是按当前zone内存量占总的内存量的大小来分配min的水位值//因为所有zone的min水位相加才是真正的min_free_kbytestmp = (u64)pages_min * zone->managed_pages;do_div(tmp, lowmem_pages);//64位机器上不会有highmem区域,因此不考虑该情况if (is_highmem(zone)) {...} else {//设置min水位值zone->watermark[WMARK_MIN] = tmp;}//low水位值=5/4的min水位值zone->watermark[WMARK_LOW]  = min_wmark_pages(zone) + (tmp >> 2);//high水位值=3/2的min水位值zone->watermark[WMARK_HIGH] = min_wmark_pages(zone) + (tmp >> 1);__mod_zone_page_state(zone, NR_ALLOC_BATCH,high_wmark_pages(zone) -low_wmark_pages(zone) -zone_page_state(zone, NR_ALLOC_BATCH));setup_zone_migrate_reserve(zone);spin_unlock_irqrestore(&zone->lock, flags);}//更新totalreserve_pages的值calculate_totalreserve_pages();
}

所以总的来说,

  • watermark[WMARK_MIN] = min_free_kbytes/4*zone.pages/zone.allpages
  • watermark[WMARK_LOW] = 5/4*watermark[WMARK_MIN]
  • watermark[WMARK_HIGH] = 3/2*watermark[WMARK_MIN]

设置完内存水位线后,会更新totalreserve_pages的值,这个值用于评估系统正常运行时需要使用的内存,该值会作用于overcommit时,判断当前是否允许此次内存分配。

不过由于此时各个zone的lowmem_reserve值还未设置,因此这里我们先不分析,我们先看lowmem_reserve值是如何设置,毕竟totalreserve_pages的值会在设置lowmem_reserve后再次更新。

那么我们就进入setup_per_zone_lowmem_reserve,看看lowmem_reserve值是如何设置的,

/** setup_per_zone_lowmem_reserve - called whenever*	sysctl_lower_zone_reserve_ratio changes.  Ensures that each zone*	has a correct pages reserved value, so an adequate number of*	pages are left in the zone after a successful __alloc_pages().*/
static void setup_per_zone_lowmem_reserve(void)
{struct pglist_data *pgdat;enum zone_type j, idx;//遍历每个nodefor_each_online_pgdat(pgdat) {//遍历每个zone,假设系统上有ZONE_DMA,ZONE_DMA32,ZONE_NORMAL三个zone类型for (j = 0; j < MAX_NR_ZONES; j++) {struct zone *zone = pgdat->node_zones + j;unsigned long managed_pages = zone->managed_pages;//j=0,则zone[DMA].lowmem_reserve[DMA] = 0//j=1,则zone[DMA32].lowmem_reserve[DMA32] = 0//j=2,则zone[NORMAL].lowmem_reserve[NORMAL] = 0//这里的意思就是对于自身zone的内存使用,不需要考虑保留zone->lowmem_reserve[j] = 0;idx = j;while (idx) {struct zone *lower_zone;idx--;if (sysctl_lowmem_reserve_ratio[idx] < 1)sysctl_lowmem_reserve_ratio[idx] = 1;lower_zone = pgdat->node_zones + idx;//j=0,不会进入循环//j=1,idx=0,则zone[DMA].lowmem_reserve[DMA32] = zone[DMA32].pages/sysctl_lowmem_reserve_ratio[DMA]//j=2,idx=1,则zone[DMA32].lowmem_reserve[NORMAL] = zone[NORMAL].pages/sysctl_lowmem_reserve_ratio[DMA32]//    idx=0,则zone[DMA].lowmem_reserve[NORMAL] = zone[NORMAL+DMA32].pages/sysctl_lowmem_reserve_ratio[DMA]  lower_zone->lowmem_reserve[j] = managed_pages /sysctl_lowmem_reserve_ratio[idx];managed_pages += lower_zone->managed_pages;}}}//再次更新totalreserve_pages的值calculate_totalreserve_pages();
}

由于内存分配只可能向下分配,不可能向上分配,即需要DMA的内存不可能到DMA32或者NORMAL上分配,
同理需要DMA32的内存也不可能到NORMAL上分配,因此不存在zone[DMA32].lowmem_reserve[DMA],
也不存在zone[NORMAL].lowmem_reserve[DMA]和zone[NORMAL].lowmem_reserve[DMA32],

整理该过程,我们有以下计算方式,

zone[DMA].lowmem_reserve[DMA] = 0
zone[DMA].lowmem_reserve[DMA32] = zone[DMA32].pages/sysctl_lowmem_reserve_ratio[DMA]
zone[DMA].lowmem_reserve[NORMAL] = zone[NORMAL+DMA32].pages/sysctl_lowmem_reserve_ratio[DMA] zone[DMA32].lowmem_reserve[DMA32] = 0
zone[DMA32].lowmem_reserve[NORMAL] = zone[NORMAL].pages/sysctl_lowmem_reserve_ratio[DMA32]zone[NORMAL].lowmem_reserve[NORMAL] = 0

总的来说就是为了避免跨zone分配内存将下级zone内存耗光的情况。

设置lowmem_reserve后就会再次更新totalreserve_pages的值,

/** calculate_totalreserve_pages - called when sysctl_lower_zone_reserve_ratio*	or min_free_kbytes changes.*/
static void calculate_totalreserve_pages(void)
{struct pglist_data *pgdat;unsigned long reserve_pages = 0;enum zone_type i, j;//遍历每个nodefor_each_online_pgdat(pgdat) {//遍历每个zonefor (i = 0; i < MAX_NR_ZONES; i++) {struct zone *zone = pgdat->node_zones + i;unsigned long max = 0;/* Find valid and maximum lowmem_reserve in the zone *///查找当前zone中,为上级zone内存类型最大的保留值for (j = i; j < MAX_NR_ZONES; j++) {if (zone->lowmem_reserve[j] > max)max = zone->lowmem_reserve[j];}/* we treat the high watermark as reserved pages. *///每个zone的high水位值和最大保留值之和当做是系统运行保留阈值max += high_wmark_pages(zone);if (max > zone->managed_pages)max = zone->managed_pages;reserve_pages += max;/** Lowmem reserves are not available to* GFP_HIGHUSER page cache allocations and* kswapd tries to balance zones to their high* watermark.  As a result, neither should be* regarded as dirtyable memory, to prevent a* situation where reclaim has to clean pages* in order to balance the zones.*/zone->dirty_balance_reserve = max;}}dirty_balance_reserve = reserve_pages;//这个totalreserve_pages在overcommit时会使用到//该值作为系统正常运行的最低保证totalreserve_pages = reserve_pages;
}

4、系统验证

通过代码分析后我们了解了内存的min、low、high水位线,以及每个zone的lowmem_reserve,下面我们就来验证下系统上这些值是否符合我们的分析。

首先是min、low、high水位线和min_free_kbytes,我们以min值为例,

[root@centos7 ~]# echo 90112 > /proc/sys/vm/min_free_kbytes
[root@centos7 ~]# cat /proc/sys/vm/min_free_kbytes
90112
[root@centos7 ~]# cat /proc/zoneinfo | grep -E "Node|managed|min"
Node 0, zone      DMAmin      92managed  3977
Node 0, zone    DMA32min      10844managed  467178
Node 1, zone    DMA32min      5703managed  245716
Node 1, zone   Normalmin      5887managed  253642

由此有,

Node[0].DMA.min=3977/4*90112/(3977+467178+245716+253642)=92
Node[0].DMA32.min=467178/4*90112/(3977+467178+245716+253642)=10844
Node[1].DMA32.min=90112/4*245716/(3977+467178+245716+253642)=5703
Node[1].Normal.min=90112/4*253642/(3977+467178+245716+253642)=5887

可见是符合我们的分析。

然后是lowmem_reserve,

[root@centos7 ~]# cat /proc/zoneinfo | grep -E "Node|managed|protection"
Node 0, zone      DMAmanaged  3977protection: (0, 1824, 1824, 1824)
Node 0, zone    DMA32managed  467178protection: (0, 0, 0, 0)
Node 1, zone    DMA32managed  245716protection: (0, 0, 990, 990)
Node 1, zone   Normalmanaged  253642protection: (0, 0, 0, 0)
[root@centos7 ~]# cat /proc/sys/vm/lowmem_reserve_ratio 
256	256	32

这里的计算是要区分node的,由于系统上没有MOVEABLE的zone,我们就不计算这个zone的相关参数,由此有,

Node[0].zone[DMA].lowmem_reserve[DMA] = 0
Node[0].zone[DMA].lowmem_reserve[DMA32] = Node[0].zone[DMA32].pages/sysctl_lowmem_reserve_ratio[DMA] = 467178/256 = 1824
Node[0].zone[DMA].lowmem_reserve[NORMAL] = Node[0].zone[NORMAL+DMA32].pages/sysctl_lowmem_reserve_ratio[DMA] = 467178/256 = 1824Node[0].zone[DMA32].lowmem_reserve[DMA] = 0
Node[0].zone[DMA32].lowmem_reserve[DMA32] = 0
Node[0].zone[DMA32].lowmem_reserve[NORMAL] = Node[0].zone[NORMAL].pages/sysctl_lowmem_reserve_ratio[DMA32] = 0/256 = 0Node[1].zone[DMA32].lowmem_reserve[DMA] = 0
Node[1].zone[DMA32].lowmem_reserve[DMA32] = 0
Node[1].zone[DMA32].lowmem_reserve[NORMAL] = Node[1].zone[NORMAL].pages/sysctl_lowmem_reserve_ratio[DMA32] = 253642/256 = 990

由此可见,确实符合我们的分析。

这篇关于vm内核参数之内存水位min_free_kbytes和保留内存lowmem_reserve_ratio的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/958771

相关文章

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

Redis过期删除机制与内存淘汰策略的解析指南

《Redis过期删除机制与内存淘汰策略的解析指南》在使用Redis构建缓存系统时,很多开发者只设置了EXPIRE但却忽略了背后Redis的过期删除机制与内存淘汰策略,下面小编就来和大家详细介绍一下... 目录1、简述2、Redis http://www.chinasem.cn的过期删除策略(Key Expir

Java内存区域与内存溢出异常的详细探讨

《Java内存区域与内存溢出异常的详细探讨》:本文主要介绍Java内存区域与内存溢出异常的相关资料,分析异常原因并提供解决策略,如参数调整、代码优化等,帮助开发者排查内存问题,需要的朋友可以参考下... 目录一、引言二、Java 运行时数据区域(一)程序计数器(二)Java 虚拟机栈(三)本地方法栈(四)J

一文详解PostgreSQL复制参数

《一文详解PostgreSQL复制参数》PostgreSQL作为一款功能强大的开源关系型数据库,其复制功能对于构建高可用性系统至关重要,本文给大家详细介绍了PostgreSQL的复制参数,需要的朋友可... 目录一、复制参数基础概念二、核心复制参数深度解析1. max_wal_seChina编程nders:WAL

java变量内存中存储的使用方式

《java变量内存中存储的使用方式》:本文主要介绍java变量内存中存储的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍2、变量的定义3、 变量的类型4、 变量的作用域5、 内存中的存储方式总结1、介绍在 Java 中,变量是用于存储程序中数据

Linux高并发场景下的网络参数调优实战指南

《Linux高并发场景下的网络参数调优实战指南》在高并发网络服务场景中,Linux内核的默认网络参数往往无法满足需求,导致性能瓶颈、连接超时甚至服务崩溃,本文基于真实案例分析,从参数解读、问题诊断到优... 目录一、问题背景:当并发连接遇上性能瓶颈1.1 案例环境1.2 初始参数分析二、深度诊断:连接状态与

史上最全nginx详细参数配置

《史上最全nginx详细参数配置》Nginx是一个轻量级高性能的HTTP和反向代理服务器,同时也是一个通用代理服务器(TCP/UDP/IMAP/POP3/SMTP),最初由俄罗斯人IgorSyso... 目录基本命令默认配置搭建站点根据文件类型设置过期时间禁止文件缓存防盗链静态文件压缩指定定错误页面跨域问题

快速修复一个Panic的Linux内核的技巧

《快速修复一个Panic的Linux内核的技巧》Linux系统中运行了不当的mkinitcpio操作导致内核文件不能正常工作,重启的时候,内核启动中止于Panic状态,该怎么解决这个问题呢?下面我们就... 感谢China编程(www.chinasem.cn)网友 鸢一雨音 的投稿写这篇文章是有原因的。为了配置完

SpringBoot请求参数接收控制指南分享

《SpringBoot请求参数接收控制指南分享》:本文主要介绍SpringBoot请求参数接收控制指南,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring Boot 请求参数接收控制指南1. 概述2. 有注解时参数接收方式对比3. 无注解时接收参数默认位置

Python使用getopt处理命令行参数示例解析(最佳实践)

《Python使用getopt处理命令行参数示例解析(最佳实践)》getopt模块是Python标准库中一个简单但强大的命令行参数处理工具,它特别适合那些需要快速实现基本命令行参数解析的场景,或者需要... 目录为什么需要处理命令行参数?getopt模块基础实际应用示例与其他参数处理方式的比较常见问http