关于Linux下的system调用

曾经的曾经,被system()函数折磨过,之因此这样,是由于对system()函数了解不够深刻。只是简单的知道用这个函数执行一个系统命令,这远远不够,它的返回值、它所执行命令的返回值以及命令执行失败缘由如何定位,这才是重点。当初由于这个函数风险较多,故抛弃不用,改用其余的方法。这里先不说我用了什么方法,这里必需要搞懂system()函数,由于仍是有不少人用了system()函数,有时你不得不面对它。

先来看一下system()函数的简单介绍:

2    int system(const char *command);

system() executes a command specified in command by calling /bin/sh -c command, and returns after the command has been completed. During execution of the command, SIGCHLD will be blocked, and SIGINT and SIGQUIT will be ignored.

system()函数调用/bin/sh来执行参数指定的命令,/bin/sh 通常是一个软链接,指向某个具体的shell,好比bash,-c选项是告诉shell从字符串command中读取命令;

在该command执行期间,SIGCHLD是被阻塞的,比如在说:hi,内核,这会不要给我送SIGCHLD信号,等我忙完再说;

在该command执行期间,SIGINT和SIGQUIT是被忽略的,意思是进程收到这两个信号后没有任何动做。

再来看一下system()函数返回值:

The value returned is -1 on error (e.g. fork(2) failed), and the return status of the command otherwise. This latter return status is in the format specified in wait(2). Thus, the exit code of the command will be WEXITSTATUS(status). In case /bin/sh could not be executed, the exit status will be that of a command that does exit(127).

If the value of command is NULL, system() returns nonzero if the shell is available, and zero if not.

为了更好的理解system()函数返回值,须要了解其执行过程,实际上system()函数执行了三步操做:

1.fork一个子进程;

2.在子进程中调用exec函数去执行command;

3.在父进程中调用wait去等待子进程结束。

对于fork失败,system()函数返回-1。

若是exec执行成功,也即command顺利执行完毕,则返回command经过exit或return返回的值。

(注意,command顺利执行不表明执行成功,好比command:”rm debuglog.txt”,无论文件存不存在该command都顺利执行了)

若是exec执行失败,也即command没有顺利执行,好比被信号中断,或者command命令根本不存在,system()函数返回127.

若是command为NULL,则system()函数返回非0值,通常为1.

popen和system均可以执行外部命令。

popen至关因而先建立一个管道,fork,关闭管道的一端,执行exec,返回一个标准的io文件指针。

system至关因而前后调用了fork, exec,waitpid来执行外部命令

popen自己是不阻塞的,要经过标准io的读取使它阻塞

system自己就是阻塞的。
看一下system()函数的源码

看完这些,我想确定有人对system()函数返回值仍是不清楚,看源码最清楚,下面给出一个system()函数的实现:

01    int system(const char * cmdstring)
08        return (1); //若是cmdstring为空,返回非零值,通常为1
13        status = -1; //fork失败,返回-1
17        execl(“/bin/sh”, ”sh”, ”-c”, cmdstring, (char *)0);
18        _exit(127); // exec执行失败返回127,注意exec只在失败时才返回如今的进程,成功的话如今的进程就不存在啦~~
22        while(waitpid(pid, &status, 0) < 0)
24            if(errno != EINTR)
26                status = -1; //若是waitpid被信号中断,则返回-1
27                break;
32        return status; //若是waitpid成功,则返回子进程的返回状态

仔细看完这个system()函数的简单实现,那么该函数的返回值就清晰了吧,那么何时system()函数返回0呢?只在command命令返回0时。

看一下该怎么监控system()函数执行状态

这里给我出的作法:

02    if(NULL == cmdstring) //若是cmdstring为空趁早闪退吧,尽管system()函数也能处理空指针popen和system均可以执行外部命令。
popen至关因而先建立一个管道,fork,关闭管道的一端,执行exec,返回一个标准的io文件指针。
system至关因而前后调用了fork, exec,waitpid来执行外部命令
popen自己是不阻塞的,要经过标准io的读取使它阻塞
system自己就是阻塞的。popen和system均可以执行外部命令。
popen至关因而先建立一个管道,fork,关闭管道的一端,执行exec,返回一个标准的io文件指针。
system至关因而前后调用了fork, exec,waitpid来执行外部命令
popen自己是不阻塞的,要经过标准io的读取使它阻塞
system自己就是阻塞的。
06    status = system(cmdstring);
09        printf(“cmd: %s\t error: %s”, cmdstring, strerror(errno)); // 这里务必要把errno信息输出或记入Log
15        printf(“normal termination, exit status = %d\n”, WEXITSTATUS(status)); //取得cmdstring执行结果
17    else if(WIFSIGNALED(status))
19        printf(“abnormal termination,signal number =%d\n”, WTERMSIG(status)); //若是cmdstring被信号中断,取得信号值
21    else if(WIFSTOPPED(status))
23        printf(“process stopped, signal number =%d\n”, WSTOPSIG(status)); //若是cmdstring被信号暂停执行,取得信号值

到于取得子进程返回值的相关介绍能够参考另外一篇文章:http://my.oschina.net/renhc/blog/35116

system()函数用起来很容易出错,返回值太多,并且返回值很容易跟command的返回值混淆。这里推荐使用popen()函数替代,关于popen()函数的简单使用也能够经过上面的连接查看。

popen()函数较于system()函数的优点在于使用简单,popen()函数只返回两个值:
成功返回子进程的status,使用WIFEXITED相关宏就能够取得command的返回结果;
失败返回-1,咱们可使用perro()函数或strerror()函数获得有用的错误信息。

这篇文章只涉及了system()函数的简单使用,尚未谈及SIGCHLD、SIGINT和SIGQUIT对system()函数的影响,事实上,之因此今天写这篇文章,是由于项目中因有人使用了system()函数而形成了很严重的事故。现像是system()函数执行时会产生一个错误:“No child processes”。

关于这个错误的分析,感兴趣的朋友能够看一下:http://my.oschina.net/renhc/blog/54582

上面这个连接续上,分割线,篇2——————————————————————————

——————————————————————————————————————-

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

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

1    int pox_system(const char *cmd_line)
3        return system(cmd_line);
函数调用:

2    ret = pox_system(“gzip -c /var/opt/I00005.xml > /var/opt/I00005.z”);
5        Log(“zip file failed\n”);

问题现象:每次执行到此处,都会zip failed。而单独把该命令拿出来在shell里执行却老是对的,事实上该段代码已运行了很长时间,从没出过问题。

糟糕的日志

system非阻塞方式注意点:’&’转后台,同时将输出重定向。不然变为阻塞方式。system非阻塞方式注意点:’&’转后台,同时将输出重定向。不然变为阻塞方式。system非阻塞方式注意点:’&’转后台,同时将输出重定向。不然变为阻塞方式。分析log时,咱们只能看到“zip file failed”这个咱们自定义的信息,至于为何fail,毫无线索。

那好,咱们先试着找出更多的线索:

2    ret = pox_system(“gzip -c /var/opt/I00005.xml > /var/opt/I00005.z”);
5        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()函数:

01    typedef void (*sighandler_t)(int);
02    int pox_system(const char *cmd_line)
05       sighandler_t old_handler;
07       old_handler = signal(SIGCHLD, SIG_DFL);
08       ret = system(cmd_line);
09       signal(SIGCHLD, old_handler);
我想这是调用system()比较完美的解决方案了,同时使用pox_system()函数封装带来了很是棒的易维护性,咱们只须要修改此处一个函数,其余调用处都不须要改。

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

1    /* Ignore SIGCHLD to avoid zombie process */
2    if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) {
system非阻塞方式注意点:’&’转后台,同时将输出重定向。不然变为阻塞方式。system非阻塞方式注意点:’&’转后台,同时将输出重定向。不然变为阻塞方式。system非阻塞方式注意点:’&’转后台,同时将输出重定向。不然变为阻塞方式。

其余思考

咱们公司的代码使用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();其用法在个人另外一篇文章有介绍。

ps:

1.若是waitpid()函数是被信号中断而返回负数的,则继续调用waitpid()函数。

这个包括SIGINT的啊,不违反POSIX.1定义啊

2.system非阻塞方式注意点:’&’转后台,同时将输出重定向。不然变为阻塞方式。

3,

popen和system均可以执行外部命令。

popen至关因而先建立一个管道,fork,关闭管道的一端,执行exec,返回一个标准的io文件指针。

system至关因而前后调用了fork, exec,waitpid来执行外部命令

popen自己是不阻塞的,要经过标准io的读取使它阻塞

system自己就是阻塞的。shell

相关文章
相关标签/搜索