Chinaunix首页 | 论坛 | 博客
  • 博客访问: 822977
  • 博文数量: 137
  • 博客积分: 3477
  • 博客等级: 中校
  • 技术积分: 1409
  • 用 户 组: 普通用户
  • 注册时间: 2006-11-30 21:53
文章分类

全部博文(137)

文章存档

2024年(8)

2023年(10)

2022年(4)

2021年(10)

2020年(9)

2018年(1)

2017年(1)

2014年(4)

2013年(3)

2012年(12)

2011年(24)

2010年(2)

2009年(8)

2008年(6)

2007年(34)

2006年(1)

分类: C/C++

2023-11-22 16:46:36

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 ,需要调用:

点击(此处)折叠或打开

  1. #/bin/sh
  2. if [ i < 10 ]; then
  3.     ps -ef
  4.     sleep 1
  5. fi
  6. exit 1
我们无法在 popen 函数中得到这个shell 执行的返回值,因为这个 popen 执行的之后已经 fork 了一个子进程,这个 shell 是在子进程中执行的,因此在父进程中,已经无法接收到这个 shell 的返回值了。
但是,由于 popen 是返回的一个文件指针,因此对于文件指针的关闭操作,是可以得到这个被执行的shell 脚本的返回值的,这个关闭操作的函数是 pclose(FILE *fp)。

但是我们需要注意的是,在使用 pclose 函数关闭这个外部调用命令返回的文件指针时,如果我们在程序中屏蔽了子进程的返回信号,即在调用 popen 函数之前,执行了如下语句:

点击(此处)折叠或打开

  1. signal(SIFCHLD,SIG_IGN);

则所有从子进程中返回的信号都会被忽略。因此当我们使用 pclose 函数关闭这个外部调用的文件指针时,是无法接收到这个 shell 的返回值的。

很多时候,我们在编写一些网络服务器程序时,我们都会编写守护进程来实现高并发调用,我们为了提高系统的性能,一般会采用 fork 一个子进程,父进程继续进行接收信息的阻塞,子进程完成业务处理的方式来实现,而采用这种方式来进行系统业务处理的时候,为了提高系统的响应能力,这一般又会带来针对子进程运行状态的监控问题,我们需要监控子进程的运行情况,当子进程运行结束的时候,我们需要感知子进程的结束信号,并进行子进程回收,否则会产生大量的僵尸进程。

对于子进程的监控,我们一般会有两个策略:
1、我们通过父进程来进行监控,父进程通过调用 wait_pid 来等待子进程结束,并进行进程回收。
2、通过屏蔽子进程的返回信号,让操作系统来实现对子进程的监控并回收子进程。

在实际的开发过程中,我们一般不会在父进程中使用 wait_pid 函数来等待子进程的返回,因为这将使得父进程等待子进程的运行返回状态,这会大幅度降低我们的系统并发响应能力。但是如果我们通过 signal(SIGCHLD,SIG_IGN) 来屏蔽子进程的返回信号,我们通过 popen 调用外部命令时,在使用 pclose 函数关闭外部调用返回的文件指针时,又无法接收到外部调用返回的信号,那么我们应该如何避免这种情况的发生呢?
我们可以在 fork 出子进程后,在子进程的 popen 函数运行之前,执行 signal(SIGCHLD,SIG_DFL) 来恢复子进程的信号接收,因为这个操作是在子进程中做的,对父进程没有任何影响。从而达到在子进程中接收到调用的外部命令的返回结果。
示例代码如下:

点击(此处)折叠或打开

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<string.h>
  4. #include<unistd.h>
  5. #include <stdarg.h>
  6. #include <sys/stat.h>
  7. #include <signal.h>

  8. #ifndef ERROR
  9. #define ERROR -1
  10. #endif

  11. #define NOFILE 3


  12. #ifndef __FILE__
  13. #define __FILE__ ((__func__))
  14. #endif


  15. int Write_log(const char *filename,const char *arglist, ...)
  16. {
  17.         FILE *logfp;
  18.         va_list arg;
  19.         int done=0;

  20.         if ( (logfp = fopen(filename,"ab")) == NULL )
  21.                 return ERROR;

  22.         va_start(arg , arglist);
  23.         done = vfprintf(logfp,arglist,arg);
  24.         va_end(arg);

  25.         fputc('\n',logfp);
  26.         fclose(logfp);
  27.         return(done);
  28. }



  29. void init_daemon( void )
  30. {
  31.         pid_t pid;
  32.         int i;
  33.         if ( (pid = fork()) == -1 )
  34.                 exit(1);
  35.         if ( pid > 0 )
  36.                 exit(0);
  37.         setsid();
  38.         if ( (pid = fork()) == -1 )
  39.                 exit(1);
  40.         if ( pid > 0 )
  41.                 exit(0);

  42.         for( i = 0 ; i< NOFILE; ++i )
  43.                 close(i);

  44.         chdir("/tmp");
  45.         umask(0);
  46.         return;
  47. }


  48. int main()
  49. {

  50.         FILE *read_fd;
  51.         char buff[128] = {0};
  52.         int read_count = 0;
  53.         int ret;
  54.         int j = 0;

  55.         pid_t pid;

  56.         init_daemon();

  57.         signal(SIGINT, SIG_IGN);
  58.         signal(SIGPIPE, SIG_IGN);
  59.         signal(SIGQUIT, SIG_IGN);
  60.         signal(SIGCHLD, SIG_IGN);

  61.         while(j < 10 )
  62.         {
  63.                 pid = fork();
  64.                 if ( pid == 0 )
  65.                 {
  66.                         signal(SIGCHLD,SIG_DFL);
  67.                         read_fd = popen("uname -a","r");
  68.                         if(read_fd != NULL)
  69.                         {

  70.                                 read_count = fread(buff,sizeof(char),127,read_fd);
  71.                                 while(read_count != 0)
  72.                                 {

  73.                                         // printf("read : %s",buff);
  74.                                         Write_log("/tmp/aaa.log","%s|%d ... buf = %s",__FILE__,__LINE__,buff);
  75.                                         read_count = fread(buff,sizeof(char),127,read_fd);
  76.                                     while(read_count != 0)
  77.                                 {

  78.                                         // printf("read : %s",buff);
  79.                                         Write_log("/tmp/aaa.log","%s|%d ... buf = %s",__FILE__,__LINE__,buff);
  80.                                         read_count = fread(buff,sizeof(char),127,read_fd);
  81.                                 }
  82.                                 ret = pclose(read_fd);
  83.                                 printf("\n ret = %d =\n",ret);
  84.                                 exit(EXIT_SUCCESS);
  85.                         }
  86.                 }
  87.                 sleep(2);
  88.                 j++;
  89.         }
  90.         exit(EXIT_FAILURE);
  91. }

以上这个代码,是可以在 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 函数进行外部调用的时候,需要特别注意的地方。



阅读(493) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~