linux之信号阻塞

来源:互联网 发布:linux移动文件夹覆盖 编辑:程序博客网 时间:2024/05/22 11:44

我的上一篇博客介绍了信号产生的原因,可以参考
http://blog.csdn.net/qq_36221862/article/details/70167847

下面我将简单介绍信号的阻塞过程,先了解几个概念:

实际执⾏行信号的处理动作称为信号递 达(Delivery)
信号从产⽣生到递达之间的状态,称为信号未决(Pending)。

进程可以选择阻塞 (Block )某个信号。
被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞, 才 执行递达的动作。

注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

信号在内核中的表示:
这里写图片描述

信号的阻塞就是让系统暂时保留信号待以后发送。由于另外有办法让系统忽略信号,所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作。

当需要修改某些全局变量时,可以通过sigprocmask()函数阻塞处理函数中也使用该变量的信号。

在某些信号处理函数中,为了防止同类信号的到来,可以使用sigaction()函数的sa_mask阻塞特定的信号。

阻塞信号的作用

使用函数sigprocmask()阻塞信号的传递,只是延迟信号的到达。信号会在解除阻塞后继续传递。这种情况往往需要在信号程序和其它程序共享全局变量时,如果全局变量的类型不是sig_atomic_t类型,当一部分程序恰好读、写到变量过程中,产生某个信号,而信号程序里会改变该变量,那么就会产生混乱。为了避免这种混乱,提供程序的可靠性,你必须在操作这类变量前阻塞信号,操作完成后恢复信号的传递。

信号集:
所有的信号阻塞函数都使用称作信号集的数据结构来表明受到影响的信号。
每一个操作都包括两个阶段:
(1)创建信号集
(2)传递信号集给特定的库函数

下面说明信号集和相关的数据类型:

sigset_t:这个数据类型用来代表信号的集合,有两种方法对它进行初始化。
一种是通过函数sigemptyset()使之不包含任何信号,然后用sigaddset()函数加入需要的信号。
另一种方法是通过函数sigfillset()使之包含所以信号,然后通过sigdelset()函数删除我们不需要的信号。注意,千万不用试图通过手工方式直接操作这种类型变量,否则会带来严重的错误。

下面介绍相关的函数:

#include <signal.h> int sigemptyset(sigset_t *set);
//初始化信号集set使之不包含任何信号,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号,这个函数总是返回0。 int sigfillset(sigset_t *set);

//初始化信号集set使之包含所有的信号,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号,这个函数也是总返回0。int sigaddset(sigset_t *set, int signo);//该函数把信号signum加入到信号集set中,需要注意的是这个函数只是修改了set变量本身,并不作其它操作。该函数成功操作返回0,失败返回-1,错误代码设置成EINVAL,表示signum不是有效的信号代码。//在使用sigset_t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态int sigdelset(sigset_t *set, int signo);
 //该函数从信号集set中删除信号signum,int sigismember(const sigset_t *set, int signo); //这个函数测试信号signum是否包含在信号集合set中,如果包含返回1,不包含返回0,出错返回-1。错误代码也只有一个EINVAL,表示signum不是有效的信号代码。

进程的信号掩码:

我们称正在阻塞的信号的集合为信号掩码(signal mask)。

每个进程都有自己的信号掩码,创建子进程时子进程将继承父进程的信号掩码。我们可以通过修改当前的信号掩码来改变信号的阻塞情况。

函数原型:

#include <signal.h>int sigprocmask(int how, const sigset_t *set, sigset_t *oset)//函数用来检查和改变调用进程的信号掩码,

返回值:若成功则为0,若出错则为-1 ,失败时错误代码只可能是EINVAL,表示参数how不合法。

如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。
如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset⾥里,然后根据set和how参数更改信号屏蔽字。

其中的how参数指出信号掩码改变的方式,必须是下面的值之一:
SIG_BLOCK,阻塞set中包含的信号。意思是说把set中的信号加到当前的信号掩码中去,新的信号掩码是set和旧信号掩码的并集。
SIG_UNBLOCK,解除set中信号的阻塞,从当前信号掩码中去除set中的信号。
SIG_SETMASK,设置信号掩码,既按照set中的信号重新设置信号掩码。

不能阻塞SIGKILL和SIGSTOP等信号,但是当set参数包含这些信号时sigprocmask()不返回错误,只是忽略它们。另外,阻塞SIGFPE这样的信号可能导致不可挽回的结果,因为这些信号是由程序错误产生的,忽略它们只能导致程序无法执行而被终止。

如果调用sigprocmask()解除了对当前若干个未决信号的阻塞,则在sigprocmask()返回前,至少将其中 一个信号递达。

信号递达:

#include <signal.h> int sigpending(sigset_t *set);

sigpending读取当前进程的未决信号集,通过set参数传出。
返回值:调用成功则返回0,出错则返回 -1

源代码:

#include <stdio.h>#include <signal.h>#include <unistd.h>void handler(){     printf("diver %d begin execu handler\n");}void show_pending(sigset_t* pending){     int i=1;    for(i=1; i<=31; i++)    {         if(sigismember(pending, i))        //判断指定信号是否在目标集合中        {             printf("1");        }        else        {            printf("2");        }    }    printf("\n");}int main(){     sigset_t block;    //定义信号集对象    sigset_t oldblock;    sigset_t pending;    signal(2, handler);    sigemptyset(&block);   //初始化信号集对象    sigemptyset(&oldblock);    sigemptyset(&pending);    sigaddset(&block, 2);  //CTRL+C    sigprocmask(SIG_SETMASK, &block, &oldblock);    //设置阻塞信号集,阻塞二号信号    int count = 0;    while(1)    {         sigpending(&pending);// 获取未决信号集        show_pending(&pending);        sleep(1);        count++;    }    if(count == 10)    {         sigprocmask(SIG_SETMASK, &block, NULL);        //解除了对当前未决信号的阻塞    }    return 0;}

运行结果:这里写图片描述

程序运行时,每秒钟把各信号的未决状态打印一遍,由于我们阻塞了SIGINT信号,按Ctrl-C将会 使SIGINT信号处于未决状态,按Ctrl-Z仍然可以终止程序.

1 0
原创粉丝点击