如何在Python中阻止SIGINT被传递给子进程?

16

我的Python脚本使用signal模块拦截SIGINT信号以防止过早退出,但是这个信号会传递给我使用Popen打开的子进程。有没有办法防止将此信号传递给子进程,以便用户按下ctrl-c时子进程也不会被意外退出?

3个回答

16

当启动子进程时,会继承信号处理程序。因此,如果您使用signal模块来忽略SIGINT信号 (signal.signal(signal.SIGINT, signal.SIG_IGN)),那么您的子进程也会自动忽略SIGINT信号。

但需要注意两个重要的限制:

  • 在派生子进程之前,必须设置忽略处理程序。
  • 自定义的信号处理程序将被重置为默认处理程序,因为子进程无法访问处理程序代码以运行它。

因此,如果您需要自定义处理SIGINT而不仅仅是忽略它,您可能需要在生成子进程时暂时忽略SIGINT,然后(重新)设置自定义信号处理程序。

如果您试图捕获SIGINT并设置标志以便在安全点退出而不是立即退出,请记住,在到达该安全点时,您的代码将必须手动清理其子孙进程,因为您的子进程和任何由它启动的进程都将忽略SIGINT信号。


1
如果这个方法可行(虽然并不总是),与终端折腾相比,这是一个更好的选择。 - zwol
1
如果使用fork()/exec()而非popen(),则可以在fork()之后在exec()之前执行SIG_IGN。这避免了在父进程中暂时禁用处理程序。似乎无论你做什么都会出现竞争条件,但fork()/exec()的方式似乎更安全一些。当然,OP是在问Python的Popen,但这个问题似乎真正涉及到Posix;这就是我来到这里的原因。 - thejoshwolfe
2
为避免竞态条件,你可以使用 sigprocmask() 延迟信号,设置处理程序,fork 进程,再次设置处理程序,最后再次使用 sigprocmask() 重新启用信号。(如果在 sigprocmask() 调用之间有任何信号到达,则它们将在此时被传递。) - Nathaniel J. Smith
1
请注意,子进程的子进程仍可能收到SIGINT信号(例如,在Python中作为子进程启动的apt-get可能会生成dpkg或gpgv)。在这种情况下,需要通过将“preexec_fn = os.setpgrp”传递给“subprocess.Popen”来创建一个新的进程组,并在父信号处理程序中处理所有内容(包括手动信号转发到子进程)。 - Kalle Richter

2
您可以使用tty模块重新分配ctrl-c的作用,该模块允许您操纵信号的分配。请注意,除非将它们在修改之前恢复,否则它们将在整个会话期间持续存在,甚至在程序退出后也是如此。
以下是一个简单的代码片段,可帮助您存储旧的tty设置,将ctrl-c重新分配为ctrl-x,然后在退出时恢复上一个tty设置。
import sys
import tty

# Back up previous tty settings
stdin_fileno = sys.stdin.fileno()
old_ttyattr = tty.tcgetattr(stdin_fileno)

try:
    print 'Reassigning ctrl-c to ctrl-x'

    # Enter raw mode on local tty
    tty.setraw(stdin_fileno)
    raw_ta = tty.tcgetattr(stdin_fileno)
    raw_ta[tty.LFLAG] |= tty.ISIG
    raw_ta[tty.OFLAG] |= tty.OPOST | tty.ONLCR

    # ^X is the new ^C, set this to 0 to disable it entirely
    raw_ta[tty.CC][tty.VINTR] = '\x18'  

    # Set raw tty as active tty
    tty.tcsetattr(stdin_fileno, tty.TCSANOW, raw_ta)

    # Dummy program loop
    import time
    for _ in range(5):
        print 'doing stuff'
        time.sleep(1)

finally:
    print 'Resetting ctrl-c'
    # Restore previous tty no matter what
    tty.tcsetattr(stdin_fileno, tty.TCSANOW, old_ttyattr)

0

对于 Python 2 代码库:子进程已经损坏。

正确的方法是

import subprocess32 as subprocess

请查看subprocess32

这是Python 3子进程模块的回溯版本,可用于Python 2。该代码尚未在Windows或其他非POSIX平台上进行测试。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接