linux memory overcommit机制--------笔记

2024-05-03 22:08

本文主要是介绍linux memory overcommit机制--------笔记,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言:

overcommit 机制介绍:

一个问题引发的对overcommit的思考:

问题背景:

问题:

问题分析:

问题的原因:

解决方案:


前言:

linux的虚拟内存支持overcommit(过度使用)

本文就fork子进程时"fork: Cannot allocate memory" 错误展开分析,解释其原因,并给出解决方法.

overcommit 机制介绍:

linux memory overcommit策略一共分为三种(可以通过/proc/sys/vm/overcommit_memory修改策略):

参考linux kernel document:overcommit-accounting

参考内核mm/util.c __vm_enough_memory()

(1)启发式策略(overcommit_memory==0):

如果有足够可用的物理内存供使用,则内存分配成功,否则失败.

可用物理内存可以通过对/proc/meminfo的统计信息做一下计算得到:"free + buffer + cached - shm + swap + slab_recalimable - zone_total_reserved(各zone预留内存总和,一般是固定的)"

__vm_enough_memory通过比较请求的内存数量与当前可用的物理内存来决定是否允许请求。

(2)允许过度使用策略(overcommit_memory==1):

无论分配多少内存都会成功,这样的好处是可以使用所有的物理内存,但是有可能引发oom。(为了验证你可以动手试一下,譬如当前有50M可用的物理内存,malloc即使100M也是成功返回的,但是写这段内存的时候,超过50M就是oom)

(3)不允许过度使用策略(overcommit_memory==2):

系统的所有虚拟内存加起来不得超过 “总物理内存 + CommitLimt”(CommitLimt=总物理内存*overcommit_ratio%)。overcommit_ratio可以在/proc/sys/vm里设定,默认是50,也就是CommitLimt默认是0.5倍的总物理内存。当前所有进程一共使用的虚拟内存Committed_AS和CommitLimt可通过/proc/meminfo查看。(为了验证你也可以试一下,看一下当你的Committed_AS超过“总物理内存 + CommitLimt”时,这时候你在终端上敲个命令就会提示你fork:alloc memory fail)

 

一个问题引发的对overcommit的思考:

问题背景:

我们在写程序的时候可能会涉及到多进程,譬如主进程fork一个子进程,或者更一般的我们会用到system和popen这些系统调用来执行我们的command。实际上system和popen也是通过fork子进程来完成自己的工作的。

system会先fork一个子进程,然后watpid(这里fork动作和waitpid动作都是在主进程中执行的)。子进程通过exec替换其上下文空间为shell进程,shell进程再fork一个子进程,然后waitpid。这个子进程同样的通过exec替换自己的上下文空间为要执行的command,执行command。popen的动作跟system一致,不同的是主子进程间有管道来进行通讯,譬如我可以获得command执行的输出结果。

问题:

由于使用system我们可能会遇到这样一个令人匪夷所思的问题:

fork: alloc memory fail

问题分析:

为了搞清楚这一个问题的原因,我举一个例子,通过这个例子来解释清楚为什么会出现这一问题。

例子:

系统当前有50M可用的物理内存(可用的物理内存同上一节的解释),启动一个进程:

  1. 分配1M大小的全局数组(这1M出现在了heap段)
  2. 申请2段15M大小的内存(malloc大于128k时采用mmap分配,出现在mmap段)
  3. 向这30M内存执行写操作(不执行这一步可fork成功)
  4. 执行fork
  5. 子进程sleep 10s,退出
  6. 父进程收到子进程退出消息后, sleep 10s
  7. 父进程释放内存. 退出

对于这个进程的内存空间的分布可以查看进程的maps(/proc/pid/maps)

我分配了15*2+1=31M的内存,但是应该还有19M内存可用,为什么fork会提示alloc memory fail呢????

                                                                         meminfo

 

                                                                            maps

 

                                                                            overcommit

为了弄明白原由,我们需要先知道在fork的时候子进程会copy父进程的一些虚拟内存区域,通过我们的实验分析可以知道它需要copy数据段,head段,部分mmap段(譬如这里我们malloc的30M内存)。在copy每一个内存区域的时候都会按照当前的overcommit策略来断定是否允许执行copy。overcommit的实现关键逻辑在"__vm_enough_memory(...)"函数。我们上面已经介绍了overcommit的三种策略,默认情况下采用启发式overcommit策略,这里不再阐述了。

在执行fork时,从系统调用开始_do_fork--->copy_process--->copy_mm--->dup_mm--->dup_mmap--->对父进程mm_struct中的每个vm_area执行security_vm_enough_memory_mm--->__vm_enough_memory,根据overcommit策略判断是否允许申请,如果允许事情则接着执行copy_page_range对页目录和页表进行拷贝

(同样的malloc一段内存的时候也会执行overcommit检查)

对于上面的例子:

(1) 通过在执行fork前,cat得到meminfo信息,计算得到当前可用物理内存 7500+2648+8816-144+1624-1068=19376Kbytes
这一结果与overcommit图中free的4861pages(19444Kbytes)基本接近(运行过程中内存存在变动,还有为数不多的其他几个进程在运行)。

(2) maps图中,第二行是data段(未初始化时大小为1个page),第三行是heap段(1M大小的全局数组分布在这个段内),第四行是一段mmap段(malloc超过128Kbytes时通过mmap分配,大小是30M+2pages,分量次申请的数据存在同一mmap段;当然还有其他mmap段,譬如文件的映射段,贡献库的映射段,这些都不需要拷贝),这三段是在fork时需要拷贝的vm_area,fork时拷贝这三段也是处于cow的考虑吧(写时拷贝)

(3)overcommit图中(我在驱动中加的log),第一行是data段(可以通过size和start_address与maps图所展示的信息断定,下同),第二行是1M的heap段,第三行是32M+2pages的一段mmap段

问题的原因:

通过overcommit图可以得知在fork时依据overcommit的决段逻辑(默认的启发式),30M+2pages的mmap段的大小(7682pages)超过了当前可用的物理内存大小19444Kbytes(4861pages).导致"alloc memory fail"

这样对"malloc的30M内存不执行写操作,fork成功"的原因就显而易见了,因为它没有去分配页框,物理内存不会因此减小30M+2pages,所以overcommit检查的时候能够通过,fork得以成功执行.(需要拷贝的最大的vm_area 30M+2pages小于当前可用的物理内存19444Kbytes+30M+ 2pages)。

解决方案:

可以使用vfork替代fork,vfork共享父进程的内存空间,不会进行copy,对资源的消耗更少,所以不会出现上述问题。而且vfork的kernel机制保证了vfork后子进程先运行(fork之后父子进程谁先运行时不确定的,当然你可以通过/proc/sys/kernel/sched_child_runs_first来使子进程先运行)。

要注意在vfork后子进程共享父进程的内存空间,不要因为子进程的一些动作而影响父进程的运行。其一般的用途是vfork之后即调用exec来替换子进程的上下文,从而执行我们的另外一个程序,避免了fork对父进程的没必要的拷贝。

这篇关于linux memory overcommit机制--------笔记的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法

《ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法》本文介绍了Elasticsearch的基本概念,包括文档和字段、索引和映射,还详细描述了如何通过Docker... 目录1、ElasticSearch概念2、ElasticSearch、Kibana和IK分词器部署

Linux流媒体服务器部署流程

《Linux流媒体服务器部署流程》文章详细介绍了流媒体服务器的部署步骤,包括更新系统、安装依赖组件、编译安装Nginx和RTMP模块、配置Nginx和FFmpeg,以及测试流媒体服务器的搭建... 目录流媒体服务器部署部署安装1.更新系统2.安装依赖组件3.解压4.编译安装(添加RTMP和openssl模块

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

Spring排序机制之接口与注解的使用方法

《Spring排序机制之接口与注解的使用方法》本文介绍了Spring中多种排序机制,包括Ordered接口、PriorityOrdered接口、@Order注解和@Priority注解,提供了详细示例... 目录一、Spring 排序的需求场景二、Spring 中的排序机制1、Ordered 接口2、Pri

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.

MySQL 缓存机制与架构解析(最新推荐)

《MySQL缓存机制与架构解析(最新推荐)》本文详细介绍了MySQL的缓存机制和整体架构,包括一级缓存(InnoDBBufferPool)和二级缓存(QueryCache),文章还探讨了SQL... 目录一、mysql缓存机制概述二、MySQL整体架构三、SQL查询执行全流程四、MySQL 8.0为何移除查

Linux环境变量&&进程地址空间详解

《Linux环境变量&&进程地址空间详解》本文介绍了Linux环境变量、命令行参数、进程地址空间以及Linux内核进程调度队列的相关知识,环境变量是系统运行环境的参数,命令行参数用于传递给程序的参数,... 目录一、初步认识环境变量1.1常见的环境变量1.2环境变量的基本概念二、命令行参数2.1通过命令编程

Linux之进程状态&&进程优先级详解

《Linux之进程状态&&进程优先级详解》文章介绍了操作系统中进程的状态,包括运行状态、阻塞状态和挂起状态,并详细解释了Linux下进程的具体状态及其管理,此外,文章还讨论了进程的优先级、查看和修改进... 目录一、操作系统的进程状态1.1运行状态1.2阻塞状态1.3挂起二、linux下具体的状态三、进程的

Linux编译器--gcc/g++使用方式

《Linux编译器--gcc/g++使用方式》文章主要介绍了C/C++程序的编译过程,包括预编译、编译、汇编和链接四个阶段,并详细解释了每个阶段的作用和具体操作,同时,还介绍了调试和发布版本的概念... 目录一、预编译指令1.1预处理功能1.2指令1.3问题扩展二、编译(生成汇编)三、汇编(生成二进制机器语