用Python处理Unix信号

UNIX / Linux系统提供了在每个单独进程之间进行通信的特殊机制。这些机制之一是信号,属于进程之间的不同通信方法(进程间通信,缩写为IPC)。

简而言之,信号是软件中断,它被发送到程序(或进程),将重要事件或请求通知程序,以便运行特殊的代码序列。接收到信号的程序要么停止或继续执行其指令,要么在有或没有内存转储的情况下终止,甚至干脆忽略该信号。

 

虽然在POSIX标准中定义了它,但是实际的情况取决于开发人员如何编写脚本和实现信号处理。

 

在本文中,我们将解释什么是信号,向您展示如何从命令行向另一个进程发送信号,以及如何处理接收到的信号。在其他模块中,程序代码主要基于信号模块。这个模块将操作系统的according C头文件与Python连接起来

 

信号简介

在基于unix的系统中,有三类信号:

  • 系统信号(硬件和系统错误):SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGKILL, SIGSEGV, SIGXCPU, SIGXFSZ, SIGIO
  • 设备信号:SIGHUP、SIGINT、SIGPIPE、SIGALRM、SIGCHLD、SIGCONT、SIGSTOP、SIGTTIN、SIGTTOU、SIGURG、SIGWINCH、SIGIO
  • 用户自定义信号:SIGQUIT、SIGABRT、SIGUSR1、SIGUSR2、SIGTERM

每个信号都用一个整数值表示,可用的信号列表相当长,而且不同UNIX/Linux发行版之间不一致。在Ubuntu18.04系统上,命令kill -l显示信号列表如下:

信号1到15基本上是标准化的,在大多数Linux系统中含义如下:

  • 1 (SIGHUP): 终止连接,或重新加载守护进程的配置
  • 2 (SIGINT): 当用户希望中断该过程时,SIGINT信号由其控制终端发送到进程。这通常通过按Ctrl+C启动,但在某些系统上,可以使用"delete" 或者 "break" 替代
  • 3 (SIGQUIT): 当用户请求进程退出并执行核心转储时,SIGQUIT信号通过其控制终端发送给进程。
  • 4 (SIGILL): 当进程试图执行非法、未知或特权指令时,SIGILL信号被发送到进程。
  • 5 (SIGTRAP): 当异常发生时,SIGTRAP信号被发送到进程:调试器请求被告知的一个条件——例如,当一个特定的函数被执行时,或者当一个特定的变量改变值时。
  • 6 (SIGABRT): 异常终止
  • 7 (SIGBUS): 系统总线错误
  • 8 (SIGFPE): 算术运算错误
  • 9 (SIGKILL): 立即终止进程
  • 10 (SIGUSR1): 用户定义的信号
  • 11 (SIGSEGV): 由于非法访问内存段而导致的分割错误,它做了一个无效的虚拟内存引用,或分割故障
  • 12 (SIGUSR2): 用户定义的信号
  • 13 (SIGPIPE): 当进程试图写入没有连接到另一端的进程的管道时,SIGPIPE信号被发送到进程
  • 14 (SIGALRM):计时器终止(alarm)
  • 15 (SIGTERM): SIGTERM信号被发送到进程请求终止。与SIGKILL信号不同,它可以被进程捕获并解释或忽略。这允许进程执行良好的终止释放资源和保存状态(如果合适的话)。SIGINT与SIGTERM几乎相同。

为了向Linux终端中的进程发送信号,可以使用上面列表中的信号号(或信号名)和进程id (pid)调用kill命令。下面的示例命令向pid为12345的进程发送信号15 (SIGTERM):

$ kill -15 12345

一个等效的方法是使用信号名而不是它的编号:

$ kill -SIGTERM 12345

 

 

使用Python信号库

自Python 1.4以来,信号库是每个Python发行版的内置库。为了使用信号库,请将信号库导入Python程序,如下

import signal  

捕获并对接收到的信号做出正确的反应是由一个回调函数完成的。 一个所谓的信号处理程序。一个相当简单的信号处理程序receiveSignal()可以编写如下:

def receiveSignal(signalNumber, frame):  
    print('Received:', signalNumber)
    return

这个信号处理程序只报告接收到的信号的数量。下一步是注册信号处理程序捕获的信号。对于Python程序,所有的信号(除了9,SIGKILL)都可以在您的脚本中捕获:

if __name__ == '__main__':  
    # register the signals to be caught
    signal.signal(signal.SIGHUP, receiveSignal)
    signal.signal(signal.SIGINT, receiveSignal)
    signal.signal(signal.SIGQUIT, receiveSignal)
    signal.signal(signal.SIGILL, receiveSignal)
    signal.signal(signal.SIGTRAP, receiveSignal)
    signal.signal(signal.SIGABRT, receiveSignal)
    signal.signal(signal.SIGBUS, receiveSignal)
    signal.signal(signal.SIGFPE, receiveSignal)
    #signal.signal(signal.SIGKILL, receiveSignal)
    signal.signal(signal.SIGUSR1, receiveSignal)
    signal.signal(signal.SIGSEGV, receiveSignal)
    signal.signal(signal.SIGUSR2, receiveSignal)
    signal.signal(signal.SIGPIPE, receiveSignal)
    signal.signal(signal.SIGALRM, receiveSignal)
    signal.signal(signal.SIGTERM, receiveSignal)

 

接下来,我们添加当前进程的进程信息,并使用os模块中的getpid()方法检测进程id。在无休止的while循环中,我们等待传入的信号。我们使用另外两个Python模块来实现它——os和time。我们在Python脚本的开头也导入了它们:

import os  
import time 

在主程序的while循环中,print语句输出“Waiting…”。函数调用time.sleep()使程序等待三秒钟。

    # output current process id
    print('My PID is:', os.getpid())

    # wait in an endless loop for signals 
    while True:
        print('Waiting...')
        time.sleep(3)

最后,我们必须测试脚本。将脚本保存为signal-handling.py后,我们可以在终端中调用它,如下所示:

$ python3 signal-handling.py 
My PID is: 5746  
Waiting...  
...

在第二个终端窗口中,我们向进程发送一个信号。我们通过上面屏幕上打印的进程id来标识第一个进程——Python脚本。

$ kill -1 5746

Python程序中的信号事件处理程序接收我们发送给进程的信号。它做出相应的反应,简单地确认接收到的信号:

...
Received: 1  
...

 

 

忽略Signal

信号模块定义了忽略接收信号的方法。为此,信号必须与预定义的函数signal. sig_ign连接。下面的示例演示了这一点,因此Python程序不能再被CTRL+C中断。为了停止Python脚本,示例脚本中实现了另一种方法——信号SIGUSR1终止Python脚本。此外,我们使用signal.pause()方法而不是循环。它只是等待接收到一个信号。

import signal  
import os  
import time

def receiveSignal(signalNumber, frame):  
    print('Received:', signalNumber)
    raise SystemExit('Exiting')
    return

if __name__ == '__main__':  
    # register the signal to be caught
    signal.signal(signal.SIGUSR1, receiveSignal)

    # register the signal to be ignored
    signal.signal(signal.SIGINT, signal.SIG_IGN)

    # output current process id
    print('My PID is:', os.getpid())

    signal.pause()

 

 

适当地处理信号

到目前为止,我们使用的信号处理程序相当简单,只报告接收到的信号。这向我们展示了Python脚本的接口工作得很好。让我们尝试改进它。

 

捕捉信号已经是一个很好的基础,但是需要一些改进才能符合POSIX标准的规则。为了获得更高的准确度,每个信号都需要适当的反应(见上面的列表)。这意味着Python脚本中的信号处理程序需要通过每个信号的特定例程进行扩展。如果我们理解了信号的作用,以及一个常见的反应是什么,这种方法就会发挥最佳效果。接收信号1、2、9或15的进程终止。在任何其他情况下,它也应该编写一个核心转储。

 

到目前为止,我们已经实现了一个覆盖所有信号的例子,并以相同的方式处理它们。下一步是为每个信号实现一个单独的例子。下面的示例代码演示了信号1 (SIGHUP)和信号15 (SIGTERM)。

 

def readConfiguration(signalNumber, frame):  
    print ('(SIGHUP) reading configuration')
    return

def terminateProcess(signalNumber, frame):  
    print ('(SIGTERM) terminating the process')
    sys.exit()

 

上述两个函数与信号连接如下:

 signal.signal(signal.SIGHUP, readConfiguration)
 signal.signal(signal.SIGTERM, terminateProcess)

运行Python脚本,然后UNIX命令kill -1 42096kill -15 42096发送信号1 (SIGHUP)和信号15 (SIGTERM),得到如下输出:

liuf2@liuf2-virtual-machine /u/l/g/tests> python daemon.py
('My PID is:', 42096)
Waiting...
Waiting...
Waiting...
Waiting...
(SIGHUP) reading configuration
Waiting...
Waiting...
Waiting...
Waiting...
(SIGTERM) terminating the process

 

程序接收信号,并正确地处理它们。以下为完整代码:

import signal
import os
import time
import sys

def readConfiguration(signalNumber, frame):
    print ('(SIGHUP) reading configuration')
    return

def terminateProcess(signalNumber, frame):
    print ('(SIGTERM) terminating the process')
    sys.exit()

def receiveSignal(signalNumber, frame):
    print('Received:', signalNumber)
    return

if __name__ == '__main__':
    # register the signals to be caught
    signal.signal(signal.SIGHUP, readConfiguration)
    signal.signal(signal.SIGINT, receiveSignal)
    signal.signal(signal.SIGQUIT, receiveSignal)
    signal.signal(signal.SIGILL, receiveSignal)
    signal.signal(signal.SIGTRAP, receiveSignal)
    signal.signal(signal.SIGABRT, receiveSignal)
    signal.signal(signal.SIGBUS, receiveSignal)
    signal.signal(signal.SIGFPE, receiveSignal)
    #signal.signal(signal.SIGKILL, receiveSignal)
    signal.signal(signal.SIGUSR1, receiveSignal)
    signal.signal(signal.SIGSEGV, receiveSignal)
    signal.signal(signal.SIGUSR2, receiveSignal)
    signal.signal(signal.SIGPIPE, receiveSignal)
    signal.signal(signal.SIGALRM, receiveSignal)
    signal.signal(signal.SIGTERM, terminateProcess)

    # output current process id
    print('My PID is:', os.getpid())

    # wait in an endless loop for signals
    while True:
        print('Waiting...')
        time.sleep(3)

 

posted @ 2019-03-25 14:16  twoseee  阅读(648)  评论(0编辑  收藏  举报