對於linux下system 函式的深度理解 整理
阿新 • • 發佈:2018-11-15
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
這幾天調程式(嵌入式linux),發現程式有時就莫名其妙的死掉,每次都定位在程式中不同的system()函式,直接在shell下輸入system()函式中呼叫的命令也都一切正常.就沒理這個bug,以為是其他的程式碼影響到這個,或是核心驅動檔案系統什麼的異常導致,昨天有出現了這個問題,就隨手百了一下度,問題出現了,很多人都說system()函式要慎用要少用要能不用則不用,system()函式不穩定?仔細看完這個system()函式的簡單實現,那麼該函式的返回值就清晰了吧,那麼什麼時候system()函式返回0呢?只在command命令返回0時。 看一下該怎麼監控system()函式執行狀態 這裡給我出的做法: int status; if(NULL == cmdstring) //如果cmdstring為空趁早閃退吧,儘管system()函式也能處理空指標 { return XXX; } status = system(cmdstring); if(status < 0) { printf("cmd: %s\t error: %s", cmdstring, strerror(errno)); // 這裡務必要把errno資訊輸出或記入Log return XXX; }
if(WIFEXITED(status)) { printf("normal termination, exit status = %d\n", WEXITSTATUS(status)); //取得cmdstring執行結果 } else if(WIFSIGNALED(status)) { printf("abnormal termination,signal number =%d\n", WTERMSIG(status)); //如果cmdstring被訊號中斷,取得訊號值 } else if(WIFSTOPPED(status)) { 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 2012-04-14 [email protected] 轉載請註明出處。
下面是第二篇,對於system()函式的錯誤詳細分析,再次感謝博主 【C/C++】Linux下system()函式引發的錯誤 今天,一個運行了近一年的程式突然掛掉了,問題定位到是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()函式封裝帶來了非常棒的易維護性,我們只需要修改此處一個函式,其他呼叫處都不需要改。 後來,查看了對方修改的程式碼,果然從程式碼上找到了答案: 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();其用法在我的另一篇文章有介紹。
[email protected] 轉載請註明出處。
繼續轉該牛X博主的部落格,對於上文提到的system()函式的替換函式popen()的詳細介紹...萬分感謝博主: 【IPC通訊】基於管道的popen和pclose函式 標準I/O函式庫提供了popen函式,它啟動另外一個程序去執行一個shell命令列。 這裡我們稱呼叫popen的程序為父程序,由popen啟動的程序稱為子程序。 popen函式還建立一個管道用於父子程序間通訊。父程序要麼從管道讀資訊,要麼向管道寫資訊,至於是讀還是寫取決於父程序呼叫popen時傳遞的引數。下在給出popen、pclose的定義: #include
FILE * popen( const char * command,const char * type); int pclose(FILE * stream); 下面通過例子看下popen的使用: 假如我們想取得當前目錄下的檔案個數,在shell下我們可以使用: ls | wc -l 我們可以在程式中這樣寫: #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執行失敗!"); exit(1); } while(fgets(result_buf, sizeof(result_buf), fp) != NULL) { if('\n' == result_buf[strlen(result_buf)-1]) { result_buf[strlen(result_buf)-1] = '\0'; } printf("命令【%s】 輸出【%s】\r\n", command, result_buf); } rc = pclose(fp); if(-1 == rc) { perror("關閉檔案指標失敗"); exit(1); } else { printf("命令【%s】子程序結束狀態【%d】命令返回值【%d】\r\n", command, rc, WEXITSTATUS(rc)); } return 0; } 編譯並執行: $ gcc popen.c $ ./a.out 命令【ls ./ | wc -l】 輸出【2】 命令【ls ./ | wc -l】子程序結束狀態【0】命令返回值【0】 上面popen只捕獲了command的標準輸出,如果command執行失敗,子程序會把錯誤資訊列印到標準錯誤輸出,父程序就無法獲取。比如,command命令為“ls nofile.txt” ,事實上我們根本沒有nofile.txt這個檔案,這時shell會輸出“ls: nofile.txt: No such file or directory”。這個輸出是在標準錯誤輸出上的。通過上面的程式並無法獲取。 注:如果你把上面程式中的command設成“ls nofile.txt”,編譯執行程式你會看到如下結果: $ gcc popen.c $ ./a.out ls: nofile.txt: No such file or directory 命令【ls nofile.txt】子程序結束狀態【256】命令返回值【1】 需要注意的是第一行輸出並不是父程序的輸出,而是子程序的標準錯誤輸出。 有時子程序的錯誤資訊是很有用的,那麼父程序怎麼才能獲取子程序的錯誤資訊呢? 這裡我們可以重定向子程序的錯誤輸出,讓錯誤輸出重定向到標準輸出(2>&1),這樣父程序就可以捕獲子程序的錯誤資訊了。 例如command為“ls nofile.txt 2>&1”,輸出如下: 命令【ls nofile.txt2>&1】 輸出【ls: nofile.txt: No such file or directory】 命令【ls nofile.txt 2>&1】子程序結束狀態【256】命令返回值【1】 附:子程序的終止狀態判斷涉及到的巨集,設程序終止狀態為status. WIFEXITED(status)如果子程序正常結束則為非0值。 WEXITSTATUS(status)取得子程序exit()返回的結束程式碼,一般會先用WIFEXITED 來判斷是否正常結束才能使用此巨集。 WIFSIGNALED(status)如果子程序是因為訊號而結束則此巨集值為真。 WTERMSIG(status)取得子程序因訊號而中止的訊號程式碼,一般會先用WIFSIGNALED 來判斷後才使用此巨集。 WIFSTOPPED(status)如果子程序處於暫停執行情況則此巨集值為真。一般只有使用WUNTRACED 時才會有此情況。 WSTOPSIG(status)取得引發子程序暫停的訊號程式碼,一般會先用WIFSTOPPED 來判斷後才使用此巨集。 2011-11-12 任洪彩 [email protected] 轉載請註明出處。
但是根據上面那位博主說的使用system()函式前把SIGCHLD訊號處理方式顯式修改為SIG_DFL方式,同時記錄原來的處理方式,使用完system()後再設為原來的處理方式後,程式還是會死掉.而且看不到system的返回值是多少(因為system在執行系統命令的時候,程式已經掛掉了),故暫時使用博主提到的第二種解決方式使用popen()函式替代system()函式.修改後的函式如下 int my_system(const char * cmd) { FILE * fp; int res; char buf[1024]; if (cmd == NULL) { printf("my_system cmd is NULL!\n"); return -1; } if ((fp = popen(cmd, "r") ) == NULL) { perror("popen"); printf("popen error: %s/n", strerror(errno)); return -1; } else { while(fgets(buf, sizeof(buf), fp)) { printf("%s", buf); } if ( (res = pclose(fp)) == -1) { printf("close popen file pointer fp error!\n"); return res; } else if (res == 0) { return res; } else { printf("popen res is :%d\n", res); return res; } } } 此時呼叫my_system()來執行system函式的功能(my_system函式中是使用popen()函式來實現的), 測試了一天,沒有再次出現程式突然死掉的問題(修改前連續迴圈呼叫system()函式測試,每10次就會至少導致程式掛掉一次.連續不停頓的呼叫). 以上是我對這個問題的總結,先做個記錄,待修復bug後再回來仔細研究. 原文地址: http://blog.sina.com.cn/s/blog_8043547601017qk0.html