为什么除非在popen之后调用被延迟,否则pclose实现为什么会在ECHILD早期返回?

问题描述

我最近想弄清楚如何派生/执行子进程并重定向其stdin,stdout和stderr,以此编写自己的popen()pclose()受到open-source implementation of popen() and pclose()启发的类似函数my_popen()my_pclose()

通过人工检查-例如在另一个终端中运行ps来查找期望的子进程-popen()似乎可以正常工作,因为期望的子进程出现了。

问题:如果我在my_pclose()之后立即调用errno == 10 (ECHILD),为什么my_popen()立即返回?我的期望是my_pclose()会等到子进程结束。

问题:鉴于上述情况,为什么my_pclose()会按预期方式返回-在子进程正常结束之后-如果我在my_popen()和{{之间插入延迟, 1}}?

问题my_pclose()需要进行哪些更正才能仅在子进程结束后才可靠地返回,而无需任何延迟或其他麻烦?>


下面的MCVE。

某些情况:我希望my_pclose()允许用户1)写入子进程“ my_popen(),2)读取子进程” stdin,3)读取子进程进程'stdout,4)知道子进程'stderr,5)在fork / exec'ed进程可能是子进程或子进程的环境中运行,并且能够杀死其中的子进程后者的情况(因此pid_t)。

setpgid()

执行失败的失败:

// main.c

#include <errno.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

typedef int Pipe[2];

typedef enum PipeEnd {
  READ_END  = 0,WRITE_END = 1
} PipeEnd;

#define INVALID_FD (-1)
#define INVALID_PID (0)

typedef struct my_popen_t {
  bool  success;  ///< true if the child process was spawned.
  Pipe  stdin;    ///< parent -> stdin[WRITE_END] -> child's stdin
  Pipe  stdout;   ///< child -> stdout[WRITE_END] -> parent reads stdout[READ_END]
  Pipe  stderr;   ///< child -> stderr[WRITE_END] -> parent reads stderr[READ_END]
  pid_t pid;      ///< child process' pid
} my_popen_t;

/** dup2( p[pe] ) then close and invalidate both ends of p */
static void dupFd( Pipe p,const PipeEnd pe,const int fd ) {
  dup2( p[pe],fd);
  close( p[READ_END] );
  close( p[WRITE_END] );
  p[READ_END] = INVALID_FD;
  p[WRITE_END] = INVALID_FD;
}

/**
 * Redirect a parent-accessible pipe to the child's stdin,and redirect the
 * child's stdout and stderr to parent-accesible pipes.
 */
my_popen_t my_popen( const char* cmd ) {
  my_popen_t r = { false,{ INVALID_FD,INVALID_FD },INVALID_PID };

  if ( -1 == pipe( r.stdin ) ) { goto end; }
  if ( -1 == pipe( r.stdout ) ) { goto end; }
  if ( -1 == pipe( r.stderr ) ) { goto end; }

  switch ( (r.pid = fork()) ) {
    case -1: // Error
      goto end;

    case 0: // Child process
      dupFd( r.stdin,READ_END,STDIN_FILENO );
      dupFd( r.stdout,WRITE_END,STDOUT_FILENO );
      dupFd( r.stderr,STDERR_FILENO );
      setpgid( getpid(),getpid() );

      {
        char* argv[] = { (char*)"sh",(char*)"-c",(char*)cmd,NULL };

        // @todo Research why - as has been pointed out - _exit() should be
        // used here,not exit().
        if ( -1 == execvp( argv[0],argv ) ) { exit(0); }
      }
  }

  // Parent process
  close( r.stdin[READ_END] );
  r.stdin[READ_END] = INVALID_FD;
  close( r.stdout[WRITE_END] );
  r.stdout[WRITE_END] = INVALID_FD;
  close( r.stderr[WRITE_END] );
  r.stderr[WRITE_END] = INVALID_FD;
  r.success = true;

end:
  if ( ! r.success ) {
    if ( INVALID_FD != r.stdin[READ_END] ) { close( r.stdin[READ_END] ); }
    if ( INVALID_FD != r.stdin[WRITE_END] ) { close( r.stdin[WRITE_END] ); }
    if ( INVALID_FD != r.stdout[READ_END] ) { close( r.stdout[READ_END] ); }
    if ( INVALID_FD != r.stdout[WRITE_END] ) { close( r.stdout[WRITE_END] ); }
    if ( INVALID_FD != r.stderr[READ_END] ) { close( r.stderr[READ_END] ); }
    if ( INVALID_FD != r.stderr[WRITE_END] ) { close( r.stderr[WRITE_END] ); }

    r.stdin[READ_END] = r.stdin[WRITE_END] =
      r.stdout[READ_END] = r.stdout[WRITE_END] =
      r.stderr[READ_END] = r.stderr[WRITE_END] = INVALID_FD;
  }

  return r;
}

int my_pclose( my_popen_t* p ) {
  if ( ! p )                    { return -1; }
  if ( ! p->success )           { return -1; }
  if ( INVALID_PID == p->pid )  { return -1; }

  {
    pid_t pid = INVALID_PID;
    int wstatus;

    do {
      pid = waitpid( -1 * (p->pid),&wstatus,0 );
    } while ( -1 == pid && EINTR == errno );

    return ( -1 == pid ? pid : wstatus );
  }
}

int main( int argc,char* argv[] ) {
  my_popen_t p = my_popen( "sleep 3" );
  //sleep( 1 ); // Uncomment this line for my_pclose() success.
  int res = my_pclose( &p );

  printf( "res: %d,errno: %d (%s)\n",res,errno,strerror( errno ) );

  return 0;
}

参考:123


更新
This link让我想知道$ gcc --version && gcc -g ./main.c && ./a.out gcc (Debian 6.3.0-18+deb9u1) 6.3.0 20170516 Copyright (C) 2016 Free Software Foundation,Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. res: -1,errno: 10 (No child processes) 之后在父进程中添加setpgid( pid,0 )是否相关。这样做似乎是可行的,因为添加后,在fork()之后立即调用my_pclose()似乎要等到该过程完成。

老实说,我不太明白为什么会有所不同。如果一个知识渊博的社区成员能够提供见解,我将不胜感激。

my_popen()

解决方法

my_pclose()的问题在于您试图执行进程组等待而不是等待特定的子进程。这个:

      pid = waitpid( -1 * (p->pid),&wstatus,0 );

试图等待属于进程组p->pid的孩子,但是如果没有您以后添加的setpgid()调用,这是极不可能的。分叉的子代最初将与其父代位于同一个进程组中,并且该组的进程组号几乎可以肯定与该子进程的进程号不同。

此外,目前尚不清楚为什么首先要等待流程组。您知道要等待的特定进程,而my_pclose()收集另一个进程是不正确的,无论它是否属于同一进程组。您应该等待该特定过程:

      pid = waitpid(p->pid,0 );

无论有没有setpgid()调用都可以使用,但是几乎可以肯定,您应该在这样的通用函数中忽略该调用。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...