Linux的system()和popen()差異__Linux

來源:互聯網
上載者:User

https://blog.csdn.net/liuxingen/article/details/47057539

Linux的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;                }            }    }}return 0;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

上面是一個不算完整的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);  }  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66

上面是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是”並行的”,不能影響其它”並行”進程。 4. 功能

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

在特權(setuid、setgid)進程中千萬注意不要使用system和popen。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.