有没有办法确定“孙子"身份?用"sh -c"产生的进程的pid_t? [英] Is there a way to determine the "grandchild" pid_t of a process spawned with "sh -c"?

查看:73
本文介绍了有没有办法确定“孙子"身份?用"sh -c"产生的进程的pid_t?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此问题来自 this .

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

To bolster my understanding of spawning processes and redirecting pipes, I've written popen-like function popen2() -- below -- that returns the pid_t of the spawned child process.

注意: popen2()的实现通过 exec ing sh -c cmd 而不是 cmd ,因为在第二个链接的问题上支持这种方法的解释.

Note: the implementation of popen2() spawns the child process by execing sh -c cmd instead of just cmd because of the explanations in favor of this approach at the second linked question.

底部的代码不是很长,而是切合实际: a.out 生成 child.out 以及 ps aux |grep child 可以在打印出它认为是 child.out 的pid之前获得视觉确认子进程的统计信息.

The code at bottom is not terribly long, but to cut to the chase: a.out spawns child.out as well as ps aux | grep child to get visual confirmation of child processes' stats before printing out what it thinks is child.out's pid.

位于上的评论者链接的问题指出,通过 sh -c 生成的进程最终可能是 child grandchild 进程,具体取决于 sh 是.
我无意间通过观察主机上的内容(在 sh 解析为/bin/bash 的主机上)进行了验证-运行 a.out 显示 child.out 作为子进程运行:

A commenter at the second linked question pointed out that processes spawned via sh -c may end up being either child or grandchild processes, depending on what sh is.
I unintentionally verified this by observing that on my host -- where sh resolves to /bin/bash -- running a.out shows that child.out is run as a child process:

$ 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]

...而在同一主机上的docker容器中-其中 sh 解析为/bin/dash -运行 a.out 显示 child.out 作为孙子进程运行:

...whereas in a docker container on the same host -- where sh resolves to /bin/dash -- running a.out shows that child.out is run as a grandchild process:

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 进程"还是 grandchild 进程?

My question is: in a.out's code, is there a way to get the pid_t of the executed command in a way that abstracts whether the actual command is a child process' or a grandchild process?

提供一些背景信息:我希望能够杀死 child.out .通过观察,在我的 popen2()产生子进程和孙进程的环境中,向 child 进程发送 SIGTERM 只会杀死child 进程,即 sh -c child.out ,而不是 grandchild 进程,即 child.out 真的很想杀人.

To give some context: I want to be able to kill child.out. By observation, in the environment where my popen2() spawns child and grandchild processes, sending the child process a SIGTERM kills only the child process, i.e. sh -c child.out but not the grandchild process, i.e. child.out, which is what I really want to kill.

代码:

// 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_FD, INVALID_FD },
    { 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, WRITE_END, 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, &status, 0 );

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

// child.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main( int argc, char* argv[] ) {
  char buf[128] = { '\0' };

  snprintf( buf, sizeof buf, "%s:%d\n", __FILE__, __LINE__ );
  write( STDOUT_FILENO, buf, strlen( buf ) );
  sleep( 1 );

  snprintf( buf, sizeof buf, "%s:%d\n", __FILE__, __LINE__ );
  write( STDOUT_FILENO, buf, strlen( buf ) );
  sleep( 1 );

  snprintf( buf, sizeof buf, "%s:%d\n", __FILE__, __LINE__ );
  write( STDOUT_FILENO, buf, strlen( buf ) );
  sleep( 1 );

  snprintf( buf, sizeof buf, "%s:%d\n", __FILE__, __LINE__ );
  write( STDOUT_FILENO, buf, strlen( buf ) );
  sleep( 1 );
  return 0;
}


推荐答案

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

This is a little above my paygrade, but since there haven't been any other answers, I'll post what I ended up doing, which is based on user414777's comment, and appears to work.

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

My approach was not to get the pid_t of the grandchild process, but to set the child process to be a process group leader. By doing so, if I send a signal to the process group (killpg()), that has the effect of the signal reaching the grandchild process. This is reflected in the addition of setpgid() below.

popen2_t popen2( const char* cmd ) {
  popen2_t r = { false,
    { INVALID_FD, INVALID_FD },
    { INVALID_FD, INVALID_FD },
    { 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, WRITE_END, 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;
}

这篇关于有没有办法确定“孙子"身份?用"sh -c"产生的进程的pid_t?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆