试图忽略 SIGINT 时不一致

Inconsistency when trying to ignore SIGINT

据我了解,当您设置信号处理程序时,所有子进程默认都会继承该处理程序。

因此,以下代码按预期运行:

import subprocess, signal
signal.signal( signal.SIGINT, signal.SIG_IGN ) # use the ignore handler
subprocess.check_call( [ "sleep", "10" ] )

即无论我按多少次 Ctrl-C,脚本都不会在 10 秒过去后终止。

但是,如果我将调用切换到 git clone xxxxxx,似乎我 能够中断脚本。

我不明白为什么行为会有所不同...有什么想法吗?

谢谢!

I am of the understanding that when you set a signal handler, all child processes inherit said handler by default.

虽然你的例子很好,但按照措辞,这不太正确。

signal.signal( signal.SIGINT, signal.SIG_IGN ) # use the ignore handler

从技术上讲,SIG_IGN 根本不是一个处理程序,而是一个特殊的值,告诉内核信号在发送时应该被丢弃。 handler 是用户提供的函数;在幕后,当您安装处理程序时,内核会设置为将信号传递给用户代码。1

这里的主要区别在于 用户代码 版本要求所述用户代码继续存在,但是当一个进程运行另一个进程时,使用 fork+exec 或 spawn 或其他( Python subprocess.Popen 接口很好地隐藏了),新进程已经丢弃,甚至从未有过来自原始进程的用户代码。这意味着新进程中不再存在任何基于用户代码的处理程序(无论是 sleep 还是 git 或其他任何东西),因此必须将信号处理恢复为默认值。

然而,当使用 SIG_IGN 时,处置是 "discard signal immediately",不需要用户代码操作。因此 fork+exec 或 spawn(再次隐藏在 subprocess.Popen 后面)不会强制重置信号配置。

一样,但是,任何进程都可以根据自己的意愿改变信号的配置。显然 Git 正在为 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 上下文中经理。如果我们先简单地设置信号的配置,然后检查它 是什么 并在需要时恢复它,我们将开启一场比赛 window,在此期间我们已经 改变了性格。为了修复竞争,我们可以使用 POSIX sigprocmask 函数暂时 阻止 信号(在内核中延迟一段时间),同时我们 fiddle左右配合配置。一旦我们确定我们有正确的处置,我们就会解锁信号。如果在此期间有任何交付,它们将在解锁点处理掉。

None 这有很大帮助,因为它需要对其他程序进行修复(以在它们安装自己的处理程序时检查信号的初始配置)。但是, 几种解决方法。最简单的是使用信号阻塞技术,因为阻塞掩码是 继承的,以及任何 "ignore" 处置——而且大多数程序不会为阻塞掩码而烦恼,或者如果他们这样做,请使用正确的样板(我们在此处隐藏在不存在的 Python with signal.hold(...) 技巧后面的样板):

  • 调用sigprocmask在入口处检索当前掩码时阻止信号
  • 在退出时调用 sigprocmask 恢复保存的掩码 (不仅仅是显式解锁)。

不幸的是,这需要调用 POSIX sigprocmask 函数,即使在 Python 3.4 中也没有公开。 Python 3.4 确实 公开了 pthread_sigmask 并且这可能(取决于您的内核)就足够了。不过,尚不清楚是否值得编写代码。

另一种(甚至更复杂)处理此问题的方法是让您的 Python 程序执行 POSIX 风格的作业控制,就像大多数 shell 一样。然后它可以决定哪个进程组应该接收 tty 生成的信号,例如 SIGINT.


1从技术上讲,内核到用户的信号传递要经过一种叫做 trampoline 的东西。有几种不同的传统机制,要确保一切正常,这是一个相当大的问题。