TIP: system errno 12 Cannot allocate memory
问题描述
程序中调用sytem执行gzip ‘file’脚本压缩文件,但是system返回-1,通过gdb查看,errno是12,就是Cannot allocate memory。
问题原因
system调用fork创建一个新的进程,然后在新的进程中调用execl函数执行脚本。这个errno 12就是fork引起的,用free命令查看了下内存,剩余不到400K,swap也用光了。而且,执行system的进程占用的内存很大。
解决方案
采用轻量级的执行脚本接口: posix_spawn。
相关资料
系统参数overcommit_memory与fork的关系
参考链接线程中fork提示 Cannot allocate memory 是否与线程库相关
系统参数:/proc/sys/vm/overcommit_memory
该文件指定了内核针对内存分配的策略,其值可以是0、1、2。
- 0: 表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。
- 1: 表示内核允许分配所有的物理内存,而不管当前的内存状态如何。
- 2: 表示内核允许分配超过所有物理内存和交换空间总和的内存(参照overcommit_ratio)。
2中考虑的是虚拟内存占用量,而不是物理内存,虚拟内存占用通常要比物理内存大很多,也就是说当设置2时,如果被fork进程的虚拟地址空间占用很大就可能导致失败的情况。
2中主要考虑的是物理内存,只有还有空闲的物理内存,而不管虚拟内存占用多少,应该都能分配到。
1则是另一种极端,如果需要保证一定不会分配失败的话,就设置1,当然当内存确是不足时,这也只是推迟了失败而已。
0是相对折中的方案,也是标准方式。
posix_spawn
博主提供了测试代码
#include <malloc.h>
#include <unistd.h>
#include <spawn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
int main (void) {
volatile void *bla = malloc (1024*1024*1024);
int status;
int pid;
char* spawnedArgs[] = { "/bin/sleep", "2d", NULL };
int f = posix_spawnp(&pid, spawnedArgs[0], NULL, NULL, spawnedArgs, NULL);
printf("Pid: %d\nposix_spawn: %d\npid: %d\n---\n", getpid(), f, pid);
scanf("scanf");
wait(&status);
}
Linux 系统使用vfork代替fork
参考使用popen遭遇ENOMEM (Cannot allocate memory)的问题
博主写了一个vpopen函数
//#ifdef OPEN_MAX
//static long openmax = OPEN_MAX;
//#else
static long openmax = 0;
//#endif
/*
* If OPEN_MAX is indeterminate, we're not
* guaranteed that this is adequate.
*/
#define OPEN_MAX_GUESS 1024
long open_max(void)
{
if (openmax == 0) { /* first time through */
errno = 0;
if ((openmax = sysconf(_SC_OPEN_MAX)) < 0) {
if (errno == 0)
openmax = OPEN_MAX_GUESS; /* it's indeterminate */
else
printf("sysconf error for _SC_OPEN_MAX");
}
}
return(openmax);
}
static pid_t *childpid = NULL; /* ptr to array allocated at run-time */
static int maxfd; /* from our open_max(), {Prog openmax} */
FILE *vpopen(const char* cmdstring, const char *type)
{
int pfd[2];
FILE *fp;
pid_t pid;
if((type[0]!='r' && type[0]!='w')||type[1]!=0)
{
errno = EINVAL;
return(NULL);
}
if (childpid == NULL) { /* first time through */
/* allocate zeroed out array for child pids */
maxfd = open_max();
if ( (childpid = (pid_t *)calloc(maxfd, sizeof(pid_t))) == NULL)
return(NULL);
}
if(pipe(pfd)!=0)
{
return NULL;
}
if((pid = vfork())<0)
{
return(NULL); /* errno set by fork() */
}
else if (pid == 0) { /* child */
if (*type == 'r')
{
close(pfd[0]);
if (pfd[1] != STDOUT_FILENO) {
dup2(pfd[1], STDOUT_FILENO);
close(pfd[1]);
}
}
else
{
close(pfd[1]);
if (pfd[0] != STDIN_FILENO) {
dup2(pfd[0], STDIN_FILENO);
close(pfd[0]);
}
}
/* close all descriptors in childpid[] */
for (int i = 0; i < maxfd; i++)
if (childpid[ i ] > 0)
close(i);
execl("/bin/sh", "sh", "-c", cmdstring, (char *) 0);
_exit(127);
}
if (*type == 'r') {
close(pfd[1]);
if ( (fp = fdopen(pfd[0], type)) == NULL)
return(NULL);
} else {
close(pfd[0]);
if ( (fp = fdopen(pfd[1], type)) == NULL)
return(NULL);
}
childpid[fileno(fp)] = pid; /* remember child pid for this fd */
return(fp);
}
int vpclose(FILE *fp)
{
int fd, stat;
pid_t pid;
if (childpid == NULL)
return(-1); /* popen() has never been called */
fd = fileno(fp);
if ( (pid = childpid[fd]) == 0)
return(-1); /* fp wasn't opened by popen() */
childpid[fd] = 0;
if (fclose(fp) == EOF)
return(-1);
while (waitpid(pid, &stat, 0) < 0)
if (errno != EINTR)
return(-1); /* error other than EINTR from waitpid() */
return(stat); /* return child's termination status */
}
回收文件系统缓存
在文件读写时,Linux会做一个缓存,放在buffer中,在特定的时机才将缓存清理掉。Linux还提供了一个清理缓存的命令:
sync && echo 3 > /proc/sys/vm/drop_caches
当然,这个命令需要root权限。
执行的效果如下。
执行前:
[root@rhel149 3rd]$ free
total used free shared buffers cached
Mem: 32765960 32073544 692416 0 2722124 11436276
-/+ buffers/cache: 17915144 14850816
Swap: 33551712 26739792 6811920
执行后:
[root@rhel149 3rd]# free
total used free shared buffers cached
Mem: 32765960 16298020 16467940 0 3732 236400
-/+ buffers/cache: 16057888 16708072
Swap: 33551712 26739780 6811932
glibc 中的system和posix_spawn的区别
system的实现与posix_spawn很相似,最主要的区别应该是调用fork的区别。
在system中ia64平台上的调用是:
INLINE_SYSCALL (clone2, 6, CLONE_PARENT_SETTID | SIGCHLD, NULL, 0, \
&pid, NULL, NULL)
在posix_spawn中ia64平台上的调用是:
#define ARCH_FORK() \
INLINE_SYSCALL (clone2, 6, \
CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, \
NULL, 0, NULL, &THREAD_SELF->tid, NULL)
posix_spawn比system的clone2多了一个参数CLONE_CHILD_CLEARTID,这个参数的解释是:
Erase the child thread ID at the location ctid in child memory
when the child exits, and do a wakeup on the futex at that
address. The address involved may be changed by the
set_tid_address(2) system call. This is used by threading
libraries.
更简单的说明:
Clone option: Erase child thread ID in child memory space when child exits.
手册中关于set_tid_address的描述。
两个函数在glibc中的位置
system的实现在sysdeps/posix/system.c
posix_spawn在posix/spawn.c