linux下system函式錯誤返回-1 錯誤原因NO child processes
呼叫system函式執行一個shell命令,返回-1,錯誤提示no child processes 但system可以執行成功
原因是呼叫system之前有放置忽略SIGCHLD的語句
signal(SIGCHLD, SIG_IGN);
如果SIGCHLD訊號行為被設定為SIG_IGN時,waitpid()函式有可能因為找不到子程序而報ECHILD錯誤。似乎我們找到了問題的解決方案:在呼叫system()函式前重新設定SIGCHLD訊號為預設值,即signal(SIGCHLD, SIG_DFL)。
解決辦法 用pox_system()函式替代system(),只需要修改此處一個函式,其他呼叫處都不需要改。
- typedefvoid (*sighandler_t)(int);
- int pox_system(constchar *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;
- }
SIG_DFL:預設訊號處理程式
SIG_IGN:忽略訊號的處理程式
測試過確實很奏效,感謝帖子的作者!
參考文章:http://my.oschina.net/renhc/blog/54582
原文如下:
今天,一個運行了近一年的程式突然掛掉了,問題定位到是system()函式出的問題,關於該函式的簡單使用在我上篇文章做過介紹: http://my.oschina.net/renhc/blog/53580先看一下問題
簡單封裝了一下system()函式:
1 |
int pox_system( const char *cmd_line) |
2 |
{ |
3 |
return system (cmd_line); |
4 |
} |
1 |
int ret = 0; |
2 |
ret = pox_system( "gzip -c /var/opt/I00005.xml > /var/opt/I00005.z" ); |
3 |
if (0 != ret) |
4 |
{ |
5 |
Log( "zip file failed\n" ); |
6 |
} |
糟糕的日誌
分析log時,我們只能看到“zip file failed”這個我們自定義的資訊,至於為什麼fail,毫無線索。
那好,我們先試著找出更多的線索:1 |
int ret = 0; |
2 |
ret = pox_system( "gzip -c /var/opt/I00005.xml > /var/opt/I00005.z" ); |
3 |
if (0 != ret) |
4 |
{ |
5 |
Log( "zip file failed: %s\n" , strerror ( errno )); //嘗試打印出系統錯誤資訊 |
6 |
} |
誰動了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) |
03 |
{ |
04 |
int ret = 0; |
05 |
sighandler_t old_handler; |
06 |
07 |
old_handler = signal (SIGCHLD, SIG_DFL); |
08 |
ret = system (cmd_line); |
09 |
signal (SIGCHLD, old_handler); |
10 |
11 |
return ret; |
12 |
} |
我想這是呼叫system()比較完美的解決方案了,同時使用pox_system()函式封裝帶來了非常棒的易維護性,我們只需要修改此處一個函式,其他呼叫處都不需要改。
後來,查看了對方修改的程式碼,果然從程式碼上找到了答案:
1 |
/* Ignore SIGCHLD to avoid zombie process */ |
2 |
if ( signal (SIGCHLD, SIG_IGN) == SIG_ERR)
{ |
3 |
return -1; |
4 |
} else { |
5 |
return 0; |
6 |
} |
其他思考
我們公司的程式碼使用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();其用法在我的另一篇文章有介紹。
本文擷取自網路。