Chinaunix首页 | 论坛 | 博客
  • 博客访问: 216619
  • 博文数量: 88
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 555
  • 用 户 组: 普通用户
  • 注册时间: 2013-09-03 13:08
个人简介

失意高调,得意低调

文章分类

全部博文(88)

文章存档

2021年(3)

2020年(2)

2018年(2)

2017年(3)

2016年(6)

2015年(19)

2014年(32)

2013年(21)

我的朋友

分类: C/C++

2015-07-08 11:40:55

一、简介
    因为linux命令的强大,因此编程时经常会需要直接引用命令的结果,或者是通过执行一个命令,通过命令返回值来做判断。
干这些事的就是system和popen接口函数,他们属于libc库,不属于系统调用
    system:执行一个shell命令,获取命令返回值,不关心命令输出
    popen:执行一个shell命令,获取命令的输出,也关心命令返回值

二、原理
    system:fork一个子进程,在子进程调用execl("/bin/sh","-c",cmd,(char*)0)执行命令,然后在父进程中waitpid,等待子进程结束,并获取命令的返回值
    popen:比system多了一个匿名管道,分为读模式和写模式,区别就在与管道的一端是stdin还是stdout。然后在父进程中,管道的另一端文件描述符生成FILE*文件指针。然后对这个指针操作。

三、问题
    命令都有fork这一步,由于fork后,子进程继承父进程所有文件描述符,这里会造成问题。
    举个例子:
        假设进程中已经打开的socket描述符为fd,执行fork后,子进程中也有了同样的描述符fd。
        然后使用popen执行命令,在子进程执行命令的的过程中,父进程意外退出,但有一些机制保障并立即又重新启动了。这时就会产生父进程创建socket失败,地址被占用的问题。(这里的前提条件是:父进程重新启动时,子进程的命令还没执行完,也即还没退出,导致文件描述符fd被子进程占用)
    这是fork照成问题的一个例子

四、解决方案
    自己重写system,popen,并在fork出的子进程执行命令之前中,主动关闭除0、1、2之外的所有描述符
    system的代码:

点击(此处)折叠或打开

  1. int my_system(const char* pCommand)
  2. {
  3.     pid_t pid;
  4.     int status;
  5.     int i = 0;

  6.     if(pCommand == NULL)
  7.     {
  8.         return (1);
  9.     }

  10.     if((pid = fork())<0)
  11.     {
  12.         status = -1;
  13.     }
  14.     else if(pid == 0)
  15.     {
  16.         /* close all descriptors in child sysconf(_SC_OPEN_MAX) */
  17.         for (i = 3; i < sysconf(_SC_OPEN_MAX); i++)
  18.         {
  19.             close(i);
  20.         }
  21.         
  22.         execl("/bin/sh", "sh", "-c", pCommand, (char *)0);
  23.         _exit(127);
  24.     }
  25.     else
  26.     {
  27.         /*当进程中SIGCHLD做SIG_IGN操作时,获取不到status返回值,XOS_System无法正常工作*/
  28.         while(waitpid(pid, &status, 0) < 0)
  29.         {
  30.             if(errno != EINTR)
  31.             {
  32.                 status = -1;
  33.                 break;
  34.             }
  35.         }
  36.     }

  37.     return status;
  38. }

    popen的代码:

点击(此处)折叠或打开

  1. FILE* my_popen(const char *pCommand, const char *pMode)
  2. {
  3.     int i;
  4.     int parent_end = 0;
  5.     int child_end = 0;
  6.     int child_std_end = 0;
  7.     int pipe_fds[2] = {0};
  8.     pid_t child_pid = 0;
  9.     
  10.     if(NULL == pCommand || NULL == pMode || pid == NULL)
  11.     {
  12.         return (NULL);
  13.     }

  14.     if (pipe(pipe_fds) < 0)
  15.     {
  16.         return(NULL);
  17.     }

  18.     /* only allow "r" or "w" */
  19.     if (pMode[0] == 'r' && pMode[1] == '\0')
  20.     {
  21.         parent_end = pipe_fds[0];
  22.         child_end = pipe_fds[1];
  23.         child_std_end = STDOUT_FILENO;
  24.     }
  25.     else if (pMode[0] == 'w' && pMode[1] == '\0')
  26.     {
  27.         parent_end = pipe_fds[1];
  28.         child_end = pipe_fds[0];
  29.         child_std_end = STDIN_FILENO;
  30.     }
  31.     else
  32.     {
  33.         return(NULL);
  34.     }
  35.     
  36.     child_pid = fork();
  37.     if (child_pid < 0)
  38.     {
  39.         close(child_end);
  40.         close (parent_end);
  41.         return NULL;
  42.     }
  43.     else if (child_pid == 0) /* child */
  44.     {
  45.         close (parent_end);
  46.         if (child_end != child_std_end)
  47.         {
  48.             dup2 (child_end, child_std_end);
  49.             close (child_end);
  50.         }

  51.         /* close all descriptors in child sysconf(_SC_OPEN_MAX) */
  52.         for (i = 3; i < sysconf(_SC_OPEN_MAX); i++)
  53.         {
  54.             close(i);
  55.         }
  56.   
  57.         execl("/bin/sh", "sh", "-c", pCommand, (char *) 0);
  58.         
  59.         _exit(127);
  60.     }
  61.     close(child_end);

  62.     return fdopen(parent_end, pMode);
  63. }

  64. int ExeCmdByPopen(char *pCmd, char *pBuf, unsighed int len)
  65. {
  66.     int count = 2000; /*最多读取2000行*/
  67.     FILE *fp = NULL;
  68.     char szRead[1024] = {0};
  69.     char *pTmp = NULL;
  70.     unsighed int nLen = 0;
  71.     int status = 0;
  72.     pid_t pid;


  73.     if(NULL == pCmd || 0 == len)
  74.     {
  75.         return XERROR;
  76.     }
  77.    
  78.     fp = my_popen(pCmd, "r");//通过一个脚本中转,在脚本中关闭所有的文件描述符,exec fd<&-
  79.     if(NULL == fp)
  80.     {
  81.         perror("XOS_ExeCmdByPopen:popen() failed");
  82.         return XERROR;
  83.     }

  84.     sleep(1);
  85.     
  86.     if(NULL != pBuf)
  87.     {
  88.         pBuf[0] = '\0';
  89.         pTmp = pBuf;
  90.         while(count > 0 && NULL != fgets(pTmp, len, fp))
  91.         {
  92.             nLen = strlen(pTmp);
  93.             if(0 == nLen)/*缓存区不够*/
  94.             {
  95.                 break;
  96.             }
  97.             pTmp += nLen;
  98.             len -= nLen;
  99.             count--;
  100.         }
  101.     }
  102.     else
  103.     {
  104.         /*防止脚本在执行的时候可能需要进行写操作,但这边父进程函数退出了,导致管道破坏*/
  105.         szRead[0] = '\0';
  106.         while(count > 0 && NULL != fgets(szRead, sizeof(szRead)-1, fp))
  107.         {
  108.             count--;
  109.         }
  110.     }
  111.     
  112.     return pclose(fp);
  113. }

五、新的问题
    使用自己写的popen,发现每次调用都产生一个僵尸进程。
    僵尸进程:父进程对子进程的退出没做处理,就照成了僵尸进程。只要在父进程中wait或waitpid均可避免僵尸进程,或通过对SIGCHLD信号设置为忽略,但这样会让system失效。当出现后,可以通过杀死父进程的方式消除。

    那么问题来了,为什么会产生僵尸进程呢?
    经分析pclose和popen的源代码,发现pclose中会调用waitpid,传入的pid是通过在一个链表上比较pf,找到对应的节点,节点中有在popen中存入的子进程pid。问题找到了!
    我们的popen正是少了这一步,没有保存子进程pid,那么调用pclose时,找不到pid,因此无法waitpid子进程。
六、改进
    在popen中传出pid,并用waitpid和close(fileno(pf))这两句代替pclose

点击(此处)折叠或打开

  1. FILE* my_popen(const char *pCommand, const char *pMode, pid_t *pid)
  2. {
  3.     int i;
  4.     int parent_end = 0;
  5.     int child_end = 0;
  6.     int child_std_end = 0;
  7.     int pipe_fds[2] = {0};
  8.     pid_t child_pid = 0;
  9.     
  10.     if(NULL == pCommand || NULL == pMode || pid == NULL)
  11.     {
  12.         return (NULL);
  13.     }

  14.     *pid = 0;

  15.     if (pipe(pipe_fds) < 0)
  16.     {
  17.         return(NULL);
  18.     }

  19.     /* only allow "r" or "w" */
  20.     if (pMode[0] == 'r' && pMode[1] == '\0')
  21.     {
  22.         parent_end = pipe_fds[0];
  23.         child_end = pipe_fds[1];
  24.         child_std_end = STDOUT_FILENO;
  25.     }
  26.     else if (pMode[0] == 'w' && pMode[1] == '\0')
  27.     {
  28.         parent_end = pipe_fds[1];
  29.         child_end = pipe_fds[0];
  30.         child_std_end = STDIN_FILENO;
  31.     }
  32.     else
  33.     {
  34.         return(NULL);
  35.     }
  36.     
  37.     child_pid = fork();
  38.     if (child_pid < 0)
  39.     {
  40.         close(child_end);
  41.         close (parent_end);
  42.         return NULL;
  43.     }
  44.     else if (child_pid == 0) /* child */
  45.     {
  46.         close (parent_end);
  47.         if (child_end != child_std_end)
  48.         {
  49.             dup2 (child_end, child_std_end);
  50.             close (child_end);
  51.         }

  52.         /* close all descriptors in child sysconf(_SC_OPEN_MAX) */
  53.         for (i = 3; i < sysconf(_SC_OPEN_MAX); i++)
  54.         {
  55.             close(i);
  56.         }
  57.   
  58.         execl("/bin/sh", "sh", "-c", pCommand, (char *) 0);
  59.         
  60.         _exit(127);
  61.     }
  62.     close(child_end);
  63.     *pid = child_pid;

  64.     return fdopen(parent_end, pMode);
  65. }

  66. int ExeCmdByPopen(char *pCmd, char *pBuf, unsighed int len)
    {
        int count = 2000; /*最多读取2000行*/
        FILE *fp = NULL;
        char szRead[1024] = {0};
        char *pTmp = NULL;
        unsighed int nLen = 0;
        int status = 0;
        pid_t pid;




        if(NULL == pCmd || 0 == len)
        {
            return XERROR;
        }
       
        fp = my_popen(pCmd, "r", &pid);//通过一个脚本中转,在脚本中关闭所有的文件描述符,exec fd<&-        
        if(NULL == fp)
        {
            perror("XOS_ExeCmdByPopen:popen() failed");
            return XERROR;
        }


        sleep(1);
        
        if(NULL != pBuf)
        {
            pBuf[0] = '\0';
            pTmp = pBuf;
            while(count > 0 && NULL != fgets(pTmp, len, fp))
            {
                nLen = strlen(pTmp);
                if(0 == nLen)/*缓存区不够*/
                {
                    break;
                }
                pTmp += nLen;
                len -= nLen;
                count--;
            }
        }
        else
        {   
            /*防止脚本在执行的时候可能需要进行写操作,但这边父进程函数退出了,导致管道破坏*/
            szRead[0] = '\0';
            while(count > 0 && NULL != fgets(szRead, sizeof(szRead)-1, fp))
            {
                count--;
            }
        }



        /* 下面 close和waitpid组合 相当于pclose. 
        -- 为了解决XOS_Popen与pclose搭配会产生僵尸进程的问题 */
        close(fileno(fp));


        while(waitpid(pid, &status, 0) < 0) 
        { 
            if(errno != EINTR) 
            { 
                status = -1; 
                break; 
            } 
        } 


        return status;    
    }



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