linux 信号阻塞和信号未决

信号的“未决”是一种状态,指的是从信号的产生到信号被处理前的这一段时间;
信号的“阻塞”是一个开关动作,指的是阻止信号被处理,但不是阻止信 号产生。

信号的阻塞就是让系统暂时保留信号留待以后发送。由于另外有办法让系统忽略信号,所以一般情况下信号的阻塞只是暂时的,只是为了 防止信号打断敏感的操作。
* 当你需要修改某些全局变量时,你可以通过sigprocmask()函数阻塞处理函数中也使用该变量的信号。
* 在某些信号处理函数中,为了阻止同类信号的到来,可以使用sigaction()函数的sa_mask阻塞特定的信号。
11.7.1 阻塞信号的作用
使用函数sigprocmask()阻塞信号的传递,只是延迟信号的到达。信号会在解除阻塞后继续传递。这种情况往往需要在信 号程序和其它程序共享全局变量时,如果全局变量的类型不是sig_atomic_t类型,当一部分程序恰好读、写到变量的一半发生信号,而信号程序里会改 变该信号,那么就会产生混乱。为了避免这种混乱,提供程序的可靠性,你必须在操作这类变量前阻塞信号,操作完成后恢复信号的传递。
信号阻塞也 用来处理必须保证连续操作的完整性方面。比如,你需要检测一个标志(可以是sig_atomic_t类型),该标志在信号程序中设置,当标志没有设置时可 以执行某个操作。假如恰好在检测标志后发生信号,那么信号返回后,程序也会执行这个操作,即使已经设置了标志。这显然会引起程序的不稳定。最好的方法就是 在检测标志到执行操作之间阻塞信号的发生。
11.7.2 信号集
所有的信号阻塞函数都使用称作信号集的数据结构来表明受到影响的信号。每一个操作都包括两个阶段:创建信号集,传递信号集给特定的库函数。下面说明信号集 和相关的数据类型:
sigset_t:这个数据类型用来代表信号的集合,有两种方法对它进行初始化。一种是通过函数 sigemptyset()使之不包含任何信号,然后用sigaddset()函数加入需要的信号。另一种方法是通过函数sigfillset()使之包 含所以信号,然后通过sigdelset()函数删除我们不需要的信号。注意,千万不用试图通过手工方式直接操作这种类型变量,否则会带来严重的错误。下 面介绍相关的函数。
int sigemptyset(sigset_t *set):初始化信号集set使之不包含任何信号,这个函数总是返回0。
int sigfillset(sigset_t *set):初始化信号集set使之包含所有的信号,这个函数也是总返回0。
int sigaddset(sigset_t *set, intsignum):该函数把信号signum加入到信号集set中,需要注意的是这个函数只是修改了set变量本身,并不作其它操作。该函数成功操作 返回0,失败返回-1,错误代码设置成EINVAL,表示signum不是有效的信号代码。
int sigdelset(sigset_t *set, int signum):该函数从信号集set中删除信号signum,其它方面和sigaddset()函数类似,不再赘述。
int sigismember(const sigset_t *set,intsignum):这个函数测试信号signum是否包含在信号集合set中,如果包含返回1,不包含返回0,出错返回-1。错误代码也只 有一个EINVAL,表示signum不是有效的信号代码。
11.7.3 进程的信号掩码
我们称正在阻塞的信号的集合为信号掩码(signal mask)。每个进程都有自己的信号掩码,创建子进程时子进程将继承父进程的信号掩码。我们可以通过修改当前的信号掩码来改变信号的阻塞情况。
int sigprocmask(int how, const sigset_t *set,sigset_t *oldset),该函数用来检查和改变调用进程的信号掩码,其中的how参数指出信号掩码改变的方式,必须是下面的值之一:
SIG_BLOCK,阻塞set中包含的信号。意思是说把set中的信号加到当前的信号掩码中去,新的信号掩码是set和旧信号掩码的并集。
SIG_UNBLOCK,解除set中信号的阻塞,从当前信号掩码中去除set中的信号。
SIG_SETMASK,设置信号掩码,既按照set中的信号重新设置信号掩码。
最后一个参数是进程原来的信号集。如果你只需要改变信号的阻塞情况而不需要关心原来的值,可以传递NULL指针给函数。如果你希望什么也不改变,只是想获 得当前信号掩码的信息,那么把set设置成NULL,old中返回当前的设置。
sigprocmask()函数成功返回0,失败返回-1。失败时错误代码只可能是EINVAL,表示参数how不合法。
不能阻塞 SIGKILL和SIGSTOP等信号,但是当set参数包含这些信号时sigprocmask()不返回错误,只是忽略它们。另外,阻塞SIGFPE这 样的信号可能导致不可挽回的结果,因为这些信号是由程序错误产生的,忽略它们只能导致程序无法执行而被终止。
11.7.4 举例:禁止关键代码时信号到达
假定你建立信号SIGALRM的处理函数,在其中设置一个标志。主程序中检查标志并清除,使用函数sigprocmask()控制信号到达:
#include
volatile sig_atomic_t flag=0;

int
main(void)
{
sigset_t block_alarm;
... ...
sigemptyset(&block_alarm);
sigaddset(&block_alarm,SIGALRM);
while(1)
{
sigprocmask(SIG_BLOCK,&block_alarm,NULL);
if(flag)
{
... ...
flag=0;
}
sigprocmask(SIG_UNBLOCK,&block_alarm,NULL);
... ...
}
}
11.7.5 在信号句柄中阻塞信号
信号句柄执行时,如果你希望从头至尾不会被其它信号打扰,那么你必须阻塞其它信号。当一个信号句柄激活时,相同的信号自动被阻塞,但是其它信号并不会阻 塞,最可靠的方法是使用sigaction结构的sa_mask成员。例如:
#include
#include

void catch_stop();

void
install_handler(void)
{
struct sigaction setup_action;
sigset_t block_mask;
sigemptyset(&block_mask);
sigaddset(&block_mask,SIGINT);
sigaddset(&block_mask,SIGQUIT);
setup_action.sa_handler=catch_stop;
setup_action.sa_mask=block_mask;
setup_action.sa_flag=0;
sigaction(SIGINT,&setup_action,NULL);
}
这个方法比在信号函数中使用 sigprocmask()函数可靠,因为使用sigprocmask()函数起码无法避免在信号函数开始的信号不被阻塞的间隙。使用这个机制你不能从当 前信号掩码中删除信号,但是,在信号函数当中,你可以使用sigprocmask()阻塞信号。在任何情况下,信号句柄返回时,系统重新装入进入信号句柄 之前的信号掩码。当信号句柄一返回,信号句柄执行时被阻塞的信号就会立即到达,甚至在返回被中断的代码前就要进入新的信号处理句柄。
11.7.6 查找阻塞的信号
你能够使用函数sigpending()来查找系统中何种信号有正在被阻塞的信号。
int sigpending(sigset_t *set),该函数把存在阻塞信号的信号类型放置到set中,可以使用sigismenber()函数判断某种信号是否是set的成员。该函数成功返回 0,失败返回-1。
下面举例说明:
#include
#include

sigset_t base_mask,wait_mask;

sigemptyset(&base_mask);
sigaddset(&base_mask,SIGINT);
sigaddset(&base_mask,SIGTSTP);
sigprocmask(SIG_SETMASK,&base_mask,NULL);
... ...
sigpending(&waiting_mask);
if(sigismember(&waiting_mask,SIGINT))
{
//do something, if user try killing the process
}
else if(sigismember(&waiting_mask,SIGTSTP))
{
//user tring to stop process
}
对于同一种信号,如果有被阻塞的信号存在,那么其它到来的信号就会被丢弃,而不是被阻塞。
11.7.7 信号阻塞的代替方法
我们可以采用在信号处理程序中读取其它程序进入关键代码前设置的标志来判断是否进行处理,如果不能进行处理,则在一个公共变量中设置不能进行处理的信号的 值,关键代码执行完成后再重新发送该信号,请看下面的例子:
volatile sig_atomic_t signal_pending;
volatile sig_atomic_t defer_signal;

void
handler(int signum)
{
if(defer_signal)
signal_pending=signum;
else
...
}

...
void
update_number(int frob)
{
defer_signal++;
...
defer_signal--;
if(defer_signal==0&&signal_pending!=0)
raise(signal_pending);
}

信号是unix处理异步事件的经典方法。产生信号的方法一般有:
        用户按中断键、硬件异常信号、软件异常信号、用户的kill命令等。系统也可以有多种方式处理这些异步 事件,比如:忽略信号、捕捉信号或者执行默认动作。
        大多数unix程序都是用信core文件来检查进程终止时候的状态的。 core文件就是对于该文件的进程存储映像进行复制。

一定要记住这两种情况:
   1)当进程启动的时候,所有信号的状态就是系统默认或者忽略。除非调用exec动作。
     因为exec动作,将原先设置为要捕捉的信号为默认动作,道理很简单,就是exec要用新的进程地址空间覆盖旧的,自然原先的信号就已经被屏蔽了。
   2)开启一个子进程的时候,子进程继承父进程的信号处理方式。因为子进程一开始启动就复制了父进程的存储映像,信号捕捉函数地址在父子进程中都是有意义 的。

signal的信号处理方式带有好多的缺陷,比如,在产生信号和调用signal信号处理程序之间有一个时 间窗口,如果再来一个信号可能丢失。等等。

在执行慢速的系统调用的时候,进程很可能阻塞几个小时或者数天,如果进程在执行一个低速系统调用期间捕捉到 一个信号的时候,那么该系统调用就被中断不在继续执行。因为一个信号发生了,这就意味着发生了某种事情,所以这是唤醒被阻塞的进程的好机会。
   为了帮助应用进程不必处理被总段的系统调用,linux系统引入了某些被中断系统调用的自动重启函 数,包括:Ioctl,read,readv,write,writev,wait和waitpid。前5个只对低速设备调用才有效,而后两个只对捕捉到 进程退出信号时才有效。可重入函数主要用于多任务环境中,一个函数是可重入与否,简单的说就是否可以被中断。假如一个链表,如果被中断则会发生莫名的错误,其执行序列是不可预测的。
    信号发生的时候到递送之间的时间间隔,我们称为信号时未决的。在未决的信号期间,如果我们阻塞了某些信号,那么,即使我们产生信号,这些都会被阻塞。除非 等解除阻塞以后,系统才会处理这些信号。而且,阻塞期间多次产生的同一个信号,解除阻塞后,系统处理且只处理一次。

引用原文:
1. http://www.hadoopor.com/redirect.php?tid=1156&goto=lastpost
2. http://www.groad.net/bbs/read.php?tid-920.html

你可能感兴趣的:(linux,C语言,全局变量,信号,控制台)