stackoverflow上的一些类似问题:
- (早期提问)当父进程退出时如何使子进程退出?
- (稍后提问)使用fork()创建的子进程是否在父进程被杀死时自动退出?
stackoverflow上一些关于Windows系统的类似问题:
一些帖子中已经提到了管道和kqueue
。实际上,您还可以通过调用socketpair()
创建一对连接的Unix域套接字。套接字类型应为SOCK_STREAM
。
假设您有两个套接字文件描述符fd1、fd2。现在fork()
来创建子进程,子进程将继承这些fd。在父进程中,您关闭fd2,在子进程中您关闭fd1。现在每个进程都可以poll()
自己端口上剩余的打开fd以获取POLLIN
事件。只要每一方在正常生命周期内没有显式close()
它的fd,您就可以相当肯定地认为POLLHUP
标志应该指示另一方的终止(无论是干净还是不干净)。当收到此事件通知时,子进程可以决定如何处理(例如死亡)。
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <poll.h>
#include <stdio.h>
int main(int argc, char ** argv)
{
int sv[2]; /* sv[0] for parent, sv[1] for child */
socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
pid_t pid = fork();
if ( pid > 0 ) { /* parent */
close(sv[1]);
fprintf(stderr, "parent: pid = %d\n", getpid());
sleep(100);
exit(0);
} else { /* child */
close(sv[0]);
fprintf(stderr, "child: pid = %d\n", getpid());
struct pollfd mon;
mon.fd = sv[1];
mon.events = POLLIN;
poll(&mon, 1, -1);
if ( mon.revents & POLLHUP )
fprintf(stderr, "child: parent hung up\n");
exit(0);
}
}
./a.out &
。您大约有100秒钟的时间来尝试使用各种信号杀死父进程ID,否则它将只是退出。无论哪种情况,您都应该看到消息“child: parent hung up”。SIGPIPE
处理程序的方法相比,此方法不需要尝试write()
调用。unix(7)
,FreeBSD的unix(4)
,poll(2)
,socketpair(2)
,socket(7)
。安装一个陷阱处理程序来捕获SIGINT信号,如果子进程仍然存活,则杀死它,尽管其他帖子正确指出它无法捕获SIGKILL。
以独占方式打开.lock文件,并使子进程轮询尝试打开它 - 如果打开成功,则子进程应该退出。
正如其他人所指出的那样,依赖父进程 ID 在父进程退出时变为 1 是不可移植的。与其等待特定的父进程进程 ID,不如等待 ID 发生变化:
pit_t pid = getpid();
switch (fork())
{
case -1:
{
abort(); /* or whatever... */
}
default:
{
/* parent */
exit(0);
}
case 0:
{
/* child */
/* ... */
}
}
/* Wait for parent to exit */
while (getppid() != pid)
;
如果你不想以全速轮询,可以根据需要添加微睡眠。
这个选项对我来说似乎比使用管道或依赖信号更简单。
getpid()
是在父进程调用 fork()
之前完成的。如果父进程在此之前死亡,则子进程不存在。可能发生的情况是子进程在一段时间内超过父进程。 - Alexis WilkeSIGKILL
信号杀死。CAP_SYS_ADMIN
权限。但是,这种方法非常有效,并且除了父进程的初始启动之外,不需要对父进程或子进程进行任何实际更改。我认为一种快速而简单的方法是在子进程和父进程之间创建一个管道。当父进程退出时,子进程将接收到一个SIGPIPE信号。
从UNIX v7开始,进程系统通过检查进程的父ID来检测进程孤立状态。历史上,init(8)
系统进程是一个特殊的进程,原因只有一个:它不能死亡。这是因为内核算法处理分配新的父进程ID时依赖于此事实。当进程执行其exit(2)
调用(通过进程系统调用或外部任务发送信号等方式)时,内核将所有子进程的ID重新分配为init进程的ID作为它们的父进程ID。这导致了最简单的测试和最可移植的方法,以知道进程是否已经孤立。只需检查getppid(2)
系统调用的结果,如果它是init(2)
进程的进程ID,则该进程在系统调用之前变成了孤立状态。
这种方法出现了两个问题,可能会导致问题:
首先,我们有可能将init
进程更改为任何用户进程,那么如何确保init
进程始终是所有孤立进程的父进程呢?在exit
系统调用代码中,有一个明确的检查来查看执行该调用的进程是否为init
进程(pid等于1的进程),如果是这种情况,内核会发生崩溃(它不再能够维护进程层次结构),因此不允许init
进程进行exit(2)
调用。init
进程的ID为1
,但这并不是POSIX方法所保证的,POSIX方法规定(如其他响应中所述)只有系统进程ID才保留了这个目的。几乎没有任何POSIX实现这样做,您可以假设在原始的Unix派生系统中,使用getppid(2)
系统调用的结果为1
就足以认为该进程是孤立的。另一种检查方法是在fork之后进行getppid(2)
调用,并将该值与新调用的结果进行比较。这在所有情况下都不起作用,因为这两个调用不是原子的,而且父进程可能会在fork(2)
之后和第一个getppid(2)
系统调用之前死亡。当其父进程进行exit(2)
调用时,进程的父ID只会更改一次,因此这应该足以检查getppid(2)
结果是否在调用之间更改以查看父进程是否已退出。对于init进程的实际子进程,此测试无效,因为它们始终是init(8)
的子进程,但您可以安全地假设这些进程也没有父进程(除非您在系统中替换了init进程)。如果有其他人也遇到类似的问题,当我从C++中的分叉子进程中生成JVM实例时,唯一能够让JVM实例在父进程完成后正确终止的方法是执行以下步骤。希望如果这不是最佳方法,有人可以在评论中提供反馈。
1)在通过execv
启动Java应用程序之前,在分叉的子进程上调用prctl(PR_SET_PDEATHSIG,SIGHUP)
,并且
2)向Java应用程序添加一个关闭挂钩,该挂钩轮询直到其父PID等于1,然后进行硬Runtime.getRuntime().halt(0)
。轮询是通过启动运行ps
命令的单独shell来完成的(参见:如何在Linux上使用Java或JRuby查找我的PID?)。
编辑130118:
看起来那不是一个稳健的解决方案。我仍然有点难以理解正在发生的细微差别,但是当在screen / SSH会话中运行这些应用程序时,有时仍会出现孤立的JVM进程。
我在Java应用程序中不再轮询PPID,而是让关闭挂钩执行清理,然后硬停止如上所述。然后,当需要终止所有内容时,我确保在C++父应用程序中调用waitpid
来处理生成的子进程。这似乎是一种更强大的解决方案,因为子进程确保它终止,而父进程使用现有引用确保其子进程终止。与先前的解决方案相比,该解决方案使父进程在适当时终止,并使子进程在终止之前尝试确定它们是否已被孤立。
在POSIX下,exit()
、_exit()
和_Exit()
函数被定义为:
因此,如果您安排父进程成为其进程组的控制进程,则当父进程退出时,子进程应该会收到SIGHUP信号。我不确定当父进程崩溃时是否会发生这种情况,但我认为会。对于非崩溃情况,它应该可以正常工作。
请注意,您可能需要阅读相当多的细节说明-包括基本定义(定义)部分以及exit()
和setsid()
和setpgrp()
的系统服务信息-才能获得完整的图片。(我也是!)
kill(0, 2); /* SIGINT */
该信号被发送到整个进程组,从而有效地终止了子进程。
您可以使用类似以下的内容轻松测试它:
(cat && kill 0) | python
"Terminated"
,这表明Python解释器确实已被终止,而不仅仅是因为stdin被关闭而退出。(echo -e "print(2+2)\n" && kill 0) | sh -c "python -"
对应的4
。而在 Ubuntu 的 WSL 中,无论是单个&
还是&&
都没有显示出来。 - Kamil Szot我通过滥用终端控制和会话,成功实现了一个便携式、非轮询的解决方案,其中包括3个进程。
技巧如下:
这样做的好处是:
缺点: