linux下,system函數和execl函數都是用於執行一條系統命令。今天仔細看了system函數的實現,想找出和execl函數的差別。
這里先進行一些背景知識補充:
fork(創建一個新的進程):
定義函數 pid_t fork(void);
函數說明 fork()會產生一個新的子進程,其子進程會復制父進程的數據與堆棧空間,並繼承父進程的用戶代碼,組代碼,環境變量、已打開的文件代碼、工作目錄和資源限制等。
返回值 如果fork()成功則在父進程會返回新建立的子進程代碼(PID),而在新建立的子進程中則返回0。如果fork 失敗則直接返回-1,失敗原因存於errno中。
waitpid(等待子進程中斷或結束):
定義函數 pid_t waitpid(pid_t pid,int * status,int options);
函數調用 waitpid(pid, NULL, 0);
函數說明 waitpid()會暫時停止目前進程的執行,直到有信號來到或子進程結束。如果在調用waitpid()時子進程已經結束,則wait()會立即返回子進程結束狀態值。子進程的結束狀態值會由參數status返回,而子進程的進程識別碼也會一快返回。如果不在意結束狀態值,則參數status可以設成NULL。參數pid為欲等待的子進程識別碼,其他數值意義如下:
pid<-1 等待進程組識別碼為pid絕對值的任何子進程。
pid=-1 等待任何子進程,相當於wait()。
pid=0 等待進程組識別碼與目前進程相同的任何子進程。
pid>0 等待任何子進程識別碼為pid的子進程。
返回值 如果執行成功則返回子進程識別碼(PID),如果有錯誤發生則返回-1。失敗原因存於errno。
waitpid把子進程的結束狀態返回后存於status,有幾個宏可判別結束情況:
WIFEXITED(status) 這個宏用來指出子進程是否為正常退出的(通過exit()或者_exit()),如果是,它會返回一個非零值。
WEXITSTATUS(status) 當WIFEXITED返回非零值時,我們可以用這個宏來提取子進程的返回值,如果子進程調用exit(5)退出,WEXITSTATUS(status)就會返回5;如果子進程調用exit(7),WEXITSTATUS(status)就會返回7。請注意,如果進程不是正常退出的,也就是說,WIFEXITED返回0,這個值就毫無意義。
WIFSIGNALED(status)如果子進程是因為信號而結束則此宏值為真。
WTERMSIG(status)取得子進程因信號而中止的信號代碼,一般會先用WIFSIGNALED 來判斷后才使用此宏。
WIFSTOPPED(status)如果子進程處於暫停執行情況則此宏值為真。一般只有使用WUNTRACED 時才會有此情況。
WSTOPSIG(status)取得引發子進程暫停的信號代碼,一般會先用WIFSTOPPED 來判斷后才使用此宏。
接下來進入正題看看execl和system函數:
execl:
定義函數 int execl(const char * path,const char * arg,....);
函數說明 execl()用來執行參數path字符串所代表的文件路徑,接下來的參數代表執行該文件時傳遞過去的argv(0)、argv[1]……,最后一個參數必須用空指針(NULL)作結束。
返回值 如果執行成功則函數不會返回,執行失敗則直接返回-1,失敗原因存於errno中。
調用ls命令范例: execl("/bin/ls", "/bin/ls", "-l" , "/etc", NULL);
exec系列函數的特點很明顯,就是執行成功的時候是不會返回的,一旦返回一定是有錯誤產生了。但是請注意,執行成功的意思是這條命令本身沒錯,但是這條命令成不成功exec管不了,他只負責執行,比如說"rm 1.txt",這條指令是正確的,即使沒有1.txt這個文件報了無法刪除1.txt文件,exec函數還是算執行成功,不會有返回。我們看下例子:
#include <stdio.h> int main(int argc, char* argv[]){ int a = execl("/bin/rm", "rm", "1.txt", NULL); printf("%d\n", a); printf("exiting...\n"); return 0; }
上面這段代碼的執行結果是:
明顯沒有返回值,我們再看看執行出錯的返回值:
#include <stdio.h> int main(int argc, char* argv[]){ int a = execl("aaa", "bbb", NULL); printf("%d\n", a); printf("exiting...\n"); return 0; }
上面這段代碼的執行結果:
很明顯,返回值是-1。這個時候execl函數就執行失敗了。
那么如果想要調用一個系統命令,又想要返回呢?這時候system函數就可以幫上忙了。實際上,system也是調用了exec函數去執行一個系統命令,可以把system函數理解成對exec函數的一個包裝。可是,光光包裝起來,加個返回值,有可能嗎?比如說把return語句在exec后面,根據exec系列的函數的特性,return語句肯定不會執行,那system函數到底是怎么實現的呢?實際上,system函數的具體執行步驟是這樣的:
1.fork一個子進程;
2.在子進程中調用exec函數去執行command;
3.在父進程中調用wait去等待子進程結束。
創建出一個子進程,然后在子進程中用exec來執行命令,即是子進程成功執行了沒返回也沒關系,還有父進程可以返回嘛!
接下來我們仔細看看system的詳細介紹。
#include <stdlib.h> 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.
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執行失敗返回127,注意exec只在失敗時才返回現在的進程,成功的話現在的進程就不存在啦~~ }else //父進程 { while(waitpid(pid, &status, 0) < 0) { if(errno != EINTR) { status = -1; //如果waitpid被信號中斷,則返回-1 break; } } } return status; //如果waitpid成功,則返回子進程的返回狀態 }
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
文章轉自: