system()、popen()

linux中我们可以通过system()来执行一个shell命令,popen()也是执行shell命令并且通过管道和shell命令进行通信。 

system()popen()给我们处理了forkexecwaitpid等一系列的处理流程,让我们只需要关注最后的返回结果(函数的返回值)即可。

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. 执行流程

    从上面的源码可以看到systempopen都是执行了类似的运行流程,大致是fork->execl->return。但是我们看到system在执行期间调用进程会一直等待shell命令执行完成(waitpid等待子进程结束)才返回,但是popen无须等待shell命令执行完成就返回了。我们可以理解system为串行执行,在执行期间调用进程放弃了"控制权"popen为并行执行。 
    popen
    中的子进程没人给它"收尸"了啊?是的,如果你没有在调用popen后调用pclose那么这个子进程就可能变成"僵尸" 
    上面我们没有给出pclose的源码,其实我们根据system的源码差不多可以猜测出pclose的源码就是system中第4部分的内容。

    4. 信号处理

    我们看到system中对SIGCHLDSIGINTSIGQUIT都做了处理,但是在popen中没有对信号做任何的处理。 
    SIGCHLD
    是子进程退出的时候发给父进程的一个信号,system()中为什么要屏蔽SIGCHLD信号可以参考:system函数的总结waitpid(or wait)SIGCHILD的关系,总结一句就是为了system()调用能够及时的退出并且能够正确的获取子进程的退出状态(成功回收子进程) 
    popen
    没有屏蔽SIGCHLD,主要的原因就是popen"并行"的。如果我们在调用popen的时候屏蔽了SIGCHLD,那么如果在调用popenpclose之间调用进程又创建了其它的子进程并且调用进程注册了SIGCHLD信号处理句柄来处理子进程的回收工作(waitpid)那么这个回收工作会一直阻塞到pclose调用。这也意味着如果调用进程在pclose之前执行了一个wait()操作的话就可能获取到popen创建的子进程的状态,这样在调用pclose的时候就会回收(waitpid)子进程失败,返回-1,同时设置errnoECHLD,标示pclose无法获取子进程状态。 
    system()
    中屏蔽SIGINTSIGQUIT的原因可以继续参考上面提到的system函数的总结popen()函数中没有屏蔽SIGINTSIGQUIT的原因也还是因为popen"并行的",不能影响其它"并行"进程。

    4. 功能

    从上面的章节我们基本已经把这两个函数剖析的差不多了,这两个的功能上面的差异也比较明显了,system就是执行shell命令最后返回是否执行成功,popen执行命令并且通过管道和shell命令进行通信。

    NOTE

    在特权(setuidsetgid)进程中千万注意不要使用systempopen

    本文介绍popen函数的使用方法和行为机理,并给出实际的例子来辅助说明了popen函数的使用方法。

    popen函数使用FIFO管道执行外部程序,首先让我们看看popen的函数原型吧:

    #include <stdio.h>

    FILE *popen(const char *command, const char *type);

    int pclose(FILE *stream);

           popen 通过typer 还是w 来确定command的输入/输出方向,rw是相对command的管道而言的。r表示command从管道中读入,w表示 command通过管道输出到它的stdoutpopen返回FIFO管道的文件流指针。pclose则用于使用结束后关闭这个指针。

    下面一起来看一个例子:

    /*******************************************************************************************

    ** Name:popen.c

    **      This program is used to show the usage of popen() .

    *******************************************************************************************/

    #include <sys/types.h> 

    #include <unistd.h> 

    #include <stdlib.h> 

    #include <stdio.h> 

    #include <string.h>

    int main( void ) 

       FILE   *stream; 

       FILE   *wstream;

       char   buf[1024]; 

         

        memset( buf, '\0', sizeof(buf) );//初始化buf,以免后面写如乱码到文件中

        stream = popen( "ls -l", "r" ); //"ls l"命令的输出 通过管道读取("r"参数)到FILE* stream

        wstream = fopen( "test_popen.txt", "w+"); //新建一个可写的文件

        fread( buf, sizeof(char), sizeof(buf), stream); //将刚刚FILE* stream的数据流读取到buf

        fwrite( buf, 1, sizeof(buf), wstream );//buf中的数据写到FILE    *wstream对应的流中,也是写到文件中

          

        pclose( stream ); 

        fclose( wstream );

          

        return 0;

    }  

    [root@localhost src]# gcc popen.c 

    [root@localhost src]# ./a.out   

    [root@localhost src]# cat test_popen.txt 

    总计 128

    -rwxr-xr-x 1 root root 5558 09-30 11:51 a.out

    -rwxr-xr-x 1 root root 542 09-30 00:00 child_fork.c

    -rwxr-xr-x 1 root root 480 09-30 00:13 execve.c

    -rwxr-xr-x 1 root root 1811 09-29 21:33 fork.c

    -rwxr-xr-x 1 root root 162 09-29 18:54 getpid.c

    -rwxr-xr-x 1 root root 1105 09-30 11:49 popen.c

    -rwxr-xr-x 1 root root 443 09-30 00:55 system.c

    -rwxr-xr-x 1 root root    0 09-30 11:51 test_popen.txt

    -rwxr-xr-x 1 root root 4094 09-30 11:39 test.txt

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值