有没有办法确定“孙子"身份?用"sh -c"产生的进程的pid_t? [英] Is there a way to determine the "grandchild" pid_t of a process spawned with "sh -c"?
问题描述
为了增强对生成过程和重定向管道的理解,我在下面编写了类似 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 exec
ing 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屋!