用Python处理Unix信号,,UNIX / Lin


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 timedef receiveSignal(signalNumber, frame):      print(‘Received:‘, signalNumber)    raise SystemExit(‘Exiting‘)    returnif __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‘)    returndef 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),得到如下输出:

[email protected] /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 signalimport osimport timeimport sysdef readConfiguration(signalNumber, frame):    print (‘(SIGHUP) reading configuration‘)    returndef terminateProcess(signalNumber, frame):    print (‘(SIGTERM) terminating the process‘)    sys.exit()def receiveSignal(signalNumber, frame):    print(‘Received:‘, signalNumber)    returnif __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)

用Python处理Unix信号

评论关闭