【C/C++】Linux下system()函数引起的错误

今天,一个运行了近一年的程序忽然挂掉了,问题定位到是system()函数出的问题,关于该函数的简单使用在我上篇文章作过介绍: http://my.oschina.net/renhc/blog/53580

先看一下问题 shell

简单封装了一下system()函数: 安全

int pox_system(const char *cmd_line)
{
    return system(cmd_line);
}
函数调用:
int ret = 0;
ret = pox_system("gzip -c /var/opt/I00005.xml > /var/opt/I00005.z");
if(0 != ret)
{
    Log("zip file failed\n");
}
问题现象:每次执行到此处,都会zip failed。而单独把该命令拿出来在shell里执行却老是对的,事实上该段代码已运行了很长时间,从没出过问题。

糟糕的日志 app

分析log时,咱们只能看到“zip file failed”这个咱们自定义的信息,至于为何fail,毫无线索。 函数

那好,咱们先试着找出更多的线索:
int ret = 0;
ret = pox_system("gzip -c /var/opt/I00005.xml > /var/opt/I00005.z");
if(0 != ret)
{
    Log("zip file failed: %s\n", strerror(errno)); //尝试打印出系统错误信息
}
咱们增长了log,经过system()函数设置的errno,咱们获得一个很是有用的线索:system()函数失败是因为“ No child processes”。继续找Root Cause。

谁动了errno 测试

咱们经过上面的线索,知道system()函数设置了errno为ECHILD,然而从system()函数的man手册里咱们找不到任何有关EHILD的信息。咱们知道system()函数执行过程为:fork()->exec()->waitpid()。很显然waitpid()有重大嫌疑,咱们去查一下man手册,看该函数有没有可能设置ECHILD: ui

ECHILD
(for waitpid() or waitid()) The process specified by pid (waitpid()) or idtype and id (waitid()) does not exist or is not a child of the calling process. (This can happen for one's own child if the action for SIGCHLD is set to SIG_IGN. See also the Linux Notes section about threads.)
果真有料,若是SIGCHLD信号行为被设置为SIG_IGN时,waitpid()函数有可能由于找不到子进程而报ECHILD错误。彷佛咱们找到了问题的解决方案:在调用system()函数前从新设置SIGCHLD信号为缺省值,即signal(SIGCHLD, SIG_DFL)。咱们很兴奋,暂时顾不上看Linux Notes部分,直接加上代码测试!乖乖,问题解决了!

如此处理问题是你的风格吗 spa

正当咱们急于check in 代码时,一个疑问出现了:“这个错误为何之前没发生”?是啊,运行良好的程序怎么忽然就挂了呢?首先咱们代码没有改动,那么确定是外部因素了。一想到外部因素,咱们开始抱怨:“确定是其余组的程序影响咱们了!”但抱怨这是没用的,若是你这么认为,那么请拿出证据!但静下来分析一下不难发现,这不多是其余程序的影响,其余进程不可能影响咱们进程对信号的处理方式。 .net

system()函数以前没出错,是由于systeme()函数依赖了系统的一个特性,那就是内核初始化进程时对SIGCHLD信号的处理方式为SIG_DFL,这是什么什么意思呢?即内核发现进程的子进程终止后给进程发送一个SIGCHLD信号,进程收到该信号后采用SIG_DFL方式处理,那么SIG_DFL又是什么方式呢?SIG_DFL是一个宏,定义了一个信号处理函数指针,事实上该信号处理函数什么也没作。这个特性正是system()函数须要的,system()函数首先fork()一个子进程执行command命令,执行完后system()函数会使用waitpid()函数对子进程进行收尸。 指针

经过上面的分析,咱们能够清醒的得知,system()执行前,SIGCHLD信号的处理方式确定变了,再也不是SIG_DFL了,至于变成什么暂时不知道,事实上,咱们也不须要知道,咱们只须要记得使用system()函数前把SIGCHLD信号处理方式显式修改成SIG_DFL方式,同时记录原来的处理方式,使用完system()后再设为原来的处理方式。这样咱们能够屏蔽因系统升级或信号处理方式改变带来的影响。 日志

验证猜测 

咱们公司采用的是持续集成+敏捷开发模式,天天都会由专门的team负责自动化case的测试,每次称为一个build,咱们分析了本次build与上次build使用的系统版本,发现版本确实升级了。因而咱们找到了相关team进行验证,咱们把问题详细的描述了一下,很快对方给了反馈,下面是邮件回复原文:

LIBGEN 里新增长了SIGCHLD的处理。将其ignore。为了不僵尸进程的产生。
看来咱们的猜测没错!问题分析到这里,解决方法也清晰了,因而咱们修改了咱们的pox_system()函数:
typedef void (*sighandler_t)(int);
int pox_system(const char *cmd_line)
{
   int ret = 0;
   sighandler_t old_handler;

   old_handler = signal(SIGCHLD, SIG_DFL);
   ret = system(cmd_line);
   signal(SIGCHLD, old_handler);

   return ret;
}

我想这是调用system()比较完美的解决方案了,同时使用pox_system()函数封装带来了很是棒的易维护性,咱们只须要修改此处一个函数,其余调用处都不须要改。

后来,查看了对方修改的代码,果真从代码上找到了答案:

/* Ignore SIGCHLD to avoid zombie process */
    if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) {
        return -1;
    } else {
        return 0;
    }

其余思考

咱们公司的代码使用SVN进程管理的,到目前为止有不少branch,逐渐的,几乎每一个branch都出现了上面的问题,因而我逐个在各个branchc上fix这个问题,几乎忙了一天,由于有的branch已被锁定,再想merge代码必须找相关负责人说明问题的严重性,还要在不一样的环境上测试,我边作这些边想,系统这样升级合适吗?

首先,因为系统的升级致使咱们的代码在测试时发现问题,这时再急忙去fix,形成了咱们的被动,我想这是他们的一个失误。你作的升级必需要考虑到对其余team的影响吧?况且你作的是系统升级。升级前须要作个风险评估,对可能形成的影响通知你们,这样才职业嘛。

再者,据他们的说法,修改信号处理方式是为了不僵尸进程,固然初衷是好的,但这样的升级影响了一些函数的使用方式,好比system()函数、wait()函数、waipid()、fork()函数,这些函数都与子进程有关,若是你但愿使用wait()或waitpid()对子进程收尸,那么你必须使用上面介绍的方式:在调用前(事实上是fork()前)将SIGCHLD信号置为SIG_DFL处理方式,调用后(事实上wait()/waitpid()后)再将信号处理方式设置为从前的值。你的系统升级,强制你们完善代码,确实提升了代码质量,可是对于这种升级我不是很认同,试想一下,你见过多少fork()->waitpid()先后都设置SIGCHLD信号的代码?

使用system()函数的建议

上在给出了调用system()函数的比较安全的用法,但使用system()函数仍是容易出错,错在哪?那就是system()函数的返回值,关于其返回值的介绍请见上篇文章。system()函数有时很方便,但不可滥用!

一、建议system()函数只用来执行shell命令,由于通常来说,system()返回值不是0就说明出错了;

二、建议监控一下system()函数的执行完毕后的errno值,争取出错时给出更多有用信息;

三、建议考虑一下system()函数的替代函数popen();其用法在个人另外一篇文章有介绍。

 

qdurenhongcai@163.com

转载请注明出处。

相关文章
相关标签/搜索