(轉)linux下execl和system函數


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的詳細介紹。

先來看一下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.

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)
如果exec執行失敗,也即command沒有順利執行,比如被信號中斷,或者command命令根本不存在,system()函數返回127.
如果command為NULL,則system()函數返回非0值,一般為1.

看一下system()函數的源碼
看完這些,我想肯定有人對system()函數返回值還是不清楚,看源碼最清楚,下面給出一個system()函數的實現:
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成功,則返回子進程的返回狀態
}

 

仔細看完這個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

 

 

文章轉自:

https://my.oschina.net/renhc/blog/53580

http://blog.sina.com.cn/s/blog_4da4ea3c0101ojnr.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM