内存不足导致不能执行 system

问题分析:

  1. 本来向板子发送个 reboot,板子程序收到 reboot 命令后会执行 system(“reboot”);

但是板子并没有重启。

  1. 分析 system 执行机制

先来看一下 system () 函数的简单介绍:

#include
int system(const char *command)

system () 函数调用 /bin/sh 来执行参数指定的命令,/bin/sh 一般是一个软连接,指向某个具体的 shell,比如 bash,-c 选项是告诉 shell 从字符串 command 中读取命令; 在该 command 执行期间,SIGCHLD 是被阻塞的,好比在说:hi,内核,这会不要给我送 SIGCHLD 信号,等我忙完再说; 在该 command 执行期间,SIGINT 和 SIGQUIT 是被忽略的,意思是进程收到这两个信号后没有任何动作。

再来看一下 system () 函数返回值:

为了更好的理解 system () 函数返回值,需要了解其执行过程,实际上 system () 函数执行了三步操作:

1、fork 一个子进程;
2、在子进程中调用 exec 函数去执行 command;
3、在父进程中调用 wait 去等待子进程结束。
对于 fork 失败,system () 函数返回 - 1。 如果 exec 执行成功,也即 command 顺利执行完毕,则返回 command 通过 exit 或 return 返回的值。 (注意,command 顺利执行不代表执行成功,比如 command:“rm debuglog.txt”,不管文件存不存在该 command 都顺利执行了) 如果 exec 执行失败,也即 command 没有顺利执行,比如被信号中断,或者 command 命令根本不存在,system () 函数返回127 . 如果 command 为 NULL,则 system () 函数返回非 0 值,一般为 1.

看一下 system () 函数的源码

int system(const char * cmdstring)  
{  
    pid_t pid;  
    int status;  
  
if(cmdstring == NULL)  
{  
    return (1); //如果cmdstring为空,返回非零值,一般为1  
}  
  
if((pid = fork())<0)  
{  
    status = -1; //fork失败,返回-1  
}  
else if(pid == 0)  
{  
    execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);  
    _exit(127); // exec执行失败返回127,注意exec只在失败时才返回现在的进程,成功的话现在的进程就不存在啦~~  
}  
else //父进程  
{  
    while(waitpid(pid, &status, 0) < 0)  
    {  
        if(errno != EINTR)  
        {  
            status = -1; //如果waitpid被信号中断,则返回-1  
            break;  
        }  
    }  
}  
  
    return status; //如果waitpid成功,则返回子进程的返回状态  
}  

由于返回值是 - 1,因此比较怀疑 fork 执行失败。在控制台上输入 fork 后,果然返回 - 1。fork 返回 - 1 主要由两种原因:系统达到最大进程数上限或者系统内存不足。由于系统当前进程数并不多,因此怀疑内存不足。
0803 版本中升级了由于 CPUH 内存空间不足,导致升级失败的问题。这个问题是通过将 CPUL 的 100M 内存挪至 CPUH 解决的。至此,问题已基本明确,确实是由于挪动内存后,CPUL 上的剩余内存不足以 fork。
其实这里我有一个疑问,fork 会复制父进程的数据空间、堆和栈的副本。因此,如果父进程的内存占用超过系统总可用内存的 50%,调用 system 或 fork 就会存在风险。而 linux 提供了另外一个函数:vfork。vfork 并不会复制父进程的完整地址空间,这个函数主要应用的场景就是 fork 后,立即调用 exec(exec 会使用新程序替换掉当前进程的正文段、数据段、堆和栈)。按照上面提到的 fork 流程,fork 时需要大量的内存来存放父进程的地址空间,而在 exec 时又会立即用新程序替换掉。对于 system 调用失败,仅仅是由于内存不足以容纳另一份父进程地址空间,未免有点冤了。而直接使用 vfork,则空闲内存并不需要那么大,system 为何没有使用更合适的 vfork 呢?
解决

有两个解决方案,一个是重新调整 CPUL 以及 CPUH 的内存分配,另一个是调整内核参数。
其中方案 1 存在一定的风险,因为内存的使用是动态的,如果分回来的过多,可能导致 CPUH 再次出现偶尔升级失败,如果分过来的不够,则有可能出现 CPUL 调用 system 失败,因此,这里主要考虑使用方案 2。
在前面分析的过程中,已经明确,是由于触发了 Linux 的虚拟内存保护,导致 fork 失败。可以通过修改 /proc/sys/vm/overcommit_memory 的值,来修改系统的虚拟内存保护策略。这个文件可以取 0、1、2 三个值,含义如下:
 0-Heuristic overcommit handling
0 是 LINUX 系统的默认值。允许进程申请的内存超出一个合理 (reasonable) 值,如果进程申请超出的过多,则内存分配会被拒绝。如果进程超出范围在合理值内,则也有可能触发 omm-kill,导致进程被杀死。
 1-Always overcommit
接收进程申请的任何内存大小
 2-Don’t overcommit
不允许超出内存总量。内存总量受另外一个参数 overcommit_ratio 控制,系统总可用内存计算公式如下:
总可用内存 = (swap space + RAM size * overcommit_ratio)
可以使用该选项为系统预留一定量的内存。overcommit_ratio 参数可通过修改 /proc/sys/vm/overcommit_ratio 设置。
考虑到我们的代码中出了 system 外,并没有 fork 的需求,而且 system 在执行 fork 后会立即调用 exec 释放掉申请的空间,因此我们将 overcommit_memory 设置为 1。

解决方法:

在启动脚本里增加:

echo 1 > /proc/sys/vm/overcommit_memory &

转载自:https://my.oschina.net/u/2252538/blog/1786204

**内核参数overcommit_memory
它是 内存分配策略
可选值:0、1、2。
0, 表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。
1, 表示内核允许分配所有的物理内存,而不管当前的内存状态如何。
2, 表示内核允许分配超过所有物理内存和交换空间总和的内存
什么是Overcommit和OOM
Linux对大部分申请内存的请求都回复"yes",以便能跑更多更大的程序。因为申请内存后,并不会马上使用内存。这种技术叫做 Overcommit。当linux发现内存不足时,会发生OOM killer(OOM=out-of-memory)。它会选择杀死一些进程(用户态进程,不是内核线程),以便释放内存。
当oom-killer发生时,linux会选择杀死哪些进程?选择进程的函数是oom_badness函数(在mm/oom_kill.c中),该 函数会计算每个进程的点数(0~1000)。点数越高,这个进程越有可能被杀死。每个进程的点数跟oom_score_adj有关,而且 oom_score_adj可以被设置(-1000最低,1000最高)。
解决方法:
很简单,按提示的操作(将vm.overcommit_memory 设为1)即可:
有三种方式修改内核参数,但要有root权限:

 (1)编辑/etc/sysctl.conf ,改vm.overcommit_memory=1,然后sysctl -p使配置文件生效
 (2)sysctl vm.overcommit_memory=1
 (3)echo 1 > /proc/sys/vm/overcommit_memory &
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值