我理解在设置信号处理程序时,所有子进程都默认继承所述处理程序。
因此,以下代码按预期运行:
err
即。无论我按Ctrl-C多少,脚本都会在10秒后才会终止。
但是,如果我将通话切换为import subprocess, signal
signal.signal( signal.SIGINT, signal.SIG_IGN ) # use the ignore handler
subprocess.check_call( [ "sleep", "10" ] )
,我似乎我能够中断脚本。
我不明白为什么行为有所不同......有什么想法?
谢谢!
答案 0 :(得分:2)
我理解在设置信号处理程序时,所有子进程都默认继承所述处理程序。
如上所述,虽然你的例子很好,但这并不完全正确。
signal.signal( signal.SIGINT, signal.SIG_IGN ) # use the ignore handler
从技术上讲,SIG_IGN
根本不是处理程序,而是一个特殊值,告诉内核在发送信号时应该丢弃该信号。 处理程序是用户提供的函数;在幕后,当您安装处理程序时,内核会设置为将信号传递给用户代码。 1
这里的关键区别是用户代码版本要求所述用户代码继续存在,但是当一个进程运行另一个进程时,使用fork + exec或spawn或者其他任何东西(所有这些都很好地隐藏了Python subprocess.Popen
接口),新进程已丢弃,甚至从未有过原始进程的用户代码。这意味着新进程中不再存在任何基于用户代码的处理程序(无论是sleep
还是git
还是其他任何处理程序),因此必须将信号处理恢复为默认值。
然而,当使用SIG_IGN
时,处置是“立即丢弃信号”,不需要用户代码操作。因此fork + exec或spawn(再次隐藏在subprocess.Popen
后面)不会强制重置信号处理。
SIGINT
设置自己的新处置。
程序员应该,至少在理论上,用一些样板来编写这种代码。在Python-ese中它将翻译为:
with signal.hold([signal.SIGINT]):
previous = signal.signal(signal.SIGINT, handler)
if previous == signal.SIG_IGN:
# Parent process told us NOT to catch SIGINT,
# so we should leave it ignored.
signal.signal(signal.SIGINT, signal.SIG_IGN)
这使用Python无法公开的一对函数(可能因为并非所有系统实际实现了它,尽管它现在是标准POSIX),包含在with
上下文管理器中。如果我们首先设置信号的处置,然后检查是什么并在需要时恢复它,我们打开一个竞赛窗口,在此期间我们更改了处置。为了解决这个问题,我们可以使用POSIX sigprocmask
函数暂时阻止信号(在内核中推迟一段时间),同时我们处理这个问题。一旦我们确定我们有正确的处置方式,我们就会解锁信号。如果在此期间有任何交付,它们将在解锁点处理。
这一切都没有多大帮助,因为它需要对其他程序进行修复(在安装自己的处理程序时检查信号的初始处置)。但是, 有几种方法可以解决它。最简单的方法是使用信号阻塞技术,因为阻塞掩码也继承,以及任何“忽略”处置 - 并且大多数程序都不会烦恼于阻塞掩码,或者如果他们这样做,使用正确的样板(我们隐藏在不存在的Python with signal.hold(...)
技巧背后的那个):
sigprocmask
来阻止信号
sigprocmask
恢复已保存的掩码(而不仅仅是显式取消阻止)。不幸的是,这需要调用POSIX sigprocmask
函数,即使在Python 3.4中也没有公开。 Python 3.4 公开pthread_sigmask
,这可能(取决于你的内核)就足够了。但是,目前尚不清楚它是否值得编码。
另一种(甚至更复杂的)处理方法是让你的Python程序像大多数shell一样进行POSIX样式的作业控制。然后,它可以决定哪个进程组应该接收tty生成的信号,例如SIGINT
。
1 从技术上讲,用户信号传递的内核通过称为 trampoline 的东西。这有几种不同的传统机制,它是一个相当大的头发,以确保它一切正常。