问题描述
为了增强我对生成过程和重定向管道的理解,我在下面编写了类似popen
的函数popen2()
,该函数返回了生成的子进程的pid_t
。
注意:popen2()
的实现通过exec
ing sh -c cmd
而不是cmd
产生子进程,原因是在第二个链接的问题。
底部的代码不是很长,但是要切合实际:a.out
产生child.out
和ps 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;
}