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对父进程的没必要的拷贝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章