与父进程一起终止子进程
我有一个程序,它会启动一些占用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
时是否有什么问题?或者你知道终端是怎么管理并杀掉它们的子进程的吗?
8 个回答
其实我发现你最开始的方法对我来说完全没问题——这是我测试过的具体示例代码,运行得很好:
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..."
我知道这已经过去很多年了,但我找到了一种简单(稍微有点黑科技)的解决办法。你可以在父进程中,使用一个非常简单的C程序来包裹所有的调用,这个程序会调用prctl(),然后再执行exec(),这样就能解决这个问题了。我把它叫做“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 [参数]”。你会发现,当父进程被杀掉时,所有的子进程(应该)会优雅地收到SIGHUP信号。
之所以能这样工作,是因为Linux会尊重对prctl的调用(不会清除它),即使在调用execvp之后(这实际上是把yeshup进程“变成”gnuchess进程,或者你在这里指定的任何命令),这和fork()是不一样的。
prctl的 PR_SET_DEATHSIG
只能为调用 prctl 的这个进程设置,不能为其他进程设置,包括这个进程的子进程。手册上是这样说的:“这个值在调用 fork
时会被清除。” fork
是在 Linux 和其他类 Unix 操作系统中用来创建新进程的方法。
如果你无法控制想要在子进程中运行的代码(就像你的 gnuchess
示例那样),我建议你先创建一个小的“监控”进程,负责跟踪所有兄弟进程(你的父进程可以在创建这些兄弟进程时把它们的进程 ID 告诉监控进程),并在共同的父进程死亡时给它们发送终止信号(监控进程需要定期检查父进程是否还活着,可以每隔 N 秒醒来一次,检查父进程的状态;使用 select
来等待父进程的信息,设置超时时间为 N 秒,并在一个循环中进行)。
这并不简单,但这样的系统任务通常都不简单。终端的处理方式不同(通过“控制终端”的概念来管理进程组),但当然任何子进程都可以轻松地阻止这种方式(比如通过双重 fork、nohup
等方法)。