有没有办法确定由“ sh -c”生成的进程的“孙子” pid_t?

问题描述

这个问题来自thisthis

为了增强我对生成过程和重定向管道的理解,我在下面编写了类似popen函数popen2(),该函数返回了生成的子进程的pid_t

注意:popen2()的实现通过exec ing sh -c cmd而不是cmd产生子进程,原因是在第二个链接的问题。

底部代码不是很长,但是要切合实际:a.out产生child.outps aux | grep child以便在打印出来之前获得子进程状态的视觉确认它认为是child.out的pid。

the second linked question评论者指出,通过sh -c生成的进程最终可能是 child grandchild 进程,具体取决于{ {1}}是。
我无意间通过观察主机上的情况(在sh解析为sh的情况下进行了验证,运行/bin/bash显示a.out是作为子进程运行的:

child.out

...而在同一主机上的docker容器中-$ g++ --version && gcc -Wall -Wextra -pedantic -Werror ./main.c && ./a.out g++ (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. p2 stdout: user 3004534 0.0 0.0 4028 732 pts/14 S+ 17:51 0:00 ./child.out user 3004535 0.0 0.0 11176 2932 pts/14 S+ 17:51 0:00 sh -c ps aux | grep child user 3004537 0.0 0.0 12780 968 pts/14 S+ 17:51 0:00 grep child p.pid[3004534] 解析为sh-运行/bin/dash表明a.out作为孙子运行过程:

child.out

我的问题是:在Step 63/63 : RUN ./a.out ---> Running in 7a355740577b p2 stdout: root 7 0.0 0.0 2384 760 ? S 00:55 0:00 sh -c ./child.out root 8 0.0 0.0 2384 760 ? S 00:55 0:00 sh -c ps aux | grep child root 9 0.0 0.0 2132 680 ? S 00:55 0:00 ./child.out root 11 0.0 0.0 3080 880 ? S 00:55 0:00 grep child p.pid[7] 代码中,是否有一种方法可以抽象实际的命令是否为获取已执行命令的a.out。子过程”还是孙子过程?

提供一些背景信息:我希望能够杀死pid_t。通过观察,在我的child.out产生子进程和孙进程的环境中,向 child 进程发送popen2()仅杀死了 child 进程,即SIGTERM,但不是我真正想要杀死的孙子流程,即sh -c child.out


代码

child.out
// main.c
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

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

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

typedef int Pipe[2];

/** Encapsulates information about a created child process. */
typedef struct popen2_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
} popen2_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.
 */
popen2_t popen2( const char* cmd ) {
  popen2_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 );

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

        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 main( int argc,char* argv[] ) {
  (void)argc;
  (void)argv;
  popen2_t p = popen2( "./child.out" );
  int status = 0;

  {
    char buf[4096] = { '\0' };
    popen2_t p2 = popen2( "ps aux | grep child" );
    waitpid( p2.pid,&status,0 );

    read( p2.stdout[READ_END],buf,sizeof buf );
    printf( "p2 stdout:\n%s\n",buf );
  }

  printf( "p.pid[%d]\n",p.pid );

  {
    pid_t wpid = waitpid( p.pid,0 );

    return wpid == p.pid && WIFEXITED( status ) ? WEXITSTATUS( status ) : -1;
  }
}

解决方法

这比我的薪水高一点,但是由于没有其他答案,我将根据user414777的评论发布最终的工作,并且似乎可以正常工作。

我的方法不是获取子流程的pid_t,而是将子流程设置为流程组负责人。这样,如果我将信号发送到进程组(killpg()),则会影响信号到达孙进程。反映在下面的setpgid()中。

popen2_t popen2( const char* cmd ) {
  popen2_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() ); // This is the relevant change

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

        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;
}