這幾天調(diào)程序(嵌入式linux),發(fā)現(xiàn)程序有時(shí)就莫名其妙的死掉,每次都定位在程序中不同的system()函數(shù),直接在shell下輸入system()函數(shù)中調(diào)用的命令也都一切正常.就沒(méi)理這個(gè)bug,以為是其他的代碼影響到這個(gè),或是內(nèi)核驅(qū)動(dòng)文件系統(tǒng)什么的異常導(dǎo)致,昨天有出現(xiàn)了這個(gè)問(wèn)題,就隨手百了一下度,問(wèn)題出現(xiàn)了,很多人都說(shuō)system()函數(shù)要慎用要少用要能不用則不用,system()函數(shù)不穩(wěn)定??
下面對(duì)system函數(shù)做一個(gè)簡(jiǎn)單的介紹:?
頭文件?
#i nclude?
定義函數(shù)?
int system(const char * string);?
函數(shù)說(shuō)明?
system()會(huì)調(diào)用fork()產(chǎn)生子進(jìn)程,由子進(jìn)程來(lái)調(diào)用/bin/sh-c string來(lái)執(zhí)行參數(shù)string字符串所代表的命令,此命>令執(zhí)行完后隨即返回原調(diào)用的進(jìn)程。在調(diào)用system()期間SIGCHLD 信號(hào)會(huì)被暫時(shí)擱置,SIGINT和SIGQUIT 信號(hào)則會(huì)被忽略。 返回值 =-1:出現(xiàn)錯(cuò)誤 =0:調(diào)用成功但是沒(méi)有出現(xiàn)子進(jìn)程 >0:成功退出的子進(jìn)程的id 如果system()在調(diào)用/bin/sh時(shí)失敗則返回127,其他失敗原因返回-1。若參數(shù)string為空指針(NULL),則返回非零值>。如果system()調(diào)用成功則最后會(huì)返回執(zhí)行shell命令后的返回值,但是此返回值也有可能為 system()調(diào)用/bin/sh失敗所返回的127,因此最好能再檢查errno 來(lái)確認(rèn)執(zhí)行成功。?
附加說(shuō)明?
在編寫(xiě)具有SUID/SGID權(quán)限的程序時(shí)請(qǐng)勿使用system(),system()會(huì)繼承環(huán)境變量,通過(guò)環(huán)境變量可能會(huì)造成系統(tǒng)安全的問(wèn)題。 system函數(shù)已經(jīng)被收錄在標(biāo)準(zhǔn)c庫(kù)中,可以直接調(diào)用,使用system()函數(shù)調(diào)用系統(tǒng)命令的基本使用方法如下:?
#include ?
int main()
{?
system("mkdir $HOME/.SmartPlatform/");?
system("mkdir $HOME/.SmartPlatform/Files/");?
system("cp mainnew.cpp $HOME/.SmartPlatform/Files/");?
return 0;?
}?
下面我們來(lái)看看system函數(shù)的源碼:?
#include ?
#include ?
#include ?
#include ?
int system(const char * cmdstring)
{?
pid_t pid;?
int status;?
if(cmdstring == NULL)
{?
return (1);?
}?
if((pid = fork())<0)
{?
status = -1;?
}?
else if(pid = 0)
{
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0); -exit(127); //子進(jìn)程正常執(zhí)行則不會(huì)執(zhí)行此語(yǔ)句 }?
else
{?
while(waitpid(pid, &status, 0) < 0)
{
if(errno != EINTER){ status = -1; break;?
}?
}
}?
return status;?
}?
花了兩天時(shí)間仔細(xì)研究了一下,在網(wǎng)上發(fā)現(xiàn)了一篇精品博客,介紹的很詳細(xì)了,謝謝博主,直接轉(zhuǎn), 原文如下:?
http://my.oschina.net/renhc/blog/53580?
【C/C++】Linux下使用system()函數(shù)一定要謹(jǐn)慎?
曾經(jīng)的曾經(jīng),被system()函數(shù)折磨過(guò),之所以這樣,是因?yàn)閷?duì)system()函數(shù)了解不夠深入。只是簡(jiǎn)單的知道用這個(gè)函數(shù)執(zhí)行一個(gè)系統(tǒng)命令,這遠(yuǎn)遠(yuǎn)不夠,它的返回值、它所執(zhí)行命令的返回值以及命令執(zhí)行失敗原因如何定位,這才是重點(diǎn)。當(dāng)初因?yàn)檫@個(gè)函數(shù)風(fēng)險(xiǎn)較多,故拋棄不用,改用其他的方法。這里先不說(shuō)我用了什么方法,這里必須要搞懂system()函數(shù),因?yàn)檫€是有很多人用了system()函數(shù),有時(shí)你不得不面對(duì)它。?
先來(lái)看一下system()函數(shù)的簡(jiǎn)單介紹:?
#include ?
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()函數(shù)調(diào)用/bin/sh來(lái)執(zhí)行參數(shù)指定的命令,/bin/sh 一般是一個(gè)軟連接,指向某個(gè)具體的shell,比如bash,-c選項(xiàng)是告訴shell從字符串command中讀取命令; 在該command執(zhí)行期間,SIGCHLD是被阻塞的,好比在說(shuō):hi,內(nèi)核,這會(huì)不要給我送SIGCHLD信號(hào),等我忙完再說(shuō); 在該command執(zhí)行期間,SIGINT和SIGQUIT是被忽略的,意思是進(jìn)程收到這兩個(gè)信號(hào)后沒(méi)有任何動(dòng)作。?
再來(lái)看一下system()函數(shù)返回值:?
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()函數(shù)返回值,需要了解其執(zhí)行過(guò)程,實(shí)際上system()函數(shù)執(zhí)行了三步操作:?
1.fork一個(gè)子進(jìn)程;?
2.在子進(jìn)程中調(diào)用exec函數(shù)去執(zhí)行command;?
3.在父進(jìn)程中調(diào)用wait去等待子進(jìn)程結(jié)束。 對(duì)于fork失敗,system()函數(shù)返回-1。 如果exec執(zhí)行成功,也即command順利執(zhí)行完畢,則返回command通過(guò)exit或return返回的值。 (注意,command順利執(zhí)行不代表執(zhí)行成功,比如command:"rm debuglog.txt",不管文件存不存在該command都順利執(zhí)行了) 如果exec執(zhí)行失敗,也即command沒(méi)有順利執(zhí)行,比如被信號(hào)中斷,或者command命令根本不存在,system()函數(shù)返回127. 如果command為NULL,則system()函數(shù)返回非0值,一般為1.?
看一下system()函數(shù)的源碼?
看完這些,
我想肯定有人對(duì)system()函數(shù)返回值還是不清楚,看源碼最清楚,下面給出一個(gè)system()函數(shù)的實(shí)現(xiàn):?
int system(const char * cmdstring)?
{?
pid_t pid;?
int status;?
if(cmdstring == NULL)?
{?
return (1); //如果cmdstring為空,返回非零值,一般為1?
}?
if((pid = fork())<0)?
{?
status = -1; //fork失敗,返回-1?
}?
else if(pid == 0)?
{?
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);?
_exit(127); // exec執(zhí)行失敗返回127,注意exec只在失敗時(shí)才返回現(xiàn)在的進(jìn)程,成功的話現(xiàn)在的進(jìn)程就不存在啦~~?
}?
else //父進(jìn)程?
{?
while(waitpid(pid, &status, 0) < 0)?
{?
if(errno != EINTR)?
{?
status = -1; //如果waitpid被信號(hào)中斷,則返回-1?
break;?
}?
}?
}?
return status; //如果waitpid成功,則返回子進(jìn)程的返回狀態(tài)?
}?
仔細(xì)看完這個(gè)system()函數(shù)的簡(jiǎn)單實(shí)現(xiàn),那么該函數(shù)的返回值就清晰了吧,那么什么時(shí)候system()函數(shù)返回0呢?只在command命令返回0時(shí)。 看一下該怎么監(jiān)控system()函數(shù)執(zhí)行狀態(tài) 這里給我出的做法:?
int status;?
if(NULL == cmdstring) //如果cmdstring為空趁早閃退吧,盡管system()函數(shù)也能處理空指針?
{?
return XXX;?
}?
status = system(cmdstring);?
if(status < 0)?
{?
printf("cmd: %s error: %s", cmdstring, strerror(errno)); // 這里務(wù)必要把errno信息輸出或記入Log?
return XXX;?
}?
if(WIFEXITED(status))?
{?
printf("normal termination, exit status = %d ", WEXITSTATUS(status)); //取得cmdstring執(zhí)行結(jié)果 ????}?
else if(WIFSIGNALED(status))?
{?
printf("abnormal termination,signal number =%d ", WTERMSIG(status)); //如果cmdstring被信號(hào)中斷,取得信號(hào)值?
}?
else if(WIFSTOPPED(status))?
{?
printf("process stopped, signal number =%d ", WSTOPSIG(status)); //如果cmdstring被信號(hào)暫停執(zhí)行,取得信號(hào)值?
}?
至于取得子進(jìn)程返回值的相關(guān)介紹可以參考另一篇文章:http://my.oschina.net/renhc/blog/35116 system()函數(shù)用起來(lái)很容易出錯(cuò),返回值太多,而且返回值很容易跟command的返回值混淆。
這里推薦使用popen()函數(shù)替代,關(guān)于popen()函數(shù)的簡(jiǎn)單使用也可以通過(guò)上面的鏈接查看。 ?
popen()函數(shù)較于system()函數(shù)的優(yōu)勢(shì)在于使用簡(jiǎn)單,popen()函數(shù)只返回兩個(gè)值: 成功返回子進(jìn)程的status,使用WIFEXITED相關(guān)宏就可以取得command的返回結(jié)果; 失敗返回-1,我們可以使用perro()函數(shù)或strerror()函數(shù)得到有用的錯(cuò)誤信息。 這篇文章只涉及了system()函數(shù)的簡(jiǎn)單使用,還沒(méi)有談及SIGCHLD、SIGINT和SIGQUIT對(duì)system()函數(shù)的影響,事實(shí)上,之所以今天寫(xiě)這篇文章,是因?yàn)轫?xiàng)目中因有人使用了system()函數(shù)而造成了很?chē)?yán)重的事故?,F(xiàn)像是system()函數(shù)執(zhí)行時(shí)會(huì)產(chǎn)生一個(gè)錯(cuò)誤:“No child processes”。 關(guān)于這個(gè)錯(cuò)誤的分析,感興趣的朋友可以看一下:http://my.oschina.net/renhc/blog/54582 2012-04-14?
qdurenhongcai@163.com 轉(zhuǎn)載請(qǐng)注明出處。?
下面是第二篇,對(duì)于system()函數(shù)的錯(cuò)誤詳細(xì)分析,再次感謝博主?
【C/C++】Linux下system()函數(shù)引發(fā)的錯(cuò)誤
今天,一個(gè)運(yùn)行了近一年的程序突然掛掉了,問(wèn)題定位到是system()函數(shù)出的問(wèn)題,關(guān)于該函數(shù)的簡(jiǎn)單使用在我上篇文章做過(guò)介紹: http://my.oschina.net/renhc/blog/53580?
先看一下問(wèn)題 簡(jiǎn)單封裝了一下system()函數(shù):?
int pox_system(const char *cmd_line)?
{?
return system(cmd_line);?
}?
函數(shù)調(diào)用:?
int ret = 0;?
ret = pox_system("gzip -c /var/opt/I00005.xml > /var/opt/I00005.z");?
if(0 != ret)?
{?
Log("zip file failed ");?
}
問(wèn)題現(xiàn)象:每次執(zhí)行到此處,都會(huì)zip failed。而單獨(dú)把該命令拿出來(lái)在shell里執(zhí)行卻總是對(duì)的,事實(shí)上該段代碼已運(yùn)行了很長(zhǎng)時(shí)間,從沒(méi)出過(guò)問(wèn)題。?
糟糕的日志?
分析log時(shí),我們只能看到“zip file failed”這個(gè)我們自定義的信息,至于為什么fail,毫無(wú)線索。 那好,我們先試著找出更多的線索:?
int ret = 0;?
ret = pox_system("gzip -c /var/opt/I00005.xml > /var/opt/I00005.z");?
if(0 != ret)?
{?
Log("zip file failed: %s ", strerror(errno)); //嘗試打印出系統(tǒng)錯(cuò)誤信息?
}?
我們?cè)黾恿薼og,通過(guò)system()函數(shù)設(shè)置的errno,我們得到一個(gè)非常有用的線索:system()函數(shù)失敗是由于“ No child processes”。繼續(xù)找Root Cause。?
誰(shuí)動(dòng)了errno?
我們通過(guò)上面的線索,知道system()函數(shù)設(shè)置了errno為ECHILD,然而從system()函數(shù)的man手冊(cè)里我們找不到任何有關(guān)EHILD的信息。我們知道system()函數(shù)執(zhí)行過(guò)程為:fork()->exec()->waitpid().?很顯然waitpid()有重大嫌疑,我們?nèi)ゲ橐幌耺an手冊(cè),看該函數(shù)有沒(méi)有可能設(shè)置
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信號(hào)行為被設(shè)置為SIG_IGN時(shí),waitpid()函數(shù)有可能因?yàn)檎也坏阶舆M(jìn)程而報(bào)ECHILD錯(cuò)誤。似乎我們找到了問(wèn)題的解決方案:在調(diào)用system()函數(shù)前重新設(shè)置SIGCHLD信號(hào)為缺省值,即signal(SIGCHLD, SIG_DFL)。
我們很興奮,暫時(shí)顧不上看Linux Notes部分,直接加上代碼測(cè)試!乖乖,問(wèn)題解決了!?
如此處理問(wèn)題是你的風(fēng)格嗎 正當(dāng)我們急于check in 代碼時(shí),一個(gè)疑問(wèn)出現(xiàn)了:“這個(gè)錯(cuò)誤為什么以前沒(méi)發(fā)生”?是啊,運(yùn)行良好的程序怎么突然就掛了呢?首先我們代碼沒(méi)有改動(dòng),那么肯定是外部因素了。一想到外部因素,我們開(kāi)始抱怨:“肯定是其他組的程序影響我們了!”但抱怨這是沒(méi)用的,如果你這么認(rèn)為,那么請(qǐng)拿出證據(jù)!但靜下來(lái)分析一下不難發(fā)現(xiàn),這不可能是其他程序的影響,其他進(jìn)程不可能影響我們進(jìn)程對(duì)信號(hào)的處理方式。 system()函數(shù)之前沒(méi)出錯(cuò),是因?yàn)閟ysteme()函數(shù)依賴(lài)了系統(tǒng)的一個(gè)特性,那就是內(nèi)核初始化進(jìn)程時(shí)對(duì)SIGCHLD信號(hào)的處理方式為SIG_DFL,這是什么什么意思呢?即內(nèi)核發(fā)現(xiàn)進(jìn)程的子進(jìn)程終止后給進(jìn)程發(fā)送一個(gè)SIGCHLD信號(hào),進(jìn)程收到該信號(hào)后采用SIG_DFL方式處理,那么SIG_DFL又是什么方式呢?SIG_DFL是一個(gè)宏,定義了一個(gè)信號(hào)處理函數(shù)指針,事實(shí)上該信號(hào)處理函數(shù)什么也沒(méi)做。這個(gè)特性正是system()函數(shù)需要的,system()函數(shù)首先f(wàn)ork()一個(gè)子進(jìn)程執(zhí)行command命令,執(zhí)行完后system()函數(shù)會(huì)使用waitpid()函數(shù)對(duì)子進(jìn)程進(jìn)行收尸。 通過(guò)上面的分析,我們可以清醒的得知,system()執(zhí)行前,SIGCHLD信號(hào)的處理方式肯定變了,不再是SIG_DFL了,至于變成什么暫時(shí)不知道,事實(shí)上,我們也不需要知道,我們只需要記得使用system()函數(shù)前把SIGCHLD信號(hào)處理方式顯式修改為SIG_DFL方式,同時(shí)記錄原來(lái)的處理方式,使用完system()后再設(shè)為原來(lái)的處理方式。
這樣我們可以屏蔽因系統(tǒng)升級(jí)或信號(hào)處理方式改變帶來(lái)的影響。 驗(yàn)證猜想 我們公司采用的是持續(xù)集成+敏捷開(kāi)發(fā)模式,每天都會(huì)由專(zhuān)門(mén)的team負(fù)責(zé)自動(dòng)化case的測(cè)試,每次稱(chēng)為一個(gè)build,我們分析了本次build與上次build使用的系統(tǒng)版本,發(fā)現(xiàn)版本確實(shí)升級(jí)了。于是我們找到了相關(guān)team進(jìn)行驗(yàn)證,我們把問(wèn)題詳細(xì)的描述了一下,很快對(duì)方給了反饋,下面是郵件回復(fù)原文: LIBGEN 里新增加了SIGCHLD的處理。將其ignore。為了避免僵尸進(jìn)程的產(chǎn)生。 看來(lái)我們的猜想沒(méi)錯(cuò)!問(wèn)題分析到這里,解決方法也清晰了,于是我們修改了我們的pox_system()函數(shù):?
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;?
}?
我想這是調(diào)用system()比較完美的解決方案了,同時(shí)使用pox_system()函數(shù)封裝帶來(lái)了非常棒的易維護(hù)性,我們只需要修改此處一個(gè)函數(shù),其他調(diào)用處都不需要改。 后來(lái),查看了對(duì)方修改的代碼,果然從代碼上找到了答案:?
if (signal(SIGCHLD, SIG_IGN) == SIG_ERR)?
{?
return -1;?
}?
else {?
return 0;?
} 其他思考 我們公司的代碼使用SVN進(jìn)程管理的,到目前為止有很多branch,逐漸的,幾乎每個(gè)branch都出現(xiàn)了上面的問(wèn)題,于是我逐個(gè)在各個(gè)branchc上fix這個(gè)問(wèn)題,幾乎忙了一天,因?yàn)橛械腷ranch已被鎖定,再想merge代碼必須找相關(guān)負(fù)責(zé)人說(shuō)明問(wèn)題的嚴(yán)重性,還要在不同的環(huán)境上測(cè)試,我邊做這些邊想,系統(tǒng)這樣升級(jí)合適嗎? 首先,由于系統(tǒng)的升級(jí)導(dǎo)致我們的代碼在測(cè)試時(shí)發(fā)現(xiàn)問(wèn)題,這時(shí)再急忙去fix,造成了我們的被動(dòng),我想這是他們的一個(gè)失誤。你做的升級(jí)必須要考慮到對(duì)其他team的影響吧?何況你做的是系統(tǒng)升級(jí)。升級(jí)前需要做個(gè)風(fēng)險(xiǎn)評(píng)估,對(duì)可能造成的影響通知大家,這樣才職業(yè)嘛。 再者,據(jù)他們的說(shuō)法,修改信號(hào)處理方式是為了避免僵尸進(jìn)程,當(dāng)然初衷是好的,但這樣的升級(jí)影響了一些函數(shù)的使用方式,比如system()函數(shù)、wait()函數(shù)、waipid()、fork()函數(shù),這些函數(shù)都與子進(jìn)程有關(guān),如果你希望使用wait()或waitpid()對(duì)子進(jìn)程收尸,那么你必須使用上面介紹的方式:在調(diào)用前(事實(shí)上是fork()前)將SIGCHLD信號(hào)置為SIG_DFL處理方式,調(diào)用后(事實(shí)上wait()/waitpid()后)再將信號(hào)處理方式設(shè)置為從前的值。你的系統(tǒng)升級(jí),強(qiáng)制大家完善代碼,確實(shí)提高了代碼質(zhì)量,但是對(duì)于這種升級(jí)我不是很認(rèn)同,試想一下,你見(jiàn)過(guò)多少fork()->waitpid()前后都設(shè)置SIGCHLD信號(hào)的代碼??
使用system()函數(shù)的建議 上在給出了調(diào)用system()函數(shù)的比較安全的用法,但使用system()函數(shù)還是容易出錯(cuò),錯(cuò)在哪?
那就是system()函數(shù)的返回值,關(guān)于其返回值的介紹請(qǐng)見(jiàn)上篇文章。system()函數(shù)有時(shí)很方便,但不可濫用!?
1、建議system()函數(shù)只用來(lái)執(zhí)行shell命令,因?yàn)橐话銇?lái)講,system()返回值不是0就說(shuō)明出錯(cuò)了;?
2、建議監(jiān)控一下system()函數(shù)的執(zhí)行完畢后的errno值,爭(zhēng)取出錯(cuò)時(shí)給出更多有用信息;?
3、建議考慮一下system()函數(shù)的替代函數(shù)popen();其用法在我的另一篇文章有介紹。?
qdurenhongcai@163.com 轉(zhuǎn)載請(qǐng)注明出處。?
繼續(xù)轉(zhuǎn)該牛X博主的博客,對(duì)于上文提到的system()函數(shù)的替換函數(shù)popen()的詳細(xì)介紹...萬(wàn)分感謝博主:
【IPC通信】基于管道的popen和pclose函數(shù)
標(biāo)準(zhǔn)I/O函數(shù)庫(kù)提供了popen函數(shù),它啟動(dòng)另外一個(gè)進(jìn)程去執(zhí)行一個(gè)shell命令行。 這里我們稱(chēng)調(diào)用popen的進(jìn)程為父進(jìn)程,由popen啟動(dòng)的進(jìn)程稱(chēng)為子進(jìn)程。 popen函數(shù)還創(chuàng)建一個(gè)管道用于父子進(jìn)程間通信。父進(jìn)程要么從管道讀信息,要么向管道寫(xiě)信息,至于是讀還是寫(xiě)取決于父進(jìn)程調(diào)用popen時(shí)傳遞的參數(shù)。下在給出popen、pclose的定義:?
#include?
FILE * popen( const char * command,const char * type);?
int pclose(FILE * stream);?
下面通過(guò)例子看下popen的使用: 假如我們想取得當(dāng)前目錄下的文件個(gè)數(shù),在shell下我們可以使用:
ls | wc -l 我們可以在程序中這樣寫(xiě):?
#include?
#include?
#include?
#include?
#define MAXLINE 1024?
int main()?
{?
char result_buf[MAXLINE], command[MAXLINE];?
int rc = 0; // 用于接收命令返回值?
FILE *fp;?
snprintf(command, sizeof(command), "ls ./ | wc -l");
fp = popen(command, "r");?
if(NULL == fp)?
{?
perror("popen執(zhí)行失敗!");?
exit(1);?
}?
while(fgets(result_buf, sizeof(result_buf), fp) != NULL)?
{?
if(' ' == result_buf[strlen(result_buf)-1])?
{?
result_buf[strlen(result_buf)-1] = '