linux 標準流管道 popen 源碼理解

標準流管道popen、pclose函數說明:

#include <stdio.h>
FILE *popen(const char *command, const char *type)

返回值:若成功,返回文件流指針;若出錯,返回-1
參數說明:

  • Command:指向的是一個以 null 結束符結尾的字符串,這個字符串包含一個 shell 命令,並被送到/bin/sh 以-c 參數執行,即由 shell 來執行
  • type:
    • ”r”: 文件指針連接到 command 的標準輸出
    • “w” :文件指針連接到 command 的標準輸入
#include <stdio.h>
int pclose(FILE *stream)

返回值:若成功,返回 popen 中執行命令的終止狀態;若出錯,返回-1
參數說明:

  • stream:要關閉的文件流

popen函數其實是對管道操作的一些包裝,所完成的工作有以下幾步:

  • 創建一個管道。
  • fork 一個子進程。
  • 在父子進程中關閉不需要的文件描述符。
  • 執行 exec 函數族調用。
  • 執行函數中所指定的命令。

可以對照着Richard Stevens 實現的源碼加深理解。
linux popen源碼:

/* 
 *  popen.c     Written by W. Richard Stevens 
 */  

#include    <sys/wait.h>  
#include    <errno.h>  
#include    <fcntl.h>  
#include    "ourhdr.h"  

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);  
}  

int  
pclose(FILE *fp)  
{  

    int     fd, stat;  
    pid_t   pid;  

    if (childpid == NULL)  
        return(-1);     /* popen() has never been called */  

    fd = fileno(fp);  
    if ( (pid = childpid[fd]) == 0)  
        return(-1);     /* fp wasn't opened by popen() */  

    childpid[fd] = 0;  
    if (fclose(fp) == EOF)  
        return(-1);  

    while (waitpid(pid, &stat, 0) < 0)  
        if (errno != EINTR)  
            return(-1); /* error other than EINTR from waitpid() */  

    return(stat);   /* return child's termination status */  
}  

接下來看一個簡單的示例程序:

/* popen.c*/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#define BUFSIZE 1000
int main()
{
    FILE *fp;
    char *cmd = "ls -al";
    char buf[BUFSIZE];
    if((fp=popen(cmd,"r"))==NULL)   //調用 popen 函數執行命令ls -al
        perror("popen");
    fread( buf, sizeof(char), sizeof(buf),fp);  //將剛剛fp指向的文件流讀取到buf中
    printf("%s",buf);
    pclose(fp);
    exit(0);
}

實驗結果:

ubuntu:~/test/process_test$ gcc popen.c -o popen
ubuntu:~/test/process_test$ ./popen
total 92
drwxr-xr-x  2  4096 Aug 15 16:56 .
drwxr-xr-x 19  4096 Aug 15 10:12 ..
-rwxr-xr-x  1    12 Aug 15 11:16 file_fork
-rwxr-xr-x  1  8784 Aug 15 10:46 fork
-rwxr-xr-x  1  8785 Aug 15 10:45 fork2
-rwxr--r--  1   571 Aug 15 11:25 fork2.c
-rwxr--r--  1   469 Aug 15 10:42 fork.c
-rwxr-xr-x  1  8890 Aug 15 11:16 open_fork
-rwxr--r--  1  1114 Aug 15 11:25 open_fork.c
-rwxr-xr-x  1  8884 Aug 15 16:16 pipe
-rwxr--r--  1  1677 Aug 15 16:16 pipe.c
-rwxr-xr-x  1  8683 Aug 15 16:56 popen
-rwxr--r--  1   409 Aug 15 16:56 popen.c

在終端執行shell命令”ls -al”

ubuntu:~/test/process_test$ ls -al
total 92       
drwxr-xr-x  2  4096 Aug 15 16:56 .
drwxr-xr-x 19  4096 Aug 15 10:12 ..
-rwxr-xr-x  1    12 Aug 15 11:16 file_fork
-rwxr-xr-x  1  8784 Aug 15 10:46 fork
-rwxr-xr-x  1  8785 Aug 15 10:45 fork2
-rwxr--r--  1   571 Aug 15 11:25 fork2.c
-rwxr--r--  1   469 Aug 15 10:42 fork.c
-rwxr-xr-x  1  8890 Aug 15 11:16 open_fork
-rwxr--r--  1  1114 Aug 15 11:25 open_fork.c
-rwxr-xr-x  1  8884 Aug 15 16:16 pipe
-rwxr--r--  1  1677 Aug 15 16:16 pipe.c
-rwxr-xr-x  1  8683 Aug 15 16:56 popen
-rwxr--r--  1   409 Aug 15 16:56 popen.c

實驗結果跟在終端執行shell命令是一樣的。理解源碼之後,對popen理解也更加深刻,在不同場景中運用也更加自如。

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