Linux的信号以及Python处理
初次接触信号这个概念实在gpiozero库文档中的这么一个专题,How do I keep my script running?
,我最最开始是在button.when_pressed = hello
用一个while True
的无限循环保持脚本运行,后来发现有个个内置库的signal.pause()
函数可以保持脚本运行,直到按下Ctrl+C程序才会退出,我当时仅仅认识到这个地步,没往下深究,直到后来一次mqtt没加loop循环之后自动断开,我后来就在想我用pause
还是loop_forever
呢? 接下来我就慢慢揭开了信号的序幕,也算当补了下操作系统的一些知识。
1. 信号基础
Linux信号是一种较高层的软件形式的异常,它允许进程和内核中断其他进程。
一个信号就是一条小消息,它通知进程系统中发生了一个某种类型的事件,Linux系统上支持30种不同类型的信号,每种信号类型都对应某种系统事件,红笔标注为常见信号:
可以用
man 7 signal
查看signal信息
如果觉得上面专业的术语较为枯燥,可以查看Vamei大神(看了这篇文章才知道Vamei博主因为抑郁症去世,R.I.P.)的Linux信号基础以及之前几篇关于进程的文章,通熟易懂
2. 信号发送
2.1 用/bin/kill
发送信号
我之前一直用kill -9 15213
强制杀掉某个pid进程,对这个数字9一直没啥反应,其实就是信号9,也就是SIGKILL,等同于kill -SIGTSTP 15213
2.2 从键盘发送信号
键盘快捷键我已经在上面图中用红笔写出来,不展开
2.3 用函数发送信号
Python的os.kill()
函数: os.kill(os.getpid(), signal.SIGUSR1)
2.4 用alarm函数向它自己发送信号
signal.alarm(n)
函数会安排内核在n秒后发送一个SIGALRM信号给调用进程。如果n是0,那么不会调度安排新的alarm
3. 接收以及处理信号
当进程决定执行信号的时候,有下面几种可能:
- 无视(ignore)信号,信号被清除,进程本身不采取任何特殊的操作
- 默认(default)操作。每个信号对应有一定的默认操作。比如上面SIGCONT用于继续进程。
- 自定义操作。也叫做获取 (catch) 信号。执行进程中预设的对应于该信号的操作。
进程会采取哪种操作,要根据该进程的程序设计。特别是获取信号的情况,程序往往会设置一些比较长而复杂的操作(通常将这些操作放到一个函数中)。
3.1 Python signal函数信号处理
signal包定义了各个信号名及其对应的整数,核心是使用signal.signal()函数来预设(register)信号处理函数,singnal.signal(signalnum, handler)
中signalnum为某个信号,handler为该信号的处理函数。我们在信号基础里提到,进程可以无视信号,可以采取默认操作,还可以自定义操作。当handler为signal.SIG_IGN
时,信号被无视(ignore)。当handler为singal.SIG_DFL
,进程采取默认操作(default)。当handler为一个函数名时,进程采取函数中定义的操作。
1 |
|
信号处理程序和主程序以及其他信号处理程序并发地进行,如果处理程序和主程序并发的访问同样的全局数据结构,那么结果可能就是不可预知的,而且经常是致命的!
在主程序中,我们首先使用signal.signal()
函数来预设信号处理函数。然后我们执行signal.pause()
来让该进程暂停以等待信号,以等待信号。当信号SIGUSR1被传递给该进程时,进程从暂停中恢复,并根据预设,执行SIGTSTP的信号处理函数myHandler()
。myHandler
的两个参数一个用来识别信号(signum),另一个用来获得信号发生时,进程栈的状况(stack frame)。这两个参数都是由signal.singnal()
函数来传递的。
上面的程序可以保存在一个文件中(比如test.py
)。我们使用如下方法运行:$python3 test.py
以便让进程运行。当程序运行到signal.pause()
的时候,进程暂停并等待信号。此时,通过按下CTRL+Z向该进程发送SIGTSTP信号。我们可以看到,进程执行了myHandle()
函数, 随后返回主程序,继续执行。(当然,也可以用$ps
查询process ID, 再使用$kill
来发出信号。)
(进程并不一定要使用signal.pause()
暂停以等待信号,它也可以在进行工作中接受信号,比如将上面的signal.pause()
改为一个需要长时间工作的循环。)
我们可以根据自己的需要更改myHandler()
中的操作,以针对不同的信号实现个性化的处理。
3.2 Python alarm函数自发自收信号
再来看个官网的例子,它使用alarm()
函数来限制等待打开文件所花费的时间;这很有用,因为如果该文件作用于可能无法打开的串行设备,这通常会导致os.open()
然后无限期挂起。解决的办法是在打开文件之前设置5秒警报。如果操作花费的时间太长,则会发送警报信号,并且处理程序将引发异常。
1 |
|
4. 说说在C语言中的实现
在深入理解计算机系统书中,讲解Linux信号用了shell以及C来进行演示,这是必须的,理解这些底层机制用C实在是太契合了,但是回过头来看,Python实现这些功能时是完全照着C来的,无论是函数名参数名,只要底层原理相通,编程语言的函数实现也大体一致,如果想看更多关于C的演示,可以看Linux 信号机制,很棒的教程!
但是,Python3的文档中明确指出,handler并不是运行在底层的C handler中的,所以不要希望捕捉同步错误例如SIGFPE与SIGSEGV。
同时新增了几个功能,大概用于阻塞signal这种,不过我还不太会用:signal.pthread_kill(thread_id, signum)
signal.pthread_sigmask(how, mask)
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!