Linux 調研popen/system, 理解這兩個函數和fork的區別.

 

自己的總結:

          1.popen是並行(最後子進程是由pclose回收),system是串行(會等待子進程做完事,然後收拾)。

          2.system() 在等待命令終止時將忽略SIGINT 和SIGQUIT 信號,同時阻塞SIGCHLD 信號,但是popen裏面都沒有涉及到信號

          3.system返回值比較複雜,裏面有fork,exec,waitpid,返回值可能就是fork,exec,waitpid的回值,而popen會返回一個流,popen可以打開一個文件

          4.system只有一個參數,例如system("mkdir dir");創建一個目錄,popen有兩個參數,一個是文件的路徑:路徑,一個是選項

        

           5.popen() 的參數是指向以空字符結尾的字符串的指針,這些字符串分別包含一個shell 命令行和一個I/O 模式,此

模式可以是進行讀取的r ,或進行寫入的w 。

 

           6.在linux中我們可以通過system()來執行一個shell命令,popen()也是執行shell命令並且通過管道和shell命令進行通信。

system()、popen()給我們處理了fork、exec、waitpid等一系列的處理流程,讓我們只需要關注最後的返回結果(函數的返回值)即可。

 

原文://blog.csdn.net/liuxingen/article/details/47057539?utm_source=copy 

linux popen()與system()的區別

popen() 可以在調用程序和POSIX shell /usr/bin/sh 要執行的命令之間創建一個管道(請參閱sh-posix(1) )。

popen() 的參數是指向以空字符結尾的字符串的指針,這些字符串分別包含一個shell 命令行和一個I/O 模式,此

模式可以是進行讀取的r ,或進行寫入的w 。

popen() 可返回一個流指針,這樣,當I/O 模式爲w 時,便可以通過寫入文件stream 來寫入到命令的標準輸入;

當I/O 模式爲r 時,通過從文件stream 讀取數據,從命令的標準輸出讀取數據。

popen() 打開的流應由pclose() 關閉,這需要等待終止關聯的進程,然後返回命令的退出狀態。

因爲打開的文件是共享的,所以類型爲r 的命令可用作輸入過濾器,類型爲w 的命令可用作輸出過濾器。

 

system() 可執行由command 指向的字符串指定的命令。已執行命令的環境就如同使用fork() (請參閱fork(2) )

創建了一個子進程,子進程按以下方式通過調用execl() (請參閱exec(2) )來調用sh-posix(1) 實用程序:

execl("/usr/bin/sh", "sh", "-c", command, 0);

system() 在等待命令終止時將忽略SIGINT 和SIGQUIT 信號,同時阻塞SIGCHLD 信號。如果這會導致應用程

序錯過一個終止它的信號,則應用程序應檢查system() 的返回值;如果由於收到某個信號而終止了命令,應用程

序應採取一切適當的措施。

system() 不影響除自己創建的一個或多個進程以外的調用進程的任何子進程的終止狀態。

在子進程終止之前, system() 不會返回。

 

 

1. system()和popen()簡介

 

在linux中我們可以通過system()來執行一個shell命令,popen()也是執行shell命令並且通過管道和shell命令進行通信。

system()、popen()給我們處理了fork、exec、waitpid等一系列的處理流程,讓我們只需要關注最後的返回結果(函數的返回值)即可。

 

2. system()、popen()源碼

 

首先我們來看一下這兩個函數在源碼(僞代碼)上面的差異。

 

1. system()和popen()簡介

在linux中我們可以通過system()來執行一個shell命令,popen()也是執行shell命令並且通過管道和shell命令進行通信。 
system()、popen()給我們處理了fork、exec、waitpid等一系列的處理流程,讓我們只需要關注最後的返回結果(函數的返回值)即可。

2. system()、popen()源碼

int system(const char *command)
{
    struct sigaction sa_ignore, sa_intr, sa_quit;
    sigset_t block_mask, orig_mask;
    pid_t pid;

    sigemptyset(&block_mask);
    sigaddset(&block_mask, SIGCHLD);
    sigprocmask(SIG_BLOCK, &block_mask, &orig_mask);        //1. block SIGCHLD

    sa_ignore.sa_handler = SIG_IGN;
    sa_ignore.sa_flags = 0;
    sigemptyset(&sa_ignore.sa_mask);
    sigaction(SIGINT, &sa_ignore, &sa_intr);                //2. ignore SIGINT signal
    sigaction(SIGQUIT, &sa_ignore, &sa_quit);                //3. ignore SIGQUIT signal

    switch((pid = fork()))
    {
        case -1:
            return -1;
        case 0:
            sigaction(SIGINT, &sa_intr, NULL); 
            sigaction(SIGQUIT, &sa_quit, NULL); 
            sigprocmask(SIG_SETMASK, &orig_mask, NULL);
            execl("/bin/sh", "sh", "-c", command, (char *) 0);
            exit(127);
        default:
            while(waitpid(pid, NULL, 0) == -1)    //4. wait child process exit
            {
                if(errno != EINTR)
                {
                    break;
                }
            }
    }
}

上面是一個不算完整的system函數源碼,後面需要我們關注和popen差異的部分已經用數字標示出來了。

static pid_t    *childpid = NULL;  
                        /* ptr to array allocated at run-time */  
static int      maxfd;  /* from our open_max(), {Prog openmax} */  

#define SHELL   "/bin/sh"  

FILE *  
popen(const char *cmdstring, const char *type)  
{  
    int     i, pfd[2];  
    pid_t   pid;  
    FILE    *fp;  

            /* only allow "r" or "w" */  
    if ((type[0] != 'r' && type[0] != 'w') || type[1] != 0) {  
        errno = EINVAL;     /* required by POSIX.2 */  
        return(NULL);  
    }  

    if (childpid == NULL) {     /* first time through */  
                /* allocate zeroed out array for child pids */  
        maxfd = open_max();  
        if ( (childpid = calloc(maxfd, sizeof(pid_t))) == NULL)  
            return(NULL);  
    }  

    if (pipe(pfd) < 0)  
        return(NULL);   /* errno set by pipe() */  

    if ( (pid = fork()) < 0)  
        return(NULL);   /* errno set by fork() */  
    else if (pid == 0) {                            /* child */  
        if (*type == 'r') {  
            close(pfd[0]);  
            if (pfd[1] != STDOUT_FILENO) {  
                dup2(pfd[1], STDOUT_FILENO);  
                close(pfd[1]);  
            }  
        } else {  
            close(pfd[1]);  
            if (pfd[0] != STDIN_FILENO) {  
                dup2(pfd[0], STDIN_FILENO);  
                close(pfd[0]);  
            }  
        }  
            /* close all descriptors in childpid[] */  
        for (i = 0; i < maxfd; i++)  
            if (childpid[ i ] > 0)  
                close(i);  

        execl(SHELL, "sh", "-c", cmdstring, (char *) 0);  
        _exit(127);  
    }  
                                /* parent */  
    if (*type == 'r') {  
        close(pfd[1]);  
        if ( (fp = fdopen(pfd[0], type)) == NULL)  
            return(NULL);  
    } else {  
        close(pfd[0]);  
        if ( (fp = fdopen(pfd[1], type)) == NULL)  
            return(NULL);  
    }  
    childpid[fileno(fp)] = pid; /* remember child pid for this fd */  
    return(fp);  
}  

上面是popen的源碼。

3. 執行流程

從上面的源碼可以看到system和popen都是執行了類似的運行流程,大致是fork->execl->return。但是我們看到system在執行期間調用進程會一直等待shell命令執行完成(waitpid等待子進程結束)才返回,但是popen無須等待shell命令執行完成就返回了。我們可以理解system爲串行執行,在執行期間調用進程放棄了”控制權”,popen爲並行執行。 
popen中的子進程沒人給它”收屍”了啊?是的,如果你沒有在調用popen後調用pclose那麼這個子進程就可能變成”殭屍”。 
上面我們沒有給出pclose的源碼,其實我們根據system的源碼差不多可以猜測出pclose的源碼就是system中第4部分的內容。

4. 信號處理

我們看到system中對SIGCHLD、SIGINT、SIGQUIT都做了處理,但是在popen中沒有對信號做任何的處理。 
SIGCHLD是子進程退出的時候發給父進程的一個信號,system()中爲什麼要屏蔽SIGCHLD信號可以參考:system函數的總結、waitpid(or wait)和SIGCHILD的關係,總結一句就是爲了system()調用能夠及時的退出並且能夠正確的獲取子進程的退出狀態(成功回收子進程)。 
popen沒有屏蔽SIGCHLD,主要的原因就是popen是”並行”的。如果我們在調用popen的時候屏蔽了SIGCHLD,那麼如果在調用popen和pclose之間調用進程又創建了其它的子進程並且調用進程註冊了SIGCHLD信號處理句柄來處理子進程的回收工作(waitpid)那麼這個回收工作會一直阻塞到pclose調用。這也意味着如果調用進程在pclose之前執行了一個wait()操作的話就可能獲取到popen創建的子進程的狀態,這樣在調用pclose的時候就會回收(waitpid)子進程失敗,返回-1,同時設置errno爲ECHLD,標示pclose無法獲取子進程狀態。 
system()中屏蔽SIGINT、SIGQUIT的原因可以繼續參考上面提到的system函數的總結,popen()函數中沒有屏蔽SIGINT、SIGQUIT的原因也還是因爲popen是”並行的”,不能影響其它”並行”進程。

5. 功能

從上面的章節我們基本已經把這兩個函數剖析的差不多了,這兩個的功能上面的差異也比較明顯了,system就是執行shell命令最後返回是否執行成功,popen執行命令並且通過管道和shell命令進行通信。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章