假设我有一个进程,它正好产生一个子进程。现在,当父进程出于任何原因(正常或异常,通过KILL、^C、Assert Failure或任何其他原因)退出时,我希望子进程死亡。如何正确地做到这一点呢?
有关堆栈溢出的一些类似问题:
- (前面询问)How can I cause a child process to exit when the parent does?
- (稍后询问)Are child processes created with fork() automatically killed when the parent is killed?
关于Windows堆栈溢出的一些类似问题:
23条答案
按热度按时间ruoxqz4g1#
一些海报已经提到了管道和
kqueue
。事实上,您还可以通过socketpair()
调用创建一对连接的Unix域套接字。插座类型应为SOCK_STREAM
。假设您有两个套接字文件描述符fd1、fd2。现在
fork()
创建子进程,它将继承FDS。在父进程中关闭fd2,在子进程中关闭fd1。现在,对于POLLIN
事件,每个进程可以在其自己的一端poll()
剩余的开放fd。只要每一端在正常生命周期内没有显式地close()
其fd,您就可以非常确定POLLHUP
标志应该指示另一端的终止(无论是否干净)。在被告知这一事件后,孩子可以决定做什么(例如死亡)。您可以尝试编译上面的概念验证代码,并在
./a.out &
这样的终端上运行它。您有大约100秒的时间来尝试通过各种信号终止父PID,否则它将直接退出。在这两种情况下,您都应该看到消息“CHILD:PARENT HOUND”。与使用
SIGPIPE
处理程序的方法相比,该方法不需要尝试write()
调用。这种方法也是对称的,即进程可以使用相同的通道来监视彼此的存在。
此解决方案仅调用POSIX函数。我在Linux和FreeBSD上尝试了这一点。我认为它应该可以在其他制服上工作,但我还没有真正测试过。
另见:
unix(7)
、适用于FreeBSD的unix(4)
、Linux上的poll(2)
、socketpair(2)
、socket(7)
。6rvt4ljy2#
通过滥用终端控制和会话,我设法用3个进程实现了一个可移植的、非轮询的解决方案。
诀窍是:
这样的话:
缺点:
wsxa1bj13#
我将使用环境的父Pid传递给子进程,然后定期检查子进程是否存在/proc/$PPID。
vsikbqxv4#
从历史上看,从Unix v7开始,进程系统通过检查进程的父ID来检测进程的孤立性。正如我所说的,从历史上看,
init(8)
系统进程是一个特殊的进程,原因只有一个:它不会消亡。它不会死,因为处理分配新的父进程ID的核心算法依赖于这一事实。当进程执行其exit(2)
调用时(通过进程系统调用或通过向其发送信号等的外部任务),内核将该进程的所有子进程重新分配初始进程的ID作为其父进程ID。这导致了最简单、最可移植的知道进程是否已成为孤立进程的方法。只需检查getppid(2)
系统调用的结果,如果它是init(2)
进程的进程ID,则该进程在系统调用之前成为孤立进程。这种方法会产生两个可能导致问题的问题:
init
进程更改为任何用户进程,那么如何确保init进程始终是所有孤立进程的父进程?在exit
系统调用代码中,有一个显式检查来查看执行调用的进程是否是init进程(id等于1的进程),如果是这样的话,内核死机(它应该不再能够维护进程层次结构),因此不允许init进程执行exit(2)
调用。1
,但POSIX方法并不保证这一点,它声明(在其他响应中暴露的)只有系统的进程id被保留用于该目的。几乎没有POSIX实现可以做到这一点,并且您可以假设在原始的Unix派生系统中,拥有1
作为getppid(2)
系统调用的响应就足以假定该进程是孤立的。另一种检查方法是在分叉之后创建一个getppid(2)
,并将该值与新调用的结果进行比较。这并不是在所有情况下都有效,因为两个调用并不是原子的,父进程可能在fork(2)
之后和第一个getppid(2)
系统调用之前死亡。Processparent id only changes once, when its parent does an
exit(2)call, so this should be enough to check if the
getppid(2)result changed between calls to see that parent process has exit. This test is not valid for the actual children of the init process, because they are always children of
init(8)`,,但您可以安全地认为这些进程也没有父进程(除非您在系统中替换init进程)tgabmvqs5#
尽管7年过去了,但我在运行SpringBoot应用程序时遇到了这个问题,该应用程序需要在开发期间启动webpack-dev-server,并在后端进程停止时终止它。
我试着使用
Runtime.getRuntime().addShutdownHook
,但它在Windows 10上可以用,但在Windows 7上不行。我已经将其更改为使用一个专用线程来等待进程退出或等待
InterruptedException
,它似乎在两个Windows版本上都能正常工作。rnmwe5a26#
我找到了两个解决方案,都不是完美的。
1.收到SIGTERM信号时,通过KILL(-PID)杀死所有儿童。
显然,这个解决方案不能处理“KILL-9”,但它确实适用于大多数情况,而且非常简单,因为它不需要记住所有子进程。
同样,如果您在某个地方调用Process.Exit,您可以像上面那样安装‘Exit’处理程序。注:Ctrl+C和突然崩溃已被操作系统自动处理以杀死进程组,因此此处不再赘述。
2.使用chjj/pty.js派生您的进程,并附加控制终端。
当你用KILL-9杀死当前进程时,所有子进程也会被自动杀死(被OS?)。我猜是因为当前进程持有终端的另一边,所以如果当前进程死了,子进程将得到SIGPIPE So Death。
jucafojl7#
如果它与其他任何人相关,当我从C++在派生的子进程中派生JVM示例时,我要让JVM示例在父进程完成后正确终止的唯一方法是执行以下操作。如果这不是最好的方法,希望有人能在评论中提供反馈。
1)在通过
execv
启动Java应用程序之前,按照建议在派生子进程上调用prctl(PR_SET_PDEATHSIG, SIGHUP)
,以及2)向Java应用程序添加一个关闭钩子,该钩子轮询直到其父进程ID等于1,然后执行硬
Runtime.getRuntime().halt(0)
。轮询是通过启动一个单独的外壳来完成的,该外壳运行ps
命令(参见:How do I find my PID in Java or JRuby on Linux?)。编辑130118:
这似乎不是一个可靠的解决方案。我仍然在努力理解发生了什么的细微差别,但在屏幕/SSH会话中运行这些应用程序时,我仍然有时会得到孤立的JVM进程。
我没有在Java应用程序中轮询PPID,而是简单地让Shutdown钩子执行清理,然后如上所述进行硬停止。然后,我确保在该终止一切的时候,在派生的子进程上的C++父应用程序中调用
waitpid
。这似乎是一个更健壮的解决方案,因为子进程确保它终止,而父进程使用现有引用来确保其子进程终止。将这与前一个解决方案进行比较,前一个解决方案让父进程随时终止,并让子进程尝试在终止之前确定它们是否已成为孤儿。9fkzdhlc8#
如果您向PID0发送信号,例如使用
该信号被发送到整个进程组,从而有效地杀死了孩子。
您可以使用如下内容轻松测试它:
如果然后按^D,您将看到文本
"Terminated"
,这表明Python解释器确实已被终止,而不是仅仅因为关闭了stdin而退出。crcmnpdw9#
在POSIX下,
exit()
、_exit()
和_Exit()
函数定义为:因此,如果将父进程安排为其进程组的控制进程,则当父进程退出时,子进程应该会收到SIGHUP信号。我不完全确定当父母崩溃时会发生这种情况,但我认为会发生。当然,对于非崩盘情况,它应该运行得很好。
请注意,您可能需要阅读大量详细的印刷品--包括基本定义(Definitions)部分,以及
exit()
、setsid()
和setpgrp()
的系统服务信息--才能全面了解情况。(我也是!)cgyqldqp10#
另一种特定于Linux的方法是在新的PID命名空间中创建父进程。然后,它在该命名空间中将是PID1,当它退出它时,它的所有子对象都将立即被
SIGKILL
杀死。遗憾的是,为了创建新的PID名称空间,您必须拥有
CAP_SYS_ADMIN
。但是,这种方法是非常有效的,除了父对象的初始启动之外,不需要对父对象或子对象进行真正的更改。请参见clone(2)、pid_namespaces(7)和unshare(2)。
sd2nnvve11#
我认为一个快速而肮脏的方法是在孩子和父母之间建立一条管道。当家长退出时,孩子们将收到SIGPIPE。
lx0bsm1f12#
正如其他人所指出的,依赖父进程ID在父进程退出时变为1是不可移植的。无需等待特定的父进程ID,只需等待ID更改:
如果您不想全速轮询,可以根据需要添加微睡眠。
在我看来,这种选择似乎比使用管道或依靠信号更简单。
vnjpjtjt13#
通过在
prctl()
syscall中指定选项PR_SET_PDEATHSIG
,子进程可以在父进程死亡时请求内核发送SIGHUP
(或其他信号):prctl(PR_SET_PDEATHSIG, SIGHUP);
详情见
man 2 prctl
。编辑:这是仅用于Linux的
avwztpqn14#
安装一个陷阱处理程序来捕获SIGINT,如果它还活着,它会杀死你的子进程,尽管其他帖子说它不会捕获SIGKILL是正确的。
打开具有独占访问权限的.lockfile文件,并让子轮询尝试打开它-如果打开成功,则子进程应该退出
vbopmzt115#
这个解决方案对我很管用:
这是对于工作者类型的进程,其存在只有在父进程活着的情况下才有意义。