2010-09-24 36 views
18

我正在使用python来管理一些模拟。我建立的参数和运行程序使用:如何阻止python传播信号到子进程?

pipe = open('/dev/null', 'w') 
pid = subprocess.Popen(shlex.split(command), stdout=pipe, stderr=pipe) 

我的代码处理不同的信号。 Ctrl + C将停止模拟,询问我是否要保存并正常退出。我有其他信号处理程序(例如强制数据输出)。

我想要的是向我的python脚本发送一个信号(SIGINT,Ctrl + C),它会询问用户要发送给程序的信号。

防止代码工作的唯一的事情是,它似乎不管我做什么,按Ctrl + C将“转”给子:代码将它捉住并退出:

try: 
    <wait for available slots> 
except KeyboardInterrupt: 
    print "KeyboardInterrupt catched! All simulations are paused. Please choose the signal to send:" 
    print " 0: SIGCONT (Continue simulation)" 
    print " 1: SIGINT (Exit and save)" 
    [...] 
    answer = raw_input() 
    pid.send_signal(signal.SIGCONT) 
    if (answer == "0"): 
    print " --> Continuing simulation..." 
    elif (answer == "1"): 
    print " --> Exit and save." 
    pid.send_signal(signal.SIGINT) 
    [...] 

所以无论我做什么,该程序正在接收SIGINT,我只想让我的Python脚本看到。我怎样才能做到这一点???

我也试过:

signal.signal(signal.SIGINT, signal.SIG_IGN) 
pid = subprocess.Popen(shlex.split(command), stdout=pipe, stderr=pipe) 
signal.signal(signal.SIGINT, signal.SIG_DFL) 

运行的程序,但是这给了相同的结果:程序捕捉SIGINT。

Thanx!

回答

4

POSIX表示程序运行execvp(这是subprocess.Popen使用的)应该继承调用进程的信号掩码。

我可能是错的,但我不认为调用signal修改掩码。你想要sigprocmask,python不直接公开。

这将是一个黑客攻击,但您可以尝试通过ctypes直接调用libc来设置它。我肯定会对这个策略看到更好的答案感兴趣。

另一种策略是为用户输入轮询stdin作为主循环的一部分。 (“按Q键退出/暂停” - 类似的事情)。这避开了处理信号的问题。

7

这确实可以使用​​完成。我不会推荐这个解决方案,但是我有兴趣去做点什么,所以我想我会分享它。

parent.py

#!/usr/bin/python 

from ctypes import * 
import signal 
import subprocess 
import sys 
import time 

# Get the size of the array used to 
# represent the signal mask 
SIGSET_NWORDS = 1024/(8 * sizeof(c_ulong)) 

# Define the sigset_t structure 
class SIGSET(Structure): 
    _fields_ = [ 
     ('val', c_ulong * SIGSET_NWORDS) 
    ] 

# Create a new sigset_t to mask out SIGINT 
sigs = (c_ulong * SIGSET_NWORDS)() 
sigs[0] = 2 ** (signal.SIGINT - 1) 
mask = SIGSET(sigs) 

libc = CDLL('libc.so.6') 

def handle(sig, _): 
    if sig == signal.SIGINT: 
     print("SIGINT from parent!") 

def disable_sig(): 
    '''Mask the SIGINT in the child process''' 
    SIG_BLOCK = 0 
    libc.sigprocmask(SIG_BLOCK, pointer(mask), 0) 

# Set up the parent's signal handler 
signal.signal(signal.SIGINT, handle) 

# Call the child process 
pid = subprocess.Popen("./child.py", stdout=sys.stdout, stderr=sys.stdin, preexec_fn=disable_sig) 

while (1): 
    time.sleep(1) 

child.py

#!/usr/bin/python 
import time 
import signal 

def handle(sig, _): 
    if sig == signal.SIGINT: 
     print("SIGINT from child!") 

signal.signal(signal.SIGINT, handle) 
while (1): 
    time.sleep(1) 

注意,这使得一堆关于各种各样的libc结构,因此假设,可能是相当脆弱的。运行时,您不会看到消息“来自孩子的信号!”打印。但是,如果您将电话注释到sigprocmask,那么您将会。似乎做的工作:)

+0

谢谢你的建议。我认为这对我的目标来说太复杂了。基本上,我只想暂停父母脚本,询问用户并向所有子进程发送信号。也许另一个键盘输入可能会暂停父母脚本,如ctrl + x? – 2010-09-26 16:02:20

+0

是的,正如我所说的,我不会推荐这个解决方案。如果您在单独的线程中监听键盘事件,则可以使用任何您想要暂停父项的组合键。 – 2010-09-26 22:10:07

2

我解决了这个问题,通过创建一个帮助器应用程序,我打电话,而不是直接创建孩子。这个帮助者改变其父组,然后产生真实的子进程。

import os 
import sys 

from time import sleep 
from subprocess import Popen 

POLL_INTERVAL=2 

# dettach from parent group (no more inherited signals!) 
os.setpgrp() 

app = Popen(sys.argv[1:]) 
while app.poll() is None: 
    sleep(POLL_INTERVAL) 

exit(app.returncode) 

我调用父这个助手,传递真实的孩子,它的参数作为参数:

Popen(["helper", "child", "arg1", ...]) 

我必须这样做,因为我的孩子的应用程序是不是我的控制之下,如果它是我可以在那里添加setpgrp,并绕过辅助器。

17

结合一些其他答案,将做的伎俩 - 没有信号发送到主应用程序将被转发到子进程。

import os 
from subprocess import Popen 

def preexec(): # Don't forward signals. 
    os.setpgrp() 

Popen('whatever', preexec_fn = preexec) 
+11

如果您使用的是Python 2.x和**不再使用'preexec_fn' **,请考虑使用[subprocess32] [1]而不是子进程。这是不安全的。改用新的Popen ** start_new_session = True **参数。 [1]:http://code.google.com/p/python-subprocess32/ – gps 2012-09-18 06:18:01

+0

这正是我一直在寻找的。谢谢。 – 2014-02-12 01:27:59

+4

除了安全参数之外,上面的代码可以工作,因为通常当你按下Ctrl-C时,SIGINT被发送到进程组。默认情况下,所有子进程与父进程位于同一个进程组中。通过调用'setpgrp()'你把你的子进程放到一个新的进程组中,所以它不会从父进程获得信号。 – 2014-11-15 12:00:12

0

功能:

os.setpgrp() 

运作良好,只有POPEN被称为权之后。如果您试图阻止信号传播到任意包的子进程,那么在创建子进程之前,程序包可能会覆盖该信号,导致无论如何都会传播信号。例如,当试图阻止信号传播到Selenium软件包产生的Web浏览器进程时就是这种情况。

该功能还消除了分离过程之间轻松通信的能力,而无需使用套接字之类的东西。

就我的目的而言,这看起来像是矫枉过正。我不用担心信号传播,而是使用自定义信号SIGUSR1。许多Python包忽略SIGUSR1,因此即使它被发送到所有的子进程,它通常会被使用

kill -10 <pid> 

忽略

它可以被发送到一个进程在bash在Ubuntu它可以识别你的代码经由

signal.signal(signal.SIGUSR1, callback_function) 

在Ubuntu可用的信号号码可以在/usr/include/asm/signal.h找到。