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命令進行通訊。