浅聊一下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被信号中断,则返回-1break;}}}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()
函数适合简单的命令执行,不需要与子进程进行交互的情况下使用。- 在使用这些函数时,需要注意对命令参数的验证和过滤,以避免安全漏洞。
本文链接:https://my.lmcjl.com/post/13672.html
4 评论