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

相关文章

ubuntu16.04如何部署dify? 在Linux上安装部署Dify的技巧

《ubuntu16.04如何部署dify?在Linux上安装部署Dify的技巧》随着云计算和容器技术的快速发展,Docker已经成为现代软件开发和部署的重要工具之一,Dify作为一款优秀的云原生应用... Dify 是一个基于 docker 的工作流管理工具,旨在简化机器学习和数据科学领域的多步骤工作流。它

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

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

Linux系统调试之ltrace工具使用与调试过程

《Linux系统调试之ltrace工具使用与调试过程》:本文主要介绍Linux系统调试之ltrace工具使用与调试过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、ltrace 定义与作用二、ltrace 工作原理1. 劫持进程的 PLT/GOT 表2. 重定

Linux区分SSD和机械硬盘的方法总结

《Linux区分SSD和机械硬盘的方法总结》在Linux系统管理中,了解存储设备的类型和特性是至关重要的,不同的存储介质(如固态硬盘SSD和机械硬盘HDD)在性能、可靠性和适用场景上有着显著差异,本文... 目录一、lsblk 命令简介基本用法二、识别磁盘类型的关键参数:ROTA查询 ROTA 参数ROTA

嵌入式Linux之使用设备树驱动GPIO的实现方式

《嵌入式Linux之使用设备树驱动GPIO的实现方式》:本文主要介绍嵌入式Linux之使用设备树驱动GPIO的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、设备树配置1.1 添加 pinctrl 节点1.2 添加 LED 设备节点二、编写驱动程序2.1

嵌入式Linux驱动中的异步通知机制详解

《嵌入式Linux驱动中的异步通知机制详解》:本文主要介绍嵌入式Linux驱动中的异步通知机制,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、异步通知的核心概念1. 什么是异步通知2. 异步通知的关键组件二、异步通知的实现原理三、代码示例分析1. 设备结构

Linux搭建单机MySQL8.0.26版本的操作方法

《Linux搭建单机MySQL8.0.26版本的操作方法》:本文主要介绍Linux搭建单机MySQL8.0.26版本的操作方法,本文通过图文并茂的形式给大家讲解的非常详细,感兴趣的朋友一起看看吧... 目录概述环境信息数据库服务安装步骤下载前置依赖服务下载方式一:进入官网下载,并上传到宿主机中,适合离线环境

JVM垃圾回收机制之GC解读

《JVM垃圾回收机制之GC解读》:本文主要介绍JVM垃圾回收机制之GC,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、死亡对象的判断算法1.1 引用计数算法1.2 可达性分析算法二、垃圾回收算法2.1 标记-清除算法2.2 复制算法2.3 标记-整理算法2.4

windows和Linux使用命令行计算文件的MD5值

《windows和Linux使用命令行计算文件的MD5值》在Windows和Linux系统中,您可以使用命令行(终端或命令提示符)来计算文件的MD5值,文章介绍了在Windows和Linux/macO... 目录在Windows上:在linux或MACOS上:总结在Windows上:可以使用certuti

Linux之systemV共享内存方式

《Linux之systemV共享内存方式》:本文主要介绍Linux之systemV共享内存方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、工作原理二、系统调用接口1、申请共享内存(一)key的获取(二)共享内存的申请2、将共享内存段连接到进程地址空间3、将