假设我有一个进程,它只生成一个子进程。现在,当父进程由于某种原因(正常或不正常,通过kill, ^C,断言失败或其他任何原因)退出时,我希望子进程死亡。如何正确地做到这一点?
关于stackoverflow的一些类似问题:
(前面问过)当父进程退出的时候,我怎么能让子进程退出呢? (稍后问)用fork()创建的子进程会在父进程被杀死时自动被杀死吗?
关于Windows的stackoverflow也有类似的问题:
如何在Windows中自动销毁子进程? 当父进程被杀死时,杀死子进程
假设我有一个进程,它只生成一个子进程。现在,当父进程由于某种原因(正常或不正常,通过kill, ^C,断言失败或其他任何原因)退出时,我希望子进程死亡。如何正确地做到这一点?
关于stackoverflow的一些类似问题:
(前面问过)当父进程退出的时候,我怎么能让子进程退出呢? (稍后问)用fork()创建的子进程会在父进程被杀死时自动被杀死吗?
关于Windows的stackoverflow也有类似的问题:
如何在Windows中自动销毁子进程? 当父进程被杀死时,杀死子进程
当前回答
通过滥用终端控制和会话,我设法用3个进程实现了一个可移植的、非轮询的解决方案。
诀窍在于:
process A is started process A creates a pipe P (and never reads from it) process A forks into process B process B creates a new session process B allocates a virtual terminal for that new session process B installs SIGCHLD handler to die when the child exits process B sets a SIGPIPE handler process B forks into process C process C does whatever it needs (e.g. exec()s the unmodified binary or runs whatever logic) process B writes to pipe P (and blocks that way) process A wait()s on process B and exits when it dies
这种方式:
如果进程A死亡:进程B得到一个SIGPIPE并死亡 如果进程B死亡:进程A的wait()返回并死亡,进程C将得到一个SIGHUP(因为当一个连接终端的会话的会话领导者死亡时,前台进程组中的所有进程都会得到一个SIGHUP) 如果进程C死亡:进程B得到一个SIGCHLD并死亡,那么进程a也会死亡
缺点:
进程C不能处理SIGHUP 进程C将在不同的会话中运行 进程C不能使用会话/进程组API,因为这会破坏脆弱的设置 为每一个这样的操作创建一个终端并不是最好的主意
其他回答
如果父母去世,孤儿的PPID变为1 -你只需要检查你自己的PPID。 在某种程度上,这就是上面提到的轮询。 这是它的外壳部分:
check_parent () {
parent=`ps -f|awk '$2=='$PID'{print $3 }'`
echo "parent:$parent"
let parent=$parent+0
if [[ $parent -eq 1 ]]; then
echo "parent is dead, exiting"
exit;
fi
}
PID=$$
cnt=0
while [[ 1 = 1 ]]; do
check_parent
... something
done
我在过去通过在“子”中运行“原始”代码和在“父”中运行“衍生”代码来实现这一点(也就是说:你在fork()之后反转了通常意义上的测试)。然后在“衍生”代码中捕获SIGCHLD…
对你来说可能不行,但管用的时候很可爱。
为了完整起见。在macOS上你可以使用kqueue:
void noteProcDeath(
CFFileDescriptorRef fdref,
CFOptionFlags callBackTypes,
void* info)
{
// LOG_DEBUG(@"noteProcDeath... ");
struct kevent kev;
int fd = CFFileDescriptorGetNativeDescriptor(fdref);
kevent(fd, NULL, 0, &kev, 1, NULL);
// take action on death of process here
unsigned int dead_pid = (unsigned int)kev.ident;
CFFileDescriptorInvalidate(fdref);
CFRelease(fdref); // the CFFileDescriptorRef is no longer of any use in this example
int our_pid = getpid();
// when our parent dies we die as well..
LOG_INFO(@"exit! parent process (pid %u) died. no need for us (pid %i) to stick around", dead_pid, our_pid);
exit(EXIT_SUCCESS);
}
void suicide_if_we_become_a_zombie(int parent_pid) {
// int parent_pid = getppid();
// int our_pid = getpid();
// LOG_ERROR(@"suicide_if_we_become_a_zombie(). parent process (pid %u) that we monitor. our pid %i", parent_pid, our_pid);
int fd = kqueue();
struct kevent kev;
EV_SET(&kev, parent_pid, EVFILT_PROC, EV_ADD|EV_ENABLE, NOTE_EXIT, 0, NULL);
kevent(fd, &kev, 1, NULL, 0, NULL);
CFFileDescriptorRef fdref = CFFileDescriptorCreate(kCFAllocatorDefault, fd, true, noteProcDeath, NULL);
CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack);
CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fdref, 0);
CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopDefaultMode);
CFRelease(source);
}
另一种Linux特有的方法是在一个新的PID名称空间中创建父进程。然后它将是该名称空间中的PID 1,当它退出时,它的所有子节点将立即被SIGKILL杀死。
不幸的是,为了创建一个新的PID名称空间,您必须拥有CAP_SYS_ADMIN。但是,这种方法非常有效,在初始启动父进程之后不需要对父进程或子进程进行任何实际更改。
请参见clone(2)、pid_namespaces(7)和unshare(2)。
如果你发送一个信号到pid 0,使用for实例
kill(0, 2); /* SIGINT */
该信号被发送到整个进程组,从而有效地杀死了子进程。
你可以很容易地测试它,比如:
(cat && kill 0) | python
如果你然后按^D,你会看到文本“Terminated”,这表明Python解释器确实已经被杀死,而不是因为stdin被关闭而退出。