【C/C++】Linux下使用system()函數一定要謹慎

原文地址:http://my.oschina.net/renhc/blog/53580




曾經的曾經,被system()函數折磨過,之所以這樣,是因爲對system()函數了解不夠深入。只是簡單的知道用這個函數執行一個系統命令,這遠遠不夠,它的返回值、它所執行命令的返回值以及命令執行失敗原因如何定位,這纔是重點。當初因爲這個函數風險較多,故拋棄不用,改用其他的方法。這裏先不說我用了什麼方法,這裏必須要搞懂system()函數,因爲還是有很多人用了system()函數,有時你不得不面對它。

先來看一下system()函數的簡單介紹:
1 #include <stdlib.h>
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.

看一下system()函數的源碼
看完這些,我想肯定有人對system()函數返回值還是不清楚,看源碼最清楚,下面給出一個system()函數的實現:
01 int system(const char * cmdstring)
02 {
03     pid_t pid;
04     int status;
05  
06 if(cmdstring == NULL)
07 {
08     return (1); //如果cmdstring爲空,返回非零值,一般爲1
09 }
10  
11 if((pid = fork())<0)
12 {
13     status = -1; //fork失敗,返回-1
14 }
15 else if(pid == 0)
16 {
17     execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
18     _exit(127); // exec執行失敗返回127,注意exec只在失敗時才返回現在的進程,成功的話現在的進程就不存在啦~~
19 }
20 else //父進程
21 {
22     while(waitpid(pid, &status, 0) < 0)
23     {
24         if(errno != EINTR)
25         {
26             status = -1; //如果waitpid被信號中斷,則返回-1
27             break;
28         }
29     }
30 }
31  
32     return status; //如果waitpid成功,則返回子進程的返回狀態
33 }

仔細看完這個system()函數的簡單實現,那麼該函數的返回值就清晰了吧,那麼什麼時候system()函數返回0呢?只在command命令返回0時。

看一下該怎麼監控system()函數執行狀態
這裏給我出的做法:
01 int status;
02 if(NULL == cmdstring) //如果cmdstring爲空趁早閃退吧,儘管system()函數也能處理空指針
03 {
04     return XXX;
05 }
06 status = system(cmdstring);
07 if(status < 0)
08 {
09     printf("cmd: %s\t error: %s", cmdstring, strerror(errno)); // 這裏務必要把errno信息輸出或記入Log
10     return XXX;
11 }
12  
13 if(WIFEXITED(status))
14 {
15     printf("normal termination, exit status = %d\n", WEXITSTATUS(status)); //取得cmdstring執行結果
16 }
17 else if(WIFSIGNALED(status))
18 {
19     printf("abnormal termination,signal number =%d\n", WTERMSIG(status)); //如果cmdstring被信號中斷,取得信號值
20 }
21 else if(WIFSTOPPED(status))
22 {
23     printf("process stopped, signal number =%d\n", WSTOPSIG(status)); //如果cmdstring被信號暫停執行,取得信號值
24 }

到於取得子進程返回值的相關介紹可以參考另一篇文章: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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章