16

我有一个程序产生并与 CPU 繁重、不稳定的进程通信,而不是我创建的。如果我的应用程序崩溃或被杀死SIGKILL,我希望子进程也被杀死,这样用户就不必追踪它们并手动杀死它们。

我知道这个话题之前已经讨论过,但我已经尝试了所有描述的方法,但似乎没有一个能够经受住考验。

我知道这一定是可能的,因为终端一直在这样做。如果我在终端中运行某些东西并杀死终端,那么这些东西总是会死掉。

我试过了atexit,双叉和ptysatexit不适用于sigkill; 双叉根本不起作用;而且ptys我发现无法使用python。

今天,我发现了prctl(PR_SET_PDEATHSIG, SIGKILL),这应该是子进程在父进程死亡时命令自己杀死自己的一种方式。我尝试将它与 一起使用popen,但它似乎完全没有效果:

import ctypes, subprocess
libc = ctypes.CDLL('/lib/libc.so.6')
PR_SET_PDEATHSIG = 1; TERM = 15
implant_bomb = lambda: libc.prctl(PR_SET_PDEATHSIG, TERM)
subprocess.Popen(['gnuchess'], preexec_fn=implant_bomb)

在上面,孩子被创建,父母退出。现在您会期望gnuchess收到 aSIGKILL并死掉,但事实并非如此。我仍然可以在使用 100% CPU 的进程管理器中找到它。

谁能告诉我我使用prctl? 是否有问题,或者你知道终端如何设法杀死他们的孩子吗?

4

8 回答 8

13

我知道已经有好几年了,但我找到了一个简单(有点笨拙)的解决方案来解决这个问题。从您的父进程中,将所有调用包装在一个非常简单的 C 程序中,该程序调用 prctl() 然后 exec() 解决了 Linux 上的这个问题。我称之为“是的”:

#include <linux/prctl.h>
#include <signal.h>
#include <unistd.h>

int main(int argc, char **argv) {
     if(argc < 2)
          return 1;
     prctl(PR_SET_PDEATHSIG, SIGHUP, 0, 0, 0);
     return execvp(argv[1], &argv[1]);
}

从 Python(或任何其他语言)生成子进程时,您可以运行“yeshup gnuchess [argments]”。您会发现,当父进程被杀死时,您的所有子进程(应该)都被很好地给予 SIGHUP。

这是有效的,因为 Linux 将尊重对 prctl 的调用(不清除它),即使在调用 execvp 之后(有效地将 yeshup 进程“转换”为 gnuchess 进程或您在其中指定的任何命令),这与 fork() 不同。

于 2012-11-08T00:58:05.797 回答
6

PR_SET_DEATHSIG只能为调用 prctl 的这个进程设置prctl ——不能为任何其他进程设置,包括这个特定进程的子进程。我所指的手册页表达这一点的方式是“这个值在 fork() 上被清除”——fork当然,是产生其他进程的方式(在 Linux 和任何其他 Unix-y 操作系统中)。

如果您无法控制要在子进程中运行的代码(对于您的gnuchess示例而言,基本上就是这种情况),我建议您首先生成一个单独的小型“监视器”进程,其作用是跟踪其所有兄弟姐妹(您的父进程可以在生成它们时让监视器知道这些兄弟姐妹的 pid)并在共同父进程死亡时向它们发送杀手信号(监视器需要轮询,每 N 秒唤醒您选择的某些 N检查父母是否还活着;用于select在循环内等待来自父母的更多信息,超时为 N 秒)。

不是微不足道的,但这样的系统任务通常不是。终端以不同的方式执行此操作(通过进程组的“控制终端”的概念),但当然任何孩子阻止它(双叉,nohup等等)都是微不足道的。

于 2009-12-11T00:16:41.117 回答
3

实际上,我发现您的原始方法对我来说效果很好 - 这是我测试过的确切示例代码:

回声器.py

#!/bin/env python

import time
import sys
i = 0
try:
    while True:
        i += 1
        print i
        time.sleep(1)
except KeyboardInterrupt:
    print "\nechoer caught KeyboardInterrupt"
    exit(0)

父进程.py

#!/bin/env python

import ctypes
import subprocess
import time

libc = ctypes.CDLL('/lib64/libc.so.6')
PR_SET_PDEATHSIG = 1
SIGINT = 2
SIGTERM = 15

def set_death_signal(signal):
    libc.prctl(PR_SET_PDEATHSIG, signal)

def set_death_signal_int():
    set_death_signal(SIGINT)

def set_death_signal_term():
    set_death_signal(SIGTERM)

#subprocess.Popen(['./echoer.py'], preexec_fn=set_death_signal_term)
subprocess.Popen(['./echoer.py'], preexec_fn=set_death_signal_int)
time.sleep(1.5)
print "parentProc exiting..."
于 2013-07-11T18:05:43.020 回答
1

我认为双叉是从控制终端分离的。我不确定你是如何尝试使用它的。

这是一个 hack,但您始终可以调用“ps”并搜索您试图杀死的进程名称。

于 2009-12-11T00:02:36.997 回答
1

我见过非常讨厌的“清理”方式,比如ps xuawww | grep myApp | awk '{ print $1}' | xargs -n1 kill -9

客户端进程,如果弹出,可以捕获 SIG_PIPE 并死亡。有很多方法可以解决这个问题,但这实际上取决于很多因素。如果您在孩子中抛出一些 ping 代码(ping 到父母),您可以确保在死亡时发出 SIG_PIPE。如果它捕获它,它应该捕获它,它将终止。您需要双向通信才能正常工作......或者始终阻止客户端作为通信的发起者。如果您不想修改孩子,请忽略此。

假设您不希望实际的 Python 解释器出现段错误,您可以将每个 PID 添加到序列中,然后在退出时终止。这对于退出甚至未捕获的异常应该是安全的。Python 具有执行退出代码的工具……用于清理。

这里有一些更安全的讨厌:将每个子 PID 附加到一个文件,包括您的主进程(单独的文件)。使用文件锁定。构建一个看门狗守护进程,查看主 pid 的 flock() 状态。如果它没有被锁定,杀死你的孩子 PID 列表中的每个 PID。在启动时运行相同的代码。

更讨厌:如上所述将 PID 写入文件,然后在子 shell 中调用您的应用程序:(./myMaster; ./killMyChildren)

于 2009-12-11T00:10:22.503 回答
1

我想知道PR_SET_PDEATHSIG标志是否被清除,即使你在你之后fork(和之前exec)设置了它,所以从文档看来它不应该被清除。

为了测试该理论,您可以尝试以下操作:使用相同的代码运行用 C 编写的子进程,基本上只是调用prctl(PR_GET_PDEATHSIG, &result)并打印结果。

您可能会尝试的另一件事是:在调用prctl. IE:

>>> implant_bomb = lambda: libc.prctl(PR_SET_PDEATHSIG, TERM, 0, 0, 0)
于 2012-10-04T12:06:11.947 回答
1

需要考虑一些安全限制,因为如果我们在 execv 之后调用 setuid,他的孩子将无法接收信号。此限制的完整列表在这里

祝你好运 !
/穆罕默德

于 2014-06-03T16:11:13.903 回答
0

其他答案提到了prctlPR_SET_DEATHSIG但忽略了可以使用setpriv命令从命令行设置的事实:

setpriv --pdeathsig HUP [command] &
于 2021-10-05T11:08:55.303 回答