信号是进程间通信的一种方式,同时它是一种异步的形式。先注册信号处理函数,当接收到信号的时候回调该函数进程处理。信号在系统中以数字的形式标识。每个信号对应着一个处理函数

信号表

来个python打印支持的信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import signal
signals_to_names = {
getattr(signal, n): n
for n in dir(signal)
if n.startswith('SIG') and '_' not in n
}
for s, name in sorted(signals_to_names.items()):
handler = signal.getsignal(s)
if handler is signal.SIG_DFL:
handler = 'SIG_DFL'
elif handler is signal.SIG_IGN:
handler = 'SIG_IGN'
print('{:<10} ({:2d}):'.format(name, s), handler)

因为平时用的少,而ubuntu上的man 7 signal的内容比较多,我只是在osx上看了一下singal的文档。它仅包含下列一些信号

序号 名称 默认动作 解释
1 SIGHUP 终止进程 让程序挂起
2 SIGINT 终止进程 打断程序的执行
3 SIGQUIT dump 程序
4 SIGILL dump 非法指令
5 SIGTRAP dump 陷阱
6 SIGABRT dump 打断程序
7 SIGEMT dump
8 SIGFPE dump 浮点数异常
9 SIGKILL 终止进程 杀死程序(不可修改)
10 SIGBUS dump 总线错误
11 SIGSEGV dump 段错误
12 SIGSYS dump 调用不存在的系统调用
13 SIGPIPE 终止进程 给一个没有读的管道写数据
14 SIGALRM 终止进程 实时计时器超时
15 SIGTERM 终止进程 软中断信号
16 SIGURG 已废弃
17 SIGSTOP 停止进程 停止(不可修改)
18 SIGTSTP 停止进程 从键盘生成停止信号
19 SIGCONT 废弃
20 SIGCHLD 废弃
21 SIGTTIN 停止进程 后台进程尝试读取终端时触发
22 SIGTOU 停止进程 后台进程尝试写入终端时触发
23 SIGIO 废弃
24 SIGXCPU 终止进程 进程的cpu时间片到期(setrlimit)
25 SIGXFSZ 终止进程 文件大小超过限制(setrlimit)
26 SIGVTLRM 终止进程 虚拟时钟超时(setitimer)
27 SIGPROF 终止进程 profil时钟超时(setitimer)
28 SIGWINCH 废弃
29 SIGINFO 废弃
30 SIGUSR1 终止进程 用户自定义信号1
31 SIGUSR2 终止进程 用户自定义信号2

信号的产生

信号可以产生自软件和硬件,硬件来说,我们最常用的Ctrl+C来终止一个程序的执行,软件层面就是经常使用kill -9 pid来强制终止一个程序。观察上表可以发现还有很多类似非法指令、陷入、段错误等等都是由操作系统触发反馈给应用程序。还有就是我们自己编写程序进行系统调用触发。比如python中常用的signal.alarm

信号的处理

观察信号表可以发现虽然有31个信号。但是默认动作只有终止进程、dump、停止进程三种。这算成了多对一的关系,多个信号对应一个处理结果。想想这其实是一种人为的约定。操作系统遇到一些异常的时候本可以直接卡擦掉进程。但是还是先知会你一声,万一程序作者觉得遇到这种情况还能再抢救一下那就不异常了。所以规定这么多信号id。还是为了方便编程的时候对信号进行分类处理。而且不仅仅约定俗成。后面还有两个自定义信号让你自己进行定义

信号处理基本有三种套路,默认、忽略、自定义。如果你觉得遇到异常你还可以再抢救一下那就自定义或者忽略。但是假如忽略掉所有的信号。管理人员可能就无法退出你的程序了。因此SIGKILL和SIGSTOP强制不允许被忽略或者自定义。所以kill -9就是一个大杀器

但是很多人并不被推荐用kill -9,而更多的人推荐用kill -15。原因是强制虽然强。但是这样编程人员别无选择。有的程序在意外退出前需要做一些清理工作,比如删掉临时文件啥的。当然,渣水平的作者怎么可能会去自定义处理SIGTERM信号┑( ̄Д  ̄)┍

应用

比如tornado中可以使用信号机制记录响应时间超过阈值的栈帧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import tornado.httpserver
import tornado.ioloop
import tornado.web
import traceback
import time
def record_block(_, frame):
a = traceback.format_stack(frame)
print("".join(a))
class MainHandler(tornado.web.RequestHandler):
def get(self):
time.sleep(10)
self.write("Hello, world")
def main():
application = tornado.web.Application([
(r"/", MainHandler),
])
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8888)
ioloop = tornado.ioloop.IOLoop.current()
ioloop.set_blocking_signal_threshold(1, record_block)
ioloop.start()
if __name__ == "__main__":
main()

关于它的实现就比较简单了。先使用signal.signal(signal.SIGALRM,callback)注册信号回调函数。在ioloop主循环中每次事件完成后将实时计时器归零signal.setitimer(signal.ITIMER_REAL, 0, 0)后再重新开始计时。此处为什么不是用alarm而使用setitimer。因为前者只支持整数秒,后者支持浮点数

参考资料

signal — Asynchronous System Events