18.8. "signal" --- 设置异步事件处理程序
***************************************

======================================================================

该模块提供了在 Python 中使用信号处理程序的机制。


18.8.1. 一般规则
================

The "signal.signal()" function allows defining custom handlers to be
executed when a signal is received.  A small number of default
handlers are installed: "SIGPIPE" is ignored (so write errors on pipes
and sockets can be reported as ordinary Python exceptions) and
"SIGINT" is translated into a "KeyboardInterrupt" exception.

一旦设置，特定信号的处理程序将保持安装，直到它被显式重置（ Python 模拟
BSD 样式接口而不管底层实现），但 "SIGCHLD" 的处理程序除外，它遵循底层
实现。


18.8.1.1. 执行 Python 信号处理程序
----------------------------------

Python 信号处理程序不会在低级（ C ）信号处理程序中执行。相反，低级信号
处理程序设置一个标志，告诉 *virtual machine* 稍后执行相应的 Python 信
号处理程序（例如在下一个 *bytecode* 指令）。这会导致：

* 捕获同步错误是没有意义的，例如 "SIGFPE" 或 "SIGSEGV" ，它们是由 C 代
  码中的无效操作引起的。Python 将从信号处理程序返回到 C 代码，这可能会
  再次引发相同的信号，导致 Python 显然的挂起。 从Python 3.3开始，你可
  以使用 "faulthandler" 模块来报告同步错误。

* 纯 C 中实现的长时间运行的计算（例如在大量文本上的正则表达式匹配）可
  以在任意时间内不间断地运行，而不管接收到任何信号。计算完成后将调用
  Python 信号处理程序。


18.8.1.2. 信号与线程
--------------------

Python 信号处理程序总是在主 Python 线程中执行，即使信号是在另一个线程
中接收的。这意味着信号不能用作线程间通信的手段。 你可以使用
"threading" 模块中的同步原函数。

此外，只允许主线程设置新的信号处理程序。


18.8.2. 模块内容
================

3.5 版更變: 信号（ SIG* ），处理程序（ "SIG_DFL" ， "SIG_IGN"）和
sigmask（ "SIG_BLOCK" ， "SIG_UNBLOCK" ， "SIG_SETMASK" ）下面列出的相
关常量变成了 "enums" 。 "getsignal()" ， "pthread_sigmask()" ，
"sigpending()" 和 "sigwait()" 函数返回人类可读的 "enums" 。

在 "signal" 模块中定义的变量是：

signal.SIG_DFL

   这是两种标准信号处理选项之一；它只会执行信号的默认函数。 例如，在大
   多数系统上，对于 "SIGQUIT" 的默认操作是转储核心并退出，而对于
   "SIGCHLD" 的默认操作是简单地忽略它。

signal.SIG_IGN

   这是另一个标准信号处理程序，它将简单地忽略给定的信号。

SIG*

   All the signal numbers are defined symbolically.  For example, the
   hangup signal is defined as "signal.SIGHUP"; the variable names are
   identical to the names used in C programs, as found in
   "<signal.h>". The Unix man page for '"signal()"' lists the existing
   signals (on some systems this is *signal(2)*, on others the list is
   in *signal(7)*). Note that not all systems define the same set of
   signal names; only those names defined by the system are defined by
   this module.

signal.CTRL_C_EVENT

   对应于 "Ctrl+C" 击键事件的信号。此信号只能用于 "os.kill()" 。

   Availability: Windows.

   3.2 版新加入.

signal.CTRL_BREAK_EVENT

   对应于 "Ctrl+Break" 击键事件的信号。此信号只能用于 "os.kill()" 。

   Availability: Windows.

   3.2 版新加入.

signal.NSIG

   比最高信号数多一。

signal.ITIMER_REAL

   实时递减间隔计时器，并在到期时发送 "SIGALRM" 。

signal.ITIMER_VIRTUAL

   仅在进程执行时递减间隔计时器，并在到期时发送 SIGVTALRM 。

signal.ITIMER_PROF

   当进程执行时以及当系统替进程执行时都会减小间隔计时器。 这个计时器与
   ITIMER_VIRTUAL 相配结，通常被用于分析应用程序在用户和内核空间中花费
   的时间。 SIGPROF 会在超期时被发送。

signal.SIG_BLOCK

   "pthread_sigmask()" 的 *how* 形参的一个可能的值，表明信号将会被阻塞
   。

   3.3 版新加入.

signal.SIG_UNBLOCK

   "pthread_sigmask()" 的 *how* 形参的是个可能的值，表明信号将被解除阻
   塞。

   3.3 版新加入.

signal.SIG_SETMASK

   "pthread_sigmask()" 的 *how* 形参的一个可能的值，表明信号掩码将要被
   替换。

   3.3 版新加入.

"signal" 模块定义了一个异常:

exception signal.ItimerError

   作为来自下层 "setitimer()" 或 "getitimer()" 实现错误的信号被引发。
   如果将无效的定时器或负的时间值传给 "setitimer()" 就导致这个错误。
   此错误是 "OSError" 的子类型。

   3.3 版新加入: 此错误是 "IOError" 的子类型，现在则是 "OSError" 的别
   名。

"signal" 模块定义了以下函数:

signal.alarm(time)

   If *time* is non-zero, this function requests that a "SIGALRM"
   signal be sent to the process in *time* seconds. Any previously
   scheduled alarm is canceled (only one alarm can be scheduled at any
   time).  The returned value is then the number of seconds before any
   previously set alarm was to have been delivered. If *time* is zero,
   no alarm is scheduled, and any scheduled alarm is canceled.  If the
   return value is zero, no alarm is currently scheduled.  (See the
   Unix man page *alarm(2)*.) Availability: Unix.

signal.getsignal(signalnum)

   返回当前用于信号 *signalnum* 的信号处理程序。 返回值可以是一个
   Python 可调用对象，或是特殊值 "signal.SIG_IGN", "signal.SIG_DFL" 或
   "None" 之一。 在这里，"signal.SIG_IGN" 表示信号在之前被忽略，
   "signal.SIG_DFL" 表示之前在使用默认的信号处理方式，而 "None" 表示之
   前的信号处理程序未由 Python 安装。

signal.pause()

   Cause the process to sleep until a signal is received; the
   appropriate handler will then be called.  Returns nothing.  Not on
   Windows. (See the Unix man page *signal(2)*.)

   另请参阅 "sigwait()", "sigwaitinfo()", "sigtimedwait()" 和
   "sigpending()"。

signal.pthread_kill(thread_id, signalnum)

   Send the signal *signalnum* to the thread *thread_id*, another
   thread in the same process as the caller.  The target thread can be
   executing any code (Python or not).  However, if the target thread
   is executing the Python interpreter, the Python signal handlers
   will be executed by the main thread.  Therefore, the only point of
   sending a signal to a particular Python thread would be to force a
   running system call to fail with "InterruptedError".

   使用 "threading.get_ident()" 或 "threading.Thread" 对象的 "ident"
   属性为 *thread_id* 获取合适的值。

   如果 *signalnum* 为 0，则不会发送信号，但仍然会执行错误检测；这可被
   用来检测目标线程是否仍在运行。

   Availability: Unix (see the man page *pthread_kill(3)* for further
   information).

   另请参阅 "os.kill()"。

   3.3 版新加入.

signal.pthread_sigmask(how, mask)

   获取和/或修改调用方线程的信号掩码。 信号掩码是一组传送过程目前为调
   用者而阻塞的信号集。 返回旧的信号掩码作为一组信号。

   该调用的行为取决于 *how* 的值，具体见下。

   * "SIG_BLOCK": 被阻塞的信号集是当前集与 *mask* 参数的并集。

   * "SIG_UNBLOCK": *mask* 中的信号会从当前已阻塞信号集中被移除。 允许
     尝试取消对一个非阻塞信号的阻塞。

   * "SIG_SETMASK": 已阻塞信号集会被设为 *mask* 参数的值。

   *mask* is a set of signal numbers (e.g. {"signal.SIGINT",
   "signal.SIGTERM"}). Use "range(1, signal.NSIG)" for a full mask
   including all signals.

   例如，"signal.pthread_sigmask(signal.SIG_BLOCK, [])" 会读取调用方线
   程的信号掩码。

   Availability: Unix. See the man page *sigprocmask(3)* and
   *pthread_sigmask(3)* for further information.

   另请参阅 "pause()", "sigpending()" 和 "sigwait()"。

   3.3 版新加入.

signal.setitimer(which, seconds[, interval])

   Sets given interval timer (one of "signal.ITIMER_REAL",
   "signal.ITIMER_VIRTUAL" or "signal.ITIMER_PROF") specified by
   *which* to fire after *seconds* (float is accepted, different from
   "alarm()") and after that every *interval* seconds. The interval
   timer specified by *which* can be cleared by setting seconds to
   zero.

   当一个间隔计时器启动时，会有信号发送至进程。 所发送的具体信号取决于
   所使用的计时器；"signal.ITIMER_REAL" 将发送 "SIGALRM",
   "signal.ITIMER_VIRTUAL" 将发送 "SIGVTALRM", 而 "signal.ITIMER_PROF"
   将发送 "SIGPROF".

   原有的值会以元组: (delay, interval) 的形式被返回。

   Attempting to pass an invalid interval timer will cause an
   "ItimerError".  Availability: Unix.

signal.getitimer(which)

   Returns current value of a given interval timer specified by
   *which*. Availability: Unix.

signal.set_wakeup_fd(fd)

   将唤醒文件描述符设为 *fd*。 当接收到信号时，会将信号编号以单个字节
   的形式写入 fd。 这可被其它库用来唤醒一次 poll 或 select 调用，以允
   许该信号被完全地处理。

   原有的唤醒 fd 会被返回（或者如果未启用文件描述符唤醒则返回 -1）。
   如果 *fd* 为 -1，文件描述符唤醒会被禁用。 如果不为 -1，则 *fd* 必须
   为非阻塞型。 需要由库来负责在重新调用 poll 或 select 之前从 *fd* 移
   除任何字节数据。

   Use for example "struct.unpack('%uB' % len(data), data)" to decode
   the signal numbers list.

   When threads are enabled, this function can only be called from the
   main thread; attempting to call it from other threads will cause a
   "ValueError" exception to be raised.

   3.5 版更變: 在 Windows 上，此函数现在也支持套接字处理。

signal.siginterrupt(signalnum, flag)

   Change system call restart behaviour: if *flag* is "False", system
   calls will be restarted when interrupted by signal *signalnum*,
   otherwise system calls will be interrupted.  Returns nothing.
   Availability: Unix (see the man page *siginterrupt(3)* for further
   information).

   请注意用 "signal()" 安装信号处理程序将重启行为重置为可通过显式调用
   "siginterrupt()" 并为给定信号的 *flag* 设置真值来实现中断。

signal.signal(signalnum, handler)

   Set the handler for signal *signalnum* to the function *handler*.
   *handler* can be a callable Python object taking two arguments (see
   below), or one of the special values "signal.SIG_IGN" or
   "signal.SIG_DFL".  The previous signal handler will be returned
   (see the description of "getsignal()" above).  (See the Unix man
   page *signal(2)*.)

   When threads are enabled, this function can only be called from the
   main thread; attempting to call it from other threads will cause a
   "ValueError" exception to be raised.

   *handler* 将附带两个参数调用：信号编号和当前堆栈帧 ("None" 或一个帧
   对象；有关帧对象的描述请参阅 类型层级结构描述 或者参阅 "inspect" 模
   块中的属性描述)。

   在 Windows 上，"signal()" 调用只能附带 "SIGABRT", "SIGFPE",
   "SIGILL", "SIGINT", "SIGSEGV", "SIGTERM" 或 "SIGBREAK"。 任何其他值
   都将引发 "ValueError"。 请注意不是所有系统都定义了同样的信号名称集
   合；如果一个信号名称未被定义为 "SIG*" 模块层级常量则将引发
   "AttributeError"。

signal.sigpending()

   检查正在等待传送给调用方线程的信号集合（即在阻塞期间被引发的信号）
   。 返回正在等待的信号集合。

   Availability: Unix (see the man page *sigpending(2)* for further
   information).

   另请参阅 "pause()", "pthread_sigmask()" 和 "sigwait()"。

   3.3 版新加入.

signal.sigwait(sigset)

   挂起调用方线程的执行直到信号集合 *sigset* 中指定的信号之一被传送。
   此函数会接受该信号（将其从等待信号列表中移除），并返回信号编号。

   Availability: Unix (see the man page *sigwait(3)* for further
   information).

   另请参阅 "pause()", "pthread_sigmask()", "sigpending()",
   "sigwaitinfo()" 和 "sigtimedwait()"。

   3.3 版新加入.

signal.sigwaitinfo(sigset)

   挂起调用方线程的执行直到信号集合 *sigset* 中指定的信号之一被传送。
   此函数会接受该信号并将其从等待信号列表中移除。 如果 *sigset* 中的信
   号之一已经在等待调用方线程，此函数将立即返回并附带有关该信号的信息
   。 被传送信号的信号处理程序不会被调用。 如果该函数被某个不在
   *sigset* 中的信号中断则会引发 "InterruptedError"。

   返回值是一个代表 "siginfo_t" 结构体所包含数据的对象，具体为:
   "si_signo", "si_code", "si_errno", "si_pid", "si_uid", "si_status",
   "si_band"。

   Availability: Unix (see the man page *sigwaitinfo(2)* for further
   information).

   另请参阅 "pause()", "sigwait()" 和 "sigtimedwait()"。

   3.3 版新加入.

   3.5 版更變: 当被某个 不在 *sigset* 中的信号中断时本函数将结束并且信
   号处理程序不会引发异常 (其理由参见 **PEP 475**)。

signal.sigtimedwait(sigset, timeout)

   类似于 "sigwaitinfo()"，但会接受一个额外的 *timeout* 参数来指定超时
   限制。 如果将 *timeout* 指定为 "0"，则会执行轮询。 如果发生超时则返
   回 "None"。

   Availability: Unix (see the man page *sigtimedwait(2)* for further
   information).

   另请参阅 "pause()", "sigwait()" 和 "sigwaitinfo()"。

   3.3 版新加入.

   3.5 版更變: 现在当此函数被某个不在 *sigset* 中的信号中断时将以计算
   出的 *timeout* 进行重试并且信号处理程序不会引发异常（请参阅 **PEP
   475** 了解其理由）。


18.8.3. 示例
============

这是一个最小示例程序。 它使用 "alarm()" 函数来限制等待打开一个文件所花
费的时间；这在文件为无法开启的串行设备时会很有用处，此情况通常会导致
"os.open()" 无限期地挂起。  解决办法是在打开文件之前设置 5 秒钟的
alarm；如果操作耗时过长，将会发送 alarm 信号，并且处理程序会引发一个异
常。

   import signal, os

   def handler(signum, frame):
       print('Signal handler called with signal', signum)
       raise OSError("Couldn't open device!")

   # Set the signal handler and a 5-second alarm
   signal.signal(signal.SIGALRM, handler)
   signal.alarm(5)

   # This open() may hang indefinitely
   fd = os.open('/dev/ttyS0', os.O_RDWR)

   signal.alarm(0)          # Disable the alarm
