一、信号的概念

信号(signal)– 进程之间通讯的方式,是一种软件中断。一个进程一旦接收到信号就会打断原来的程序执行流程来处理信号。

在Linux下可以通过kill -l来查看所有的信息:

$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX

信号所传递的每一个整数都被赋予了特殊的意义,并有一个信号名对应该整数。常用的信号有SIGINT, SIGQUIT, SIGCONT, SIGTSTP, SIGALRM等。其含义如下:

SIGINT:当键盘按下CTRL+C从shell中发出信号,信号被传递给shell中前台运行的进程,对应该信号的默认操作是中断 (INTERRUPT) 该进程。

SIGQUIT:当键盘按下CTRL+\从shell中发出信号,信号被传递给shell中前台运行的进程,对应该信号的默认操作是退出 (QUIT) 该进程。

SIGTSTP:当键盘按下CTRL+Z从shell中发出信号,信号被传递给shell中前台运行的进程,对应该信号的默认操作是暂停 (STOP) 该进程。

SIGCONT:用于通知暂停的进程继续。

SIGALRM:起到定时器的作用,通常是程序在一定的时间之后才生成该信号。

二、Python Signal

在了解了Linux的信号基础之后,Python标准库中的signal包就很容易学习和理解。signal包负责在Python程序内部处理信号,典型的操作包括预设信号处理函数,暂停并等待信号,以及定时发出SIGALRM等。要注意,signal包主要是针对UNIX平台(比如Linux, MAC OS),而Windows内核中由于对信号机制的支持不充分,所以在Windows上的Python不能发挥信号系统的功能。

signal.SIGHUP # 连接挂断;
signal.SIGILL # 非法指令;
signal.SIGINT # 终止进程(ctrl+c);
signal.SIGTSTP # 暂停进程(ctrl+z);
signal.SIGKILL # 杀死进程(此信号不能被捕获或忽略);
signal.SIGQUIT # 终端退出;
signal.SIGTERM # 终止信号,软件终止信号;
signal.SIGALRM # 闹钟信号,由signal.alarm()发起;
signal.SIGCONT # 继续执行暂停进程;

2.1 进程结束信号SIGTERM和SIGKILL的区别?

SIGTERM比较友好,进程能捕捉这个信号,根据你的需要来关闭程序。在关闭程序之前,你可以结束打开的记录文件和完成正在做的任务。在某些情况下,假如进程正在进行作业而且不能中断,那么进程可以忽略这个SIGTERM信号。

对于SIGKILL信号,进程是不能忽略的。这是一个 “我不管您在做什么,立刻停止”的信号。假如你发送SIGKILL信号给进程,Linux就将进程停止在那里。

2.2 发送信号一般有两种原因

被动式:内核检测到一个系统事件,例如子进程退出会像父进程发送SIGCHLD信号,键盘按下control+c会发送SIGINT信号。

主动式:通过系统调用kill来向指定进程发送信号。

操作系统规定了进程收到信号以后的默认行为,但是,我们可以通过绑定信号处理函数来修改进程收到信号以后的行为,有两个信号SIGTOP和SIGKILL是不可更改的。

2.3 信号处理函数

signal.signal(signalnum, handler):设置信号处理的函数。

signal.alarm(time):设置发送SIGALRM信号的定时器。

os.kill:这个不属于signal模块,但其可以使用给某一进程发送信号。

signal包的核心是使用signal.signal()函数来预设(register)信号处理函数,如下所示:

singnal.signal(signalnum, handler)

signalnum为某个信号,handler为该信号的处理函数。我们在信号基础里提到,进程可以无视信号,可以采取默认操作,还可以自定义操作。当handler为signal.SIG_IGN时,信号被无视(ignore)。当handler为singal.SIG_DFL,进程采取默认操作(default)。当handler为一个函数名时,进程采取函数中定义的操作。

import signal
# Define signal handler function
def myHandler(signum, frame):
print('I received: ', signum)
# register signal.SIGTSTP's handler
signal.signal(signal.SIGTSTP, myHandler)
signal.pause()
print('End of Signal Demo')

在主程序中,我们首先使用signal.signal()函数来预设信号处理函数。然后我们执行signal.pause()来让该进程暂停以等待信号,以等待信号。当信号SIGUSR1被传递给该进程时,进程从暂停中恢复,并根据预设,执行SIGTSTP的信号处理函数myHandler()。myHandler的两个参数一个用来识别信号(signum),另一个用来获得信号发生时,进程栈的状况(stack frame)。这两个参数都是由signal.singnal()函数来传递的。

当程序运行到signal.pause()的时候,进程暂停并等待信号。此时,通过按下CTRL+Z向该进程发送SIGTSTP信号。我们可以看到,进程执行了myHandle()函数, 随后返回主程序,继续执行。(当然,也可以用$ps查询process ID, 再使用$kill来发出信号。)

(进程并不一定要使用signal.pause()暂停以等待信号,它也可以在进行工作中接受信号,比如将上面的signal.pause()改为一个需要长时间工作的循环。)

我们可以根据自己的需要更改myHandler()中的操作,以针对不同的信号实现个性化的处理。

2.3 定时发出SIGALRM信号

一个有用的函数是signal.alarm(),它被用于在一定时间之后,向进程自身发送SIGALRM信号:

import signal
# Define signal handler function
def myHandler(signum, frame):
print("Now, it's the time")
exit()
# register signal.SIGALRM's handler
signal.signal(signal.SIGALRM, myHandler)
signal.alarm(5)
while True:
print('not yet')

我们这里用了一个无限循环以便让进程持续运行。在signal.alarm()执行5秒之后,进程将向自己发出SIGALRM信号,随后,信号处理函数myHandler开始执行。

2.4 发送信号

signal包的核心是设置信号处理函数。除了signal.alarm()向自身发送信号之外,并没有其他发送信号的功能。但在os包中,有类似于Linux的kill命令的函数,分别为:

os.kill(pid, sid)
os.killpg(pgid, sid)

分别向进程和进程组(见Linux进程关系)发送信号,sid为信号所对应的整数或者singal.SIG*。

如下测试:

import os
import signal
from time import sleep
def onsignal_term(a,b):
print('收到SIGTERM信号')
# 这里是绑定信号处理函数,将SIGTERM绑定在函数onsignal_term上面;
signal.signal(signal.SIGTERM,onsignal_term)
def onsignal_usr1(a,b):
print('收到SIGUSR1信号')
# 这里是绑定信号处理函数,将SIGUSR1绑定在函数onsignal_term上面;
signal.signal(signal.SIGUSR1,onsignal_usr1)
while 1:
print('我的进程id是:',os.getpid())
sleep(10)

运行该程序,然后通过另外一个进程来发送信号。

发送消息的代码如下:

import os
import signal
# 发送信号,16175是前面那个绑定信号处理函数的pid,需要自行修改;
os.kill(16175,signal.SIGTERM)
os.kill(16175,signal.SIGUSR1)

使用信号需要特别注意的地方:

如果一个进程收到一个SIGUSR1信号,然后执行信号绑定函数,第二个SIGUSR2信号又来了,第一个信号没有被处理完毕的话,第二个信号就会丢弃。所以,尽量不要在多线程中使用信号。

实际上signal, pause,kill和alarm都是Linux应用编程中常见的C库函数,在这里,我们只不过是用Python语言来实现了一下。实际上,Python 的解释器是使用C语言来编写的,所以有此相似性也并不意外。此外,在Python 3.4中,signal包被增强,信号阻塞等功能被加入到该包中。