使用popen遭遇ENOMEM (Cannot allocate memory)的问题

本文介绍了在使用popen函数时遇到ENOMEM问题的原因及解决办法。通过strace跟踪发现是clone函数内存分配失败,最终通过自定义vpopen替代popen解决问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

遇到一个popen遭遇ENOMEM (Cannot allocate memory)的问题,记录一下


我需要在程序里获取标准输出的内容,于是在一个模块里使用了popen这个函数,本来一直运行着都没,但是最近这个模块老是出问题,最后定位到是popen调用出错。返回的errno是ENOMEM (Cannot allocate memory),查看popen的文档并没有ENOMEM 相关的说明,到网上搜索,有人说popen无非是pipe+fork+execel的一个函数,可以跟一下看看是哪个函数的问题。
使用
strace -o app.starce ./app
跟进去看程序的调用信息,找到了对应的出错点
pipe2([17, 18], O_CLOEXEC)              = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f084002aab0) = -1 ENOMEM (Cannot allocate memory)
close(18)                               = 0
close(17)  
可见,实际是clone函数出错了,man clone,确实有
ENOMEM Cannot  allocate  sufficient memory to allocate a task structure for the child, or to copy those parts of the caller's context that need to be copied.
写一个使用了fork的小程序,使用strace查看,
13097 getrlimit(RLIMIT_STACK, {rlim_cur=512*1024, rlim_max=512*1024}) = 0
13097 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7feed3ccf9f0) = 13098
13097 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 10), ...}) = 0
13097 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0 <unfinished ...>
的确,fork函数调用了clone的。
我们都知道,linux使用fork创建子进程会copy父进程的堆,栈,静态存储区,文件描述符等等,那么就有可能父进程内存使用过多,导致子进程无法再从剩余的内存上分配内存。

同样使用system也有
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
clone(child_stack=0, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0x7fffed9206f8) = 32710

wait4(32710, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 32710


那么使用vfork呢
vfork()                           = 13123
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 10), ...}) = 0
fstat(1,  <unfinished ...>
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0 <unfinished ...>
可将,用vfork就并没有调用clone


于是自己写了一个函数来代替popen

[cpp]  view plain copy
  1. <pre name="code" class="cpp">//#ifdef  OPEN_MAX  
  2. //static long openmax = OPEN_MAX;  
  3. //#else  
  4. static long openmax = 0;  
  5. //#endif  
  6.    
  7. /* 
  8.  * If OPEN_MAX is indeterminate, we're not 
  9.  * guaranteed that this is adequate. 
  10.  */  
  11. #define OPEN_MAX_GUESS 1024  
  12.    
  13. long open_max(void)  
  14. {  
  15.     if (openmax == 0) {      /* first time through */  
  16.         errno = 0;  
  17.         if ((openmax = sysconf(_SC_OPEN_MAX)) < 0) {  
  18.            if (errno == 0)  
  19.                openmax = OPEN_MAX_GUESS;    /* it's indeterminate */  
  20.            else  
  21.                printf("sysconf error for _SC_OPEN_MAX");  
  22.         }  
  23.     }  
  24.    
  25.     return(openmax);  
  26. }  
  27.   
  28. static pid_t    *childpid = NULL;  /* ptr to array allocated at run-time */  
  29. static int      maxfd;  /* from our open_max(), {Prog openmax} */  
  30.   
  31. FILE *vpopen(const char* cmdstring, const char *type)  
  32. {  
  33.     int pfd[2];  
  34.     FILE *fp;  
  35.     pid_t   pid;  
  36.   
  37.     if((type[0]!='r' && type[0]!='w')||type[1]!=0)  
  38.     {  
  39.         errno = EINVAL;  
  40.         return(NULL);  
  41.     }  
  42.   
  43.     if (childpid == NULL) {     /* first time through */    
  44.                 /* allocate zeroed out array for child pids */    
  45.         maxfd = open_max();    
  46.         if ( (childpid = (pid_t *)calloc(maxfd, sizeof(pid_t))) == NULL)    
  47.             return(NULL);    
  48.     }  
  49.   
  50.     if(pipe(pfd)!=0)  
  51.     {  
  52.         return NULL;  
  53.     }  
  54.       
  55.     if((pid = vfork())<0)  
  56.     {  
  57.         return(NULL);   /* errno set by fork() */    
  58.     }  
  59.     else if (pid == 0) {    /* child */  
  60.         if (*type == 'r')  
  61.         {  
  62.             close(pfd[0]);    
  63.             if (pfd[1] != STDOUT_FILENO) {    
  64.                 dup2(pfd[1], STDOUT_FILENO);    
  65.                 close(pfd[1]);    
  66.             }             
  67.         }  
  68.         else  
  69.         {  
  70.             close(pfd[1]);    
  71.             if (pfd[0] != STDIN_FILENO) {    
  72.                 dup2(pfd[0], STDIN_FILENO);    
  73.                 close(pfd[0]);    
  74.             }             
  75.         }  
  76.   
  77.         /* close all descriptors in childpid[] */    
  78.         for (int i = 0; i < maxfd; i++)    
  79.         if (childpid[ i ] > 0)     
  80.             close(i);    
  81.   
  82.         execl("/bin/sh""sh""-c", cmdstring, (char *) 0);    
  83.         _exit(127);       
  84.     }  
  85.   
  86.     if (*type == 'r') {    
  87.         close(pfd[1]);    
  88.         if ( (fp = fdopen(pfd[0], type)) == NULL)    
  89.             return(NULL);    
  90.     } else {    
  91.         close(pfd[0]);    
  92.         if ( (fp = fdopen(pfd[1], type)) == NULL)    
  93.             return(NULL);    
  94.     }  
  95.   
  96.     childpid[fileno(fp)] = pid; /* remember child pid for this fd */    
  97.     return(fp);       
  98. }  
  99.   
  100.   
  101. int vpclose(FILE *fp)  
  102. {  
  103.     int     fd, stat;    
  104.     pid_t   pid;    
  105.     
  106.     if (childpid == NULL)    
  107.         return(-1);     /* popen() has never been called */    
  108.     
  109.     fd = fileno(fp);    
  110.     if ( (pid = childpid[fd]) == 0)    
  111.         return(-1);     /* fp wasn't opened by popen() */    
  112.     
  113.     childpid[fd] = 0;    
  114.     if (fclose(fp) == EOF)    
  115.         return(-1);    
  116.     
  117.     while (waitpid(pid, &stat, 0) < 0)    
  118.         if (errno != EINTR)    
  119.             return(-1); /* error other than EINTR from waitpid() */    
  120.     
  121.     return(stat);   /* return child's termination status */    
  122.   
  123. }</pre><pre name="code" class="cpp"></pre><pre name="code" class="cpp"><pre name="code" class="cpp"><p>//我一度怀疑是程序使用的内存多,导致fork时无法给子进程分配内存</p><p>//但是文档上都说,fork时并非马上分配,只有实际使用到时才真正分配。  
  124.   
  125. //如果这样的话,那出现ENOMEM 的错误是为什么呢?不解</p></pre>  
  126. <pre></pre>  
  127. <pre name="code" class="cpp"></pre>  
  128. <pre></pre>  
  129.   
  130. </pre>  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值