你好,我是雨乐!
在上一篇文章基于线程池的线上服务性能优化中,我们提到了使用线程池进行某个业务功能优化,在上线之后,实时性提高了大概24-30倍样子,基本能够满足实时性要求。在正常运行了几天之后,突然收到了报警,提示popen失败,于是打开了日志,发现有如下提示:
代码语言:javascript复制popen file failed, id: abc url: http:xxx.txt errno: 12
于是,开始查看错误提示,如下:
看来是内存不足,于是,通过free
命令查看所在机器的内存信息,如下:
可用内存还有2.7G,不至于分配失败呀。
问题定位
看到popen()提示内存分配失败,首先就开始怀疑是否是wget使用有问题,但经过仔细研究之后,发现问题跟该命令无关,这是因为wget仅仅是将文件下载到本地,并不会占用过多的内存
。
既然问题与wget命令本身无关,那么问题苗头就指向popen本身了,于是在搜索引擎中搜索popen ENOMEM
,其中有一条与本次遇到的问题很像,如下:
通过该文内容,得到了一个很重要的信息,那就是popen的实现是fork execve。熟悉fork()的开发人员都知道,fork()以当前进程作为父进程创建出一个新的子进程,并且将父进程的所有资源拷贝给子进程,这样子进程作为父进程的一个副本存在
。既然fork()会生成父进程的一个副本,那么父进程所占用的所有资源,在子进程中也就会被拷贝一份。换句话说,fork()函数为clone父进程的所有资源,这样就能理解为什么当可用内存小于50%
的时候,popen()会失败。
于是,为了验证文章的内容是否与本次遇到的问题一致,在本地写了一个简单的测试用例,测试代码中仅仅包含popen()函数,编译,然后使用starce ./test
之后,输出如下:
...
futex(0x7ffdd648a69c, FUTEX_WAKE, 1) = 0
futex(0x7ffdd648a69c, FUTEX_WAKE_PRIVATE, 1) = 0
pipe2([3, 4], O_CLOEXEC) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f82fcab37d0) = 27437
close(4) = 0
fcntl(3, F_SETFD, 0) = 0
exit_group(0) = ?
...
在上面的strace命令输出中,我们能看到一个很重要的函数那就是clone()(fork()函数会调用clone()
),看来问题就在这。。。
源码分析
为了能够确认是否是因为popen()中的fork()所引起,于是找到了popen()函数的源码实现,如下:
代码语言:javascript复制FILE *
popen(const char *program, const char *type)
{
struct pid * volatile cur;
FILE *iop;
int pdes[2];
pid_t pid;
char *argp[] = {"sh", "-c", NULL, NULL};
if ((*type != 'r' && *type != 'w') || type[1] != '