Python信号相关概念及运用
Python信号相关概念及运用
介绍
信号是软件中断。信号提供了一种处理异步事件的方法。
不存在编号为0的信号。kill函数对信号编号0有特殊的应用。POSIX.1将此信号编号值称为空信号。
很多条件可以产生信号:
- 当用户按某些按键时,引发终端产生的信号。(DELETE,Ctrl+C,Ctrl+\,Ctrl+Z)。这些键可以自定义。
- 硬件异常产生信号:除数为0、无效的内存引用等。
- 进程调用kill(2)函数可将信号发送到另一个进程或进程组。有限制:只能发送给同一用户的进程,或者发送信号的进程所有者是超级用户。
- 用户可用kill(1)命令将信号发送给其他进程。
- 当检测到某种软件条件发生,并应将其通知有关进程时也产生信号。例句SIGURG、SIGPIPE、SIGALRM。
信号是异步事件的经典实例。产生信号的事件对进程而言是随机出现的。
可以要求内核在某个信号出现时按照下列方式之一进行处理:
- 忽略此信号。大多数都可以,但有两个不能忽略,他们是SIGKILL和SIGSTOP。原因是:它们向超级用户提供了使进程终止或停止的可靠方法。 另外,有些如果忽略由硬件异常引发的异常(如除0),则进程的行为是未定义的。
- 捕捉信号。自定义信号处理函数。注意:不能捕捉SIGKILL和SIGSTOP信号。
- 执行系统默认动作。针对大多数信号的系统默认动作是终止进程。
UNIX系统信号
名字 | 说明 | 默认动作 | 细节说明 |
---|---|---|---|
SIGABRT | 异常终止(abort) | 终止+core | 调用abort函数时产生此信号 |
SIGALRM | 超时(alarm) | 终止 | 调用alarm函数设置的计时器超时,由setitimer函数设置的间隔时间超时时,产生此信号 |
SIGBUS | 硬件故障 | 终止+core | 指示一个实现定义的 硬件故障。当出现某些类型的内存故障时,实现常常产生此信号 |
SIGCANCEL | 线程库内部使用 | 忽略 | Solaris线程库内部使用的信号 |
SIGCHLD | 子进程状态改变 | 忽略 | 在一个进程终止或停止时,将SIGCHLD信号发送给其父进程。 |
SIGCONT | 使暂停进程继续 | 继续/忽略 | 此作业控制信号被发送给需要继续运行,但当前处于停止状态的进程。 |
SIGEMT | 硬件故障 | 终止+core | 指示一个实现定义的 硬件故障。(emt = emulator trap)。 |
SIGFPE | 算术异常 | 终止+core | 此信号表示算术运算异常,例如除以0,浮点溢出等。 |
SIGFREEZE | 检查点冻结 | 忽略 | 仅由Solaris定义。用于通知进程在冻结系统状态之前需要采取特定的动作。 |
SIGHUP | 连接断开 | 终止 | 如果终端接口检测到一个连接断开,则将此信号发送给与该终端相关的控制进程(会话首进程) |
SIGILL | 非法硬件指令 | 终止+core | 此信号指示进程已执行一条非法硬件指令。 |
SIGINFO | 键盘状态请求 | 忽略 | BSD信号 |
SIGINT | 终端中断符 | 终止 | 当用户按中断键(一般采用DELETE或Ctrl+C)时,终端驱动程序产生此号并送至前台进程组中的每一个进程。 |
SIGIO | 异步IO | 终止/忽略 | 指示一个异步IO事件。 |
SIGIOT | 硬件异常 | 终止+core | 指示一个实现定义的 硬件故障 (iot = input/output trap) |
SIGKILL | 终止 | 终止 | 这是两个不能被捕捉或忽略的信号之一。它向系统管理员提供了一种可以杀死任一进程的可靠方法。 |
SIGLWP | 线程库内部使用 | 忽略 | Solaris线程库内部使用 |
SIGPIPE | 写至无读进程的管道 | 终止 | 如果在写到管道时读进程已终止,则产生此信号。 |
SIGPOLL | 可轮询事件 | 终止 | 当在一个 可轮询设备 上发生 一特定事件 时产生此信号。 |
SIGPROF | 梗概时间超时(setitimer) | 终止 | 当setitimer(2)函数设置的梗概统计间隔计时器已到期时产生此信号。 |
SIGPWR | 电源失效/重启 | 终止/忽略 | 依赖于系统。主要用于具有不间断电源(UPS)的系统。如果电源失效,则UPS起作用,而且通常软件会接到通知。 |
SIGQUIT | 终端退出符 | 终止+core | 当用户在终端上按退出键(一般是Ctrl+)时,产生此信号,并送至前台进程组中的所有进程。 |
SIGSEGV | 无效的内存引用 | 终止+core | 此信号指示进程进行了一次无效的内存引用 (segv = segmentation violation) |
SIGSTKFLT | 协处理器栈故障 | 终止 | 此信号仅由Linux定义。 |
SIGSTOP | 停止 | 暂停进程 | 作业控制信号,用于停止一个进程。类似于交互停止信号(SIGTSTP),但是SIGSTOP不能被捕捉或忽略。 |
SIGSYS | 无效系统调用 | 终止+core | 该信号指示一个无效的系统调用。 |
SIGTERM | 终止 | 终止 | 这是由kill(1)命令发送的系统默认终止信号。 |
SIGTHAW | 检查点解冻 | 忽略 | 此信号仅由Solaris定义。 |
SIGTRAP | 硬件故障 | 终止+core | 指示一个实现定义的 硬件故障 。当执行断点指令时,实现常用此信号将控制转移至调试程序 |
SIGTSTP | 终端停止符 | 暂停进程 | 交互式停止指令。Ctrl+Z |
SIGTTIN | 后台读控制tty | 暂停进程 | 当一个后台进程组中进程试图读其控制终端时,终端驱动程序产生此信号。有两种情况不产生。 |
SIGTTOU | 后台写至控制tty | 暂停进程 | 当一个后台进程组中的进程试图写到其控制终端时。一个进程可以允许后台进程写控制终端,不允许时也有两种情况不产生此信号 |
SIGURG | 紧急情况(套接字) | 忽略 | 此信号通知进程已经发生了一个紧急情况。在网络连接上接收到带外的数据时,可选择产生此信号。 |
SIGUSR1 | 用户定义的信号 | 终止 | 用户定义的信号,可用于应用程序。 |
SIGUSR2 | 用户定义的信号 | 终止 | 用户定义的信号,可用于应用程序。 |
SIGVTALRM | 虚拟时间闹钟(setitimer) | 终止 | 当一个由setitimer(2)函数设置的虚拟间隔时间到期时产生此信号。 |
SIGWAITING | 线程库内部使用 | 忽略 | 此信号由Solaris线程库内部使用 |
SIGWINCH | 终端窗口大小改变 | 忽略 | 内核维持与每个终端或伪终端相关联的窗口大小。进程可以用ioctl函数得到或设置窗口的大小。 |
SIGXCPU | 超过CPU限制(setrlimit) | 终止+core/忽略 | 如果进程超过了其软CPU时间限制,则产生SIGXCPU信号。 |
SIGXFSZ | 超过文件长度限制(setrlimit) | 终止+core/忽略 | 如果进程超过了其软文件长度限制,则产生此限制。 |
SIGXRES | 超过资源限制 | 忽略 | 此信号仅由Solaris定义。 |
在下列条件下并不会产生core文件:
- 进程是设置用户ID的,而且当前用户并非程序文件的所有者。
- 进程是设置组ID的,而且当前用户并非该程序的组所有者。
- 用户没有写当前工作目录的权限。
- 该文件已存在,而且用户对该文件设有写权限。
- 该文件太大。
signal包
signal包负责在Python程序内部处理信号,典型的操作包括预设信号处理函数,暂停并等待信号,以及定时发出SIGALRM等。要注意,signal包主要是针对UNIX平台(比如Linux, MAC OS),而Windows内核中由于对信号机制的支持不充分,所以在Windows上的Python不能发挥信号系统的功能。
捕捉信号
signal包的核心是使用signal.signal()函数来预设(register)信号处理函数,如下所示:
singnal.signal(signalnum, handler)
signalnum为某个信号,handler为该信号的处理函数。我们在信号基础里提到,进程可以无视信号,可以采取默认操作,还可以自定义操作。当handler为signal.SIG_IGN时,信号被无视(ignore)。当handler为singal.SIG_DFL,进程采取默认操作(default)。当handler为一个函数名时,进程采取函数中定义的操作。
示例代码s1.py:
#coding: utf-8
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')
执行过程1:
执行 python s1.py
键入Ctrl+Z
输出结果:
Z(‘I received: ‘, 18)
End of Signal Demo
执行过程2:
执行 python s1.py
键入Ctrl+C
输出结果:
CTraceback (most recent call last):
File “s1.py”, line 9, in
signal.pause()
KeyboardInterrupt
因为Ctrl+Z产生的SIGTSTP由自定义函数处理了。打印完返回原来代码执行处,继续打印’End of Signal Demo‘后程序退出。
而Ctrl+C产生的SIGINT信号没有设置捕捉函数,按系统默认操作执行,终止程序运行。
发送信号
signal.alarm(),它被用于在一定时间之后,向进程自身发送SIGALRM信号:
import signal
import time
# Define signal handler function
def myHandler(signum, frame):
print("Now, it’s the time ", time.ctime())
exit()
# register signal.SIGALRM’s handler
signal.signal(signal.SIGALRM, myHandler)
signal.alarm(1)
while True:
print('not yet')
输出结果:
not yet
not yet
…
not yet
Now, it’s the time Thu Feb 9 11:49:05 2017
os包的kill发送信号
signal只有alarm可以发送SIGALRM信号。其它信号需要借助os包。
os.kill(pid, sid) #向进程pid发送信号sid
os.killpg(pgid, sid)#向进程组pgid发送信号sid
其他注意事项
进程捕捉到信号并对其进行处理时,进程正在执行的指令会被临时中断,转而去处理该信号处理程序中的指令,返回继续处理之前被中断的指令。
但是之前被中断的指令可能是malloc,getpwnam等,而信号处理函数也可能调用malloc,getpwnam,结果导致继续执行之前的malloc,getpwnam出错。malloc\getpwnam就是不可重入函数。
相对应的就是可重入函数。
Single UNIX Specification说明了保证可重入函数。一共有117个。
不可重入的原因有:
- 已知它们使用静态数据结构
- 它们调用malloc或free
- 它们是标准I/O函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。
应当了解,即使使用可重入函数,但是由于每个线程只有一个errno变量,所以信号处理程序可能会修改其原先值。
因此,作为一个通用的规则,当在信号处理程序中调用可重入函数时,应当在其前保存,在其后恢复errno。
若在信号处理程序中调用一个不可重入函数,则其结果是不可预见的。
#coding: utf-8
import signal
# Define signal handler function
i = 0
def myHandler(signum, frame):
global i;
i += 1;
print('I received: ', signum)
# register signal.SIGTSTP’s handler
signal.signal(signal.SIGTSTP, myHandler)
signal.pause()
i += 1;
print('End of Signal Demo, i=',i)
输出结果:
python s1.py
Z(‘I received: ‘, 18)
(‘End of Signal Demo, i=’, 2)
这个例子比较简单。但是在更复杂的环境下,i被信号捕捉函数修改过并不是显而易见的。