Back to home

有关信号的那些大大小小的坑

初次发表于2014/12/16
最近几个月被几个信号有关的问题折腾的死去活来,期间有麻烦到四哥指点迷津,问题也都解决了。接着这篇文章http://bbs.intra.nsfocus.com/forums/thread/79573.aspx,狗尾续貂一篇,也算是把那些大大小小的坑填一下。
1. 打不死的小强
现象:通过pythonmultiprocessing.Process创建出来的进程,给它发送SIGTERM信号,始终干不掉它。而SIGKILL信号可以杀掉进程,但是由于可能需要在进程退出的时候还要做一些善后事宜,所以就不能使用SIGKILL。进程本身也没添加任何SIGTERM信号的信号处理函数。
分析:用gdb看到进程阻塞在read操作上,并没有啥特殊之处。Strace上阵,同时给它发送个SIGTERM信号,
read(25, 0xb14cfd0c, 4) = ? ERESTARTSYS (To be restarted)
--- SIGTERM (Terminated) @ 0 (0) ---
从这个地方看,信号应该是收到了,不过被某个信号处理函数按非默认的方式和谐掉了。这是谁干的呢?
之前并不知道信号bit掩码这个东东,幸得四哥指点,终于真相大白。
cat /proc/25530/status | grep -i '^Sig'
SigQ: 0/7977
SigPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000001005007
SigCgt: 0000000180000a00
其中:
SigQ number of signals queued/max. number for queue
SigPnd bitmap of pending signals for the thread
ShdPnd bitmap of shared pending signals for the process
SigBlk bitmap of blocked signals
SigIgn bitmap of ignored signals
SigCgt bitmap of catched signals
把被忽略的信号的bit掩码1005007换算成二进制:1000000000101000000000111,每个信号对应一个bit,从右至左第15位也就是SIGTERM信号对应的那位被置位了。也就是说有信号处理函数把SIGTERM信号设置为忽略了。
跟《从中断到信号——段错误是如何产生的》一文中碰到的问题类似,某个第三方库可能在你不知情的情况下屏蔽了某个信号,这就是我用SIGTERM无论如何也弄不死那个进程的原因了。
解决:
解决方法也很简单,在进程启动之后(主要是第三方库加载完成之后),重新将SIGTERM的处理函数设置为默认,及终止进程即可。
2. 无疾而终
现象:
当主进程通过信号(SIGUSR1SIGUSR2)跟子进程进行通信时,子进程往往会无疾而终。按道理我们对SIGUSR1SIGUSR2均设置了我们的信号处理函数,且信号处理函数被正确地执行了。但是信号处理函数返回之后,该进程就终止退出了。
分析:
strace上阵跟踪进程,发现在收到信号之前,进程一直阻塞在piperead操作上。当信号到来时,read操作会被中断转而执行信号处理函数,之后会抛出一个IO EINTR异常,正是这个异常导致进程无疾而终的。
【转载】

慢系统调用(slow system call):此术语适用于那些可能永远阻塞的系统调用。永远阻塞的系统调用是指调用有可能永远无法返回,多数网络支持函数都属于这一类。如:若没有客户连接到服务器上,那么服务器的accept调用就没有返回的保证。
EINTR错误的产生:当阻塞于某个慢系统调用的一个进程捕获某个信号且相应信号处理函数返回时,该系统调用可能返回一个EINTR错误。例如:在socket服务器端,设置了信号捕获机制,有子进程,当在父进程阻塞于慢系统调用时由父进程捕获到了一个有效信号时,内核会致使accept返回一个EINTR错误(被中断的系统调用)
当碰到EINTR错误的时候,可以采取有一些可以重启的系统调用要进行重启,而对于有一些系统调用是不能够重启的。例如:acceptreadwriteselect、和open之类的函数来说,是可以进行重启的。不过对于套接字编程中的connect函数我们是不能重启的,若connect函数返回一个EINTR错误的时候,我们不能再次调用它,否则将立即返回一个错误。针对connect不能重启的处理方法是,必须调用select来等待连接完成。
解决:
被中断的read操作属于可重启的系统调用,因而捕获到INTER异常时,直接忽略,继续read即可。