http://huchh.com/2015/08/30/linux-%E4%BF%A1%E5%8F%B7%E6%9F%A5%E9%98%85/
查阅
最好的查阅文档都在linux自带的manual里。
查阅信号数字对应的具体信号: kill -l
查阅信号的定义: man 7 signal
SIGTTIN和SIGTTOUT
根据手册里提到的,SIGTTIN是后台进程想要从终端读取的时候触发的,SIGTTOU是后台进程想要写完终端的时候触发的。他们的默认动作都是暂停进程。
但是实际在linux上做测试的时候,可以发现
- 后台进程往终端写不会触发SIGTTOU,并且可以顺利的在终端上输出。
- 后台进程从终端读会收到SIGTTIN,并且导致程序产生段错误,崩溃。
信号掩码和信号处理函数的继承
根据手册的记载,信号处理函数的继承有以下规则:
- 信号处理函数是进程属性,所以进程里的每个线程的信号处理函数是相同的
- 通过fork创建的子进程会继承父进程的信号处理函数
- execve 后设置为处理的信号处理函数会被重置为默认函数,设置为忽略的信号保持不变。意思是如果父进程里信号设置处理为SIG_IGN,那么等到子进程被exec了,这个信号的处理还是被忽略,不会重置为默认函数。
可以通过以下小程序验证:
test.c --> test #include <stdlib.h> typedef void (*sighandler_t)(int); static sighandler_t old_int_handler; static sighandler_t old_handlers[SIGSYS + 1]; void sig_handler(int signo) { printf("receive signo %d\n",signo); old_handlers[signo](signo); } int main(int argc, char **argv) { old_handlers[SIGINT] = signal(SIGINT, SIG_IGN); old_handlers[SIGTERM] = signal(SIGTERM, sig_handler); int ret; ret = fork(); if (ret == 0) { //child execlp("/tmp/test2", "/tmp/test2",(char*)NULL); }else if (ret > 0) { //parent while(1) { sleep(1); } }else{ perror(""); abort(); } } ================================================ test2.c --> test2 #include <stdio.h> int main(int argc, char **argv) { while(1) { sleep(1); } }
test换成test2后,SIGINT的处理方式还是忽略,SIGTERM被重置为默认的方式。
信号掩码有以下规则:
- 每个线程可以有自己信号掩码
- fork出来的子进程会继承父进程的信号掩码,exec后信号掩码保持不变
- 针对进程发送的信号,会被任意的没有屏蔽该信号的线程接收,注意只有一个线程会随机收到。
- fork之后子进程里pending的信号集初始化为空,exec会保持pending信号集
此处有一个地方需要特别注意,手册里说fork之后子进程继承了父进程的信号掩码,但是如果父进程是多线程程序呢,要知道多线程程序里每个线程都有自己的信号掩码,此时子进程继承了所有线程的信号掩码的集合吗?
于是我写了下面的小程序验证了一下:
#include <stdio.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> #include <pthread.h> typedef void (*sighandler_t)(int); static void *thread1(void *arg) { sigset_t set; printf("in thread1\n"); sigemptyset(&set); sigaddset(&set, SIGTERM); pthread_sigmask(SIG_BLOCK, &set, NULL); while(1) { sleep(1); } } static void sigset_print(sigset_t *set) { int i; for (i = 1; i <= SIGSYS; i++) { if (sigismember(set, i)) { printf("signal %d is in set\n",i); } } } int main(int argc, char **argv) { int ret; sigset_t set; pthread_t pid; pthread_create(&pid, NULL, thread1, NULL); sleep(1); sigemptyset(&set); sigaddset(&set, SIGINT); pthread_sigmask(SIG_BLOCK, &set, NULL); ret = fork(); if (ret == 0) { //child pthread_sigmask(SIG_BLOCK, NULL, &set); sigset_print(&set); while(1) { sleep(1); } }else if (ret > 0) { //parent while(1) { sleep(1); } }else{ perror(""); abort(); } }
结果证明,只有在主线程里设置的掩码才被子进程继承了。这里面的原因在于linux里的fork只是复制了调用fork()的那个线程,因此在子进程里只有父进程的主线程被拷贝了,当然信号掩码就是父进程的主线程的信号掩码的复制了。再次验证证明,如果是在thread1里调用fork,那么子进程的信号掩码就会是thread1的拷贝了。
最后不得不感慨一下,fork和exec之后信号掩码,信号处理函数,当前信号屏蔽集这些的变化实在是不好记忆,过一段时间就混乱了。。。
多线程下的信号处理
多线程的时候处理信号,推荐的做法应该是大部分线程屏蔽需要处理的信号,然后专门留下一个线程使用sigwait这样的函数等待信号的发生并处理信号。这样将信号的处理从异步变为同步,去除了原本异步时候可重入函数的问题。
范例可以参看 man pthread_sigmask里提供的例子。
实时信号
实时信号相对于标准信号,哪些是标准信号,从kill -l 里看到的SIGRTMIN之前的信号都是标准信号,标准信号都被定义好了,每个信号代表特殊用途,但是实时信号的用途是应用程序决定的,没有被事先规定好哪个信号应该用于什么目的。
实时信号有几个优势:
- 多个相同的实时信号可以排队,相对于标准信号,如果有多个SIGINT被pending,等到取消阻塞,那么只有一个SIGINT会被发送。
- 实时信号可以按序发送,多个相同的实时信号会按他们发送的顺序被处理,如果有多个不同号码的实时信号,那么最低号码的实时信号会先处理。相比之下,标准信号并没有规定信号处理的顺序。
没有见过实时信号的应用,不知道实时信号在什么情境下适合使用?