博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

linux 信号处理函数,信号掩码的继承

Posted on 2016-01-20 18:36  bw_0927  阅读(1723)  评论(1)    收藏  举报

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上做测试的时候,可以发现

  1. 后台进程往终端写不会触发SIGTTOU,并且可以顺利的在终端上输出。
  2. 后台进程从终端读会收到SIGTTIN,并且导致程序产生段错误,崩溃。

信号掩码和信号处理函数的继承

根据手册的记载,信号处理函数的继承有以下规则:

  1. 信号处理函数是进程属性,所以进程里的每个线程的信号处理函数是相同的
  2. 通过fork创建的子进程会继承父进程的信号处理函数
  3. 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被重置为默认的方式。

信号掩码有以下规则:

  1. 每个线程可以有自己信号掩码
  2. fork出来的子进程会继承父进程的信号掩码,exec后信号掩码保持不变
  3. 针对进程发送的信号,会被任意的没有屏蔽该信号的线程接收,注意只有一个线程会随机收到。
  4. 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之前的信号都是标准信号,标准信号都被定义好了,每个信号代表特殊用途,但是实时信号的用途是应用程序决定的,没有被事先规定好哪个信号应该用于什么目的。

实时信号有几个优势:

  1. 多个相同的实时信号可以排队,相对于标准信号,如果有多个SIGINT被pending,等到取消阻塞,那么只有一个SIGINT会被发送。
  2. 实时信号可以按序发送,多个相同的实时信号会按他们发送的顺序被处理,如果有多个不同号码的实时信号,那么最低号码的实时信号会先处理。相比之下,标准信号并没有规定信号处理的顺序。

没有见过实时信号的应用,不知道实时信号在什么情境下适合使用?