1、关于 popen 函数
popen函数用于执行一个外部调用,和 system 函数一样,该调用会 fork 一个子进程,并在子进程中执行外部命令的调用过程。和 system 函数不一样的是,其执行过程中会返回一个文件指针,将外部命令执行的屏幕输出进行捕获。借助 popen 函数执行外部命令的这个特性,我们可以将其用在特定的外部命令调用场合,实现特定的目标。
2、popen 函数的原型说明
FILE *popen(const char *filename,const char *model)
该函数有两个参数:
const char *filename , 是需要调用的外部命令的名字。一般要求绝对路径。
const char *model , 读写属性,其值只能是 “r" ,或者是 "w" 。
3、popen 函数使用时的注意事项。
诚如前面的分析所言,popen 函数会 fork 一个子进程,并在子进程中完成对于外部命令的调用过程,因此其返回值在 popen 函数中无法被捕获,我们可以通过判断 popen 函数的返回是否为 NULL 来判断 popen 函数本身的调用是否成功,但是没有办法捕获到被调用的外部命令的返回值。如,我们有如下一个 shell ,需要调用:
-
#/bin/sh
-
-
if [ i < 10 ]; then
-
ps -ef
-
sleep 1
-
fi
-
exit 1
我们无法在 popen 函数中得到这个shell 执行的返回值,因为这个 popen 执行的之后已经 fork 了一个子进程,这个 shell 是在子进程中执行的,因此在父进程中,已经无法接收到这个 shell 的返回值了。
但是,由于 popen 是返回的一个文件指针,因此对于文件指针的关闭操作,是可以得到这个被执行的shell 脚本的返回值的,这个关闭操作的函数是 pclose(FILE *fp)。
但是我们需要注意的是,在使用 pclose 函数关闭这个外部调用命令返回的文件指针时,如果我们在程序中屏蔽了子进程的返回信号,即在调用 popen 函数之前,执行了如下语句:
则所有从子进程中返回的信号都会被忽略。因此当我们使用 pclose 函数关闭这个外部调用的文件指针时,是无法接收到这个 shell 的返回值的。
很多时候,我们在编写一些网络服务器程序时,我们都会编写守护进程来实现高并发调用,我们为了提高系统的性能,一般会采用 fork 一个子进程,父进程继续进行接收信息的阻塞,子进程完成业务处理的方式来实现,而采用这种方式来进行系统业务处理的时候,为了提高系统的响应能力,这一般又会带来针对子进程运行状态的监控问题,我们需要监控子进程的运行情况,当子进程运行结束的时候,我们需要感知子进程的结束信号,并进行子进程回收,否则会产生大量的僵尸进程。
对于子进程的监控,我们一般会有两个策略:
1、我们通过父进程来进行监控,父进程通过调用 wait_pid 来等待子进程结束,并进行进程回收。
2、通过屏蔽子进程的返回信号,让操作系统来实现对子进程的监控并回收子进程。
在实际的开发过程中,我们一般不会在父进程中使用 wait_pid 函数来等待子进程的返回,因为这将使得父进程等待子进程的运行返回状态,这会大幅度降低我们的系统并发响应能力。但是如果我们通过 signal(SIGCHLD,SIG_IGN) 来屏蔽子进程的返回信号,我们通过 popen 调用外部命令时,在使用 pclose 函数关闭外部调用返回的文件指针时,又无法接收到外部调用返回的信号,那么我们应该如何避免这种情况的发生呢?
我们可以在 fork 出子进程后,在子进程的 popen 函数运行之前,执行 signal(SIGCHLD,SIG_DFL) 来恢复子进程的信号接收,因为这个操作是在子进程中做的,对父进程没有任何影响。从而达到在子进程中接收到调用的外部命令的返回结果。
示例代码如下:
-
#include<stdio.h>
-
#include<stdlib.h>
-
#include<string.h>
-
#include<unistd.h>
-
#include <stdarg.h>
-
#include <sys/stat.h>
-
#include <signal.h>
-
-
#ifndef ERROR
-
#define ERROR -1
-
#endif
-
-
#define NOFILE 3
-
-
-
#ifndef __FILE__
-
#define __FILE__ ((__func__))
-
#endif
-
-
-
int Write_log(const char *filename,const char *arglist, ...)
-
{
-
FILE *logfp;
-
va_list arg;
-
int done=0;
-
-
if ( (logfp = fopen(filename,"ab")) == NULL )
-
return ERROR;
-
-
va_start(arg , arglist);
-
done = vfprintf(logfp,arglist,arg);
-
va_end(arg);
-
-
fputc('\n',logfp);
-
fclose(logfp);
-
return(done);
-
}
-
-
-
-
void init_daemon( void )
-
{
-
pid_t pid;
-
int i;
-
if ( (pid = fork()) == -1 )
-
exit(1);
-
if ( pid > 0 )
-
exit(0);
-
setsid();
-
if ( (pid = fork()) == -1 )
-
exit(1);
-
if ( pid > 0 )
-
exit(0);
-
-
for( i = 0 ; i< NOFILE; ++i )
-
close(i);
-
-
chdir("/tmp");
-
umask(0);
-
return;
-
}
-
-
-
int main()
-
{
-
-
FILE *read_fd;
-
char buff[128] = {0};
-
int read_count = 0;
-
int ret;
-
int j = 0;
-
-
pid_t pid;
-
-
init_daemon();
-
-
signal(SIGINT, SIG_IGN);
-
signal(SIGPIPE, SIG_IGN);
-
signal(SIGQUIT, SIG_IGN);
-
signal(SIGCHLD, SIG_IGN);
-
-
while(j < 10 )
-
{
-
pid = fork();
-
if ( pid == 0 )
-
{
-
signal(SIGCHLD,SIG_DFL);
-
read_fd = popen("uname -a","r");
-
if(read_fd != NULL)
-
{
-
-
read_count = fread(buff,sizeof(char),127,read_fd);
-
while(read_count != 0)
-
{
-
-
// printf("read : %s",buff);
-
Write_log("/tmp/aaa.log","%s|%d ... buf = %s",__FILE__,__LINE__,buff);
-
read_count = fread(buff,sizeof(char),127,read_fd);
-
while(read_count != 0)
-
{
-
-
// printf("read : %s",buff);
-
Write_log("/tmp/aaa.log","%s|%d ... buf = %s",__FILE__,__LINE__,buff);
-
read_count = fread(buff,sizeof(char),127,read_fd);
-
}
-
ret = pclose(read_fd);
-
printf("\n ret = %d =\n",ret);
-
exit(EXIT_SUCCESS);
-
}
-
}
-
sleep(2);
-
j++;
-
}
-
exit(EXIT_FAILURE);
-
}
以上这个代码,是可以在 pclose 函数运行的时候获得其被调用的外部命令的返回值的。这个 pclose 的返回值,就是外部命令的返回值。
需要注意的是
fork 函数会返回两次,一次是 fork 执行是否成功的状态,另一次是父进程的id号,如果 pid == 0 代表了在子进程中,pid > 0 表示在父进程中,pid < 0 表示 fork 子进程失败。
上述代码在 fork 子进程的 pid == 0 的时候,执行了 signal(SIGCHLD,SIG_DFL) 函数,即表示在子进程中恢复了对于子进程信号的默认处理方式,也就是说在子进程中通过 popen 调用外部命令时,子进程创建的孙子进程,在子进程中是可以通过 pclose 函数的返回值进行获取的。但这个时候,父进程对于子进程的信号处理还是执行的 signal(SIGCHLD,SIG_IGN) ;
以上部分,就是我们在使用 popen 函数进行外部调用的时候,需要特别注意的地方。
阅读(546) | 评论(0) | 转发(0) |