如何阻止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)
[...]
所以无论我怎么做,程序都收到了我只想让Python脚本看到的SIGINT信号。我该怎么做才能实现呢???
我还尝试过:
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信号。
谢谢!
5 个回答
根据POSIX的规定,使用execvp(这就是subprocess.Popen使用的方式)运行的程序应该会继承调用进程的信号屏蔽。
我可能错了,但我觉得调用signal
并不会修改这个屏蔽。你需要使用sigprocmask
,不过Python并没有直接提供这个功能。
虽然这有点绕,但你可以尝试通过直接调用libc来设置它,使用ctypes。我很想看到关于这个方法的更好答案。
另一种方法是,在你的主循环中轮询标准输入,等待用户输入。比如可以提示“按Q退出/暂停”之类的。这种方式可以避免处理信号的问题。
确实可以用 ctypes
来实现这个功能。我并不太推荐这个方法,但我对这个问题挺感兴趣的,所以就做了一些尝试,想和大家分享一下。
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 结构做了很多假设,因此可能会比较脆弱。在运行时,你不会看到“来自子进程的 SIGINT!”这个消息被打印出来。不过,如果你把 sigprocmask
的调用注释掉,那么你就会看到。看起来这个方法还是能起作用的 :)
把其他一些回答结合起来,这样做就能解决问题——没有信号会被发送到主应用程序,也不会转发给子进程。
import os
from subprocess import Popen
def preexec(): # Don't forward signals.
os.setpgrp()
Popen('whatever', preexec_fn = preexec)