文档章节

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

恋恋美食
 恋恋美食
发布于 2012/04/21 11:33
字数 1982
阅读 16506
收藏 26
点赞 9
评论 6
今天,一个运行了近一年的程序突然挂掉了,问题定位到是system()函数出的问题,关于该函数的简单使用在我上篇文章做过介绍: http://my.oschina.net/renhc/blog/53580

先看一下问题

简单封装了一下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里执行却总是对的,事实上该段代码已运行了很长时间,从没出过问题。

糟糕的日志

分析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:

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部分,直接加上代码测试!乖乖,问题解决了!

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

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

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()函数有时很方便,但不可滥用!

1、建议system()函数只用来执行shell命令,因为一般来讲,system()返回值不是0就说明出错了;

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

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

 

qdurenhongcai@163.com

转载请注明出处。

© 著作权归作者所有

共有 人打赏支持
恋恋美食

恋恋美食

粉丝 59
博文 69
码字总数 57098
作品 0
杭州
高级程序员
加载中

评论(6)

恋恋美食
恋恋美食

引用来自“hrljy”的评论

楼主,我想请问一下,文中提到的“对方修改的代码”是在系统文件加上的吗?
那倒不是,一个应用程序某个模块里改过信号处理函数,会直接作用于整个进程。
hrljy
hrljy
楼主,我想请问一下,文中提到的“对方修改的代码”是在系统文件加上的吗?
岁月漫步
岁月漫步
真是细心啊
tuzhu
tuzhu

引用来自“恋恋美食”的评论

引用来自“tuzhu”的评论

对于LZ封装的pox_system函数,有一个问题:
如果在system函数执行时,某个子进程挂了,此时SIGCHLD信号被忽视掉了。可能会导致对于子进程的后处理无法执行到。。

你有兴趣做个demo探讨下么?

解释一下我的看法,假设是如下的环境:
0.父进程创建了10个子进程,然后一个信号处理函数来监视所有子进程的死活状态。
1.父进程在调用pox_system函数时,将SIGCHLD信号的处理函数设置为默认(忽略)。
2.恰巧在父进程执行pox_system函数的期间,子进程0挂掉了。此时,父进程将捕捉不到子进程挂掉的信息。
我只是觉得存在这个可能性。由于上述问题的时机比较难以把握,所以不好写demo分析,不好意思。

对于system函数的这个问题,我的做法是这样的:
0.调用system函数执行所需的操作;
1.在使用waitpid来处理子进程结束时,函数的options这个参数一定要设置为WNOHANG。这样确保父进程不会因为system函数返回的SIGCHLD信号,导致主进程发生堵塞;
2.对waitpid函数的返回值进行判定,如果是需要处理的子进程,则进行相应处理。否则,丢弃SIGCHLD信号。
恋恋美食
恋恋美食

引用来自“tuzhu”的评论

对于LZ封装的pox_system函数,有一个问题:
如果在system函数执行时,某个子进程挂了,此时SIGCHLD信号被忽视掉了。可能会导致对于子进程的后处理无法执行到。。

你有兴趣做个demo探讨下么?
tuzhu
tuzhu
对于LZ封装的pox_system函数,有一个问题:
如果在system函数执行时,某个子进程挂了,此时SIGCHLD信号被忽视掉了。可能会导致对于子进程的后处理无法执行到。。
【C/C++】Linux下使用system()函数一定要谨慎

曾经的曾经,被system()函数折磨过,之所以这样,是因为对system()函数了解不够深入。只是简单的知道用这个函数执行一个系统命令,这远远不够,它的返回值、它所执行命令的返回值以及命令执行...

恋恋美食 ⋅ 2012/04/15 ⋅ 8

【C/C++】正则表达式

首先说明,标准C/C++都不支持正则表达式,但有些函数库提供了这个功能,Philip Hazel的Perl-Compatible Regular Expression库,并且大多数Linux发行版本都带有这个函数库。 使用正则表达式可...

恋恋美食 ⋅ 2012/07/03 ⋅ 1

对于linux下system()函数的深度理解(整理)

对于linux下system()函数的深度理解(整理) (2013-02-07 08:58:54) 这几天调程序(嵌入式linux),发现程序有时就莫名其妙的死掉,每次都定位在程序中不同的system()函数,直接在shell下输入syste...

李东委 ⋅ 2014/11/30 ⋅ 0

MySQL下perror工具查看System Error Code信息

原文出处:潇湘隐者 在MySQL数据库的维护过程中,我们有时候会在MySQL的错误日志文件中看到一些关于Operating system error的错误信息,例如在MySQL的错误日志里面,有时候会看到关于 InnoDB...

潇湘隐者 ⋅ 04/25 ⋅ 0

异常:Exception

我们开发了函数,当函数的调用者在使用函数的时候,我们希望函数的调用者可以正确的使用函数,比如给予函数正确的参数值。但在实际的过程中,函数的调用者可能由于各种原因给予了错误的值。在...

彭博 ⋅ 2012/03/09 ⋅ 0

为保护开源项目被合理使用 红帽变更其开源许可协议规则

预计使用 GPLv2 或 LGPLv2.1 的由红帽发起的开源项目,它们的开源许可协议都将会被加上 GPLv3 的终止条件。 在开源领域,开源协议的重要性不言而喻。因此,领先的 Linux 公司红帽宣布,所有由...

局长 ⋅ 06/19 ⋅ 0

嵌入式物联网开发学习班,嵌入式Linux加快物联网开发

物联网是新一代信息技术的重要组成部分,是互联网与嵌入式系统发展到高级阶段的融合。作为物联网重要技术组成的嵌入式系统,嵌入式系统视角有助于深刻地、全面地理解物联网的本质。 很明显,...

长沙千锋 ⋅ 06/05 ⋅ 0

C#异常处理及心得

C sharp中的异常用于处理系统级和应用程序级的错误状态,它是一种结构化、统一的类型安全的处理机制。c#的异常 机制非常类似于c++的异常处理机制,但是还是有一些重要的区别: 1,在 C# 中,...

C2056LOVE ⋅ 2013/10/17 ⋅ 0

Python 文件和目录管理(os)

简述 目录(或文件夹)是文件和子目录的集合 在 Linux 中,操作系统提供了很多的命令(例如:、),用于文件和目录管理。在 Python 中,有一个 模块,也提供了许多便利的方法来管理文件和目录...

u011012932 ⋅ 2017/10/12 ⋅ 0

Linus 又开怼:有时候标准就是一坨屎!

在一个 Linux 内核 4.18-rc1 的 Pull Request 中,开发者 Andy Shevchenko 表示其在对设备属性框架进行更新时,移除了 union 别名,这引发了 Linus 的暴怒。 这一次 Linus Torvalds 发怒的原...

h4cd ⋅ 06/09 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

门面模式

1. 门面模式定义理解 是比较常用的封装模式,也称为外观模式; 标准定义:Provide a unified interface to a set of interfaces in a subsystem.Facade defines a higher-level interface t...

liuyan_lc ⋅ 23分钟前 ⋅ 0

SpringCloud 微服务 (九) Spring Cloud Bus

壹 本篇结合上篇学习使用SpringCloud Bus结合MQ来实现自动刷新 上篇有远端git码云,本地git,config统一配置中心服务,order服务,product服务 过程: 启动服务时,config服务将远端git的配置拉取到...

___大侠 ⋅ 25分钟前 ⋅ 0

别被别人抢走了你的时间!

前两天,有读者在评论区给我留言,问我类似于 618、双十一这样的电商狂欢,我会参与么? 我给他的回复很直接,我很少参与这类活动,尤其是一些整点抢券的行为,更加不会参与,因为我的时间很...

古乙丁三雨 ⋅ 29分钟前 ⋅ 0

nginx开启gzip压缩JS文件失败?

检查下GZIP设置的压缩的文件类型有没有application/x-javascript,在服务器在传送JavaScript文件时使用的MIME类型通常是这个.

hang1989 ⋅ 29分钟前 ⋅ 0

DockOne微信分享(一二九):聊聊Service Mesh:linkerd

【编者的话】随着企业逐渐将传统的单体应用向微服务或云原生应用的转变,虽然微服务或者云原生应用能给企业带来更多的好处,但也会带来一些具有挑战的问题,如怎么管理从单体应用转向微服务所...

xiaomin0322 ⋅ 34分钟前 ⋅ 0

linear regression

今天分享一个很经典的ML算法---LIner regression(线性回归) **线性回归(Linear regression)**是利用称为线性回归方程的最小二乘函数对一个或多个自变量和因变量之间关系进行建模的一种回归...

gfjjfuy112 ⋅ 36分钟前 ⋅ 0

百度地图使用经验-第1集:成功展现第1个地图

第1步:引入js(携带密钥) 第2步: 创建一个div 第3步: 写js 类: js自己的面向对象的特点; ES6 PHP,JAVA,C++ 插曲:事件 22小时前 事件和函数的区别: 函数是事先写好函数体,手工调用。...

宁哥实战课堂 ⋅ 38分钟前 ⋅ 0

sudo java : command not found

运行sudo java报错 $ sudo java$ sudo: java:找不到命令 解决方案: sudo命令后加-E,保留用户运行环境的环境变量。 sudo -E env PATH=$PATH java...

勇敢的飞石 ⋅ 41分钟前 ⋅ 0

ecs修改主机名称

https://help.aliyun.com/knowledge_detail/41305.html

张宏亮1982 ⋅ 43分钟前 ⋅ 0

扩展JS validate

if ($.validator) { //验证手机号码 $.validator.addMethod("phone", function (value, element, params) { return /^(1)[0-9]{10}$/.test(value); }, "请输入有效的......

熊猫你好 ⋅ 44分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部