我有一个程序产生并与CPU重,不稳定的进程通信,而不是由我创建的。如果我的应用程序崩溃或被SIGKILL
杀死,我希望子进程也被杀死,因此用户无需跟踪它们并手动终止它们。
我知道之前已经讨论过这个话题,但是我已经尝试了所有描述的方法,但似乎没有一个方法能够在测试中幸存下来。
我知道这一定是可能的,因为终端一直都在做。如果我在终端中运行某些东西并杀死终端,这些东西总会死掉。
我尝试了atexit
,双叉和ptys
。 atexit
不适用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
是否有问题吗?
或者你知道终端如何设法杀死他们的孩子吗?
答案 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)
prctl的PR_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
祝你好运!