浅聊一下system()函数与popen()函数

浅聊一下system()函数与popen()函数

文章目录

  • 浅聊一下system()函数与popen()函数
    • 1.system()函数
    • 2.popen()函数
    • 3.区别
    • 总结:

1.system()函数

system()函数先fork一个子进程,在这个子进程中调用/bin/sh -c来执行command指定的命令。/bin/sh在系统中一般是个软链接,指向dash或者bash等常用的shell,-c选项是告诉shell从字符串command中读取要执行的命令。父进程则调用waitpid()函数来为变成僵尸的子进程收尸,获得其结束状态,然后将这个结束状态返回给system()函数的调用者。

system()源码:

int system(const char * cmdstring) {
    pid_t pid;
    int status;
    if(cmdstring == NULL) {
        return (1); //如果cmdstring为空,返回非零值,一般为1
    }
    if((pid = fork())<0) {
        status = -1; //fork失败,返回-1
    } else if (pid == 0) {
        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
        _exit(127); // exec执行失败返回127,注意exec只在失败时才返回现在的进程,成功的话现在的进程就不存在
    } else { //父进程
        while(waitpid(pid, &status, 0) < 0) {
            if(errno != EINTR) {
                status = -1; //如果waitpid被信号中断,则返回-1
                break;
            }
        }
    }
    return status; //如果waitpid成功,则返回子进程的返回状态
}

(1)当参数command是NULL的时候
返回1,表明系统的命令处理程序,即/bin/sh是可用的。
返回0,命令处理程序不可用。
(2)当参数command不是NULL的时候
1)如果fork等系统调用失败,或者waitpid函数发生除EINTR外的错误时,system返回-1
2)一切致使execl失败的情况下,system返回127
3)除此之外,system返回/bin/sh的终止状态

2.popen()函数

popen总是和pclose一起出现被使用的。popen() 创建一个匿名管道,通过fork或者invoke一个子进程,然后执行command。返回值在标准IO流中,由于是在管道之中,因此数据流是单向的,command只能产生stdout或者读取stdin,因此type只有两个值:‘w’或‘r’。r表示command从管道中读取数据流,而w表示command的stdout输出到管道中。command无法同时读取和输出。popen返回该FIFO数据流的指针。并行执行,可以在程序中读取命令的标准输出,也可在程序中写入命令的标准输入。
pclose函数关闭标准IO流,等待命令的终止,然后返回shell的终止状态。如果你没有在调用popen后调用pclose那么这个子进程就可能变成”僵尸”。
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() */  
    
    /* child */ 
    else if (pid == 0) {                             
        if (*type == 'r') {  
            close(pfd[0]);  
            if (pfd[1] != STDOUT_FILENO) {  
                dup2(pfd[1], STDOUT_FILENO);  // 子进程的标准输出写到pfd[1]
                close(pfd[1]);  
            }  
        } else {  
            close(pfd[1]);  
            if (pfd[0] != STDIN_FILENO) {  
                dup2(pfd[0], STDIN_FILENO);  // 子进程从的标准输入从pfd[0]读入
                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);  
}  

若成功则返回文件指针,否则返回NULL,错误原因存于errno中。

3.区别

1.功能:

  • popen()函数:popen()函数可以用来创建一个管道,通过管道与子进程进行双向通信。它可以执行一个外部命令,并返回一个文件指针,可以用于读取子进程的输出或向子进程发送输入。
  • system()函数:system()函数用于执行一个外部命令,但它只能执行命令,无法直接获取子进程的输入或输出。

2.使用方式:

  • popen()函数:popen()函数需要传入两个参数,第一个参数是要执行的命令字符串,第二个参数是打开模式("r"用于读取子进程输出,"w"用于向子进程发送输入)。它返回一个标准I/O文件指针,可以用于读取子进程的输出或向子进程发送输入。使用完毕后,需要通过pclose()函数关闭文件指针。
  • system()函数:system()函数只需要传入要执行的命令字符串作为参数即可。它会在执行命令完成后返回,并返回命令的退出状态。

3.安全性:

  • popen()函数:popen()函数对于执行外部命令的参数需要进行适当的过滤和验证,以防止命令注入等安全漏洞。
  • system()函数:system()函数对于参数的安全性验证较弱,如果参数字符串来自用户输入等不可信源,存在命令注入的风险。

4.信号处理

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

总结:

  • popen()函数适合在需要与子进程进行双向通信的情况下使用,可以方便地读取子进程的输出或向子进程发送输入。
  • system()函数适合简单的命令执行,不需要与子进程进行交互的情况下使用。
  • 在使用这些函数时,需要注意对命令参数的验证和过滤,以避免安全漏洞。

你可能感兴趣的:(浅聊一下,bash,开发语言,linux)