与父母一起杀害孩子

时间:2009-12-10 23:46:56

标签: python subprocess sigkill

我有一个程序产生并与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收到SIGKILL并死亡,但事实并非如此。我仍然可以在我的流程管理器中使用100%CPU找到它。

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

7 个答案:

答案 0 :(得分:13)

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

#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。

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

答案 1 :(得分:6)

prctlPR_SET_DEATHSIG只能为这个正在调用prctl 的进程设置 - 不适用于任何其他进程,包括此特定进程的子进程。我指向的手册页表达的方式是“这个值在fork()时被清除” - fork当然是生成其他进程的方式(在Linux和任何其他Unix中 - y OS)。

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

不是微不足道的,但是这样的系统任务往往不是。终端以不同的方式做到这一点(通过进程组的“控制终端”的概念)但当然,任何一个孩子都可以阻止它(双叉,nohup等等)。

答案 2 :(得分:4)

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

<强> echoer.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)

<强> parentProc.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..."

答案 3 :(得分:1)

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

这是一个黑客,但你可以随时调用'ps'并搜索你想要杀死的进程名称。

答案 4 :(得分: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)

答案 5 :(得分:1)

我想知道PR_SET_PDEATHSIG标志是否已被清除,即使您在fork之后(以及exec之前设置),所以它似乎来自像这样的文档< em>不应该被清除。

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

您可以尝试的另一件事:在调用prctl时为arg3,arg4和arg5添加显式零。即:

>>> implant_bomb = lambda: libc.prctl(PR_SET_PDEATHSIG, TERM, 0, 0, 0)

答案 6 :(得分:1)

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

祝你好运!
/穆罕默德