信号的阻塞就是让系统暂时保留信号留待以后发送。由于另外有办法让系统忽略信号,所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作。
* 当你需要修改某些全局变量时,你可以通过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, int signum):该函数把信号signum加入到信号集set中,需要注意的是这个函数只是修改了set变量本身,并不作其它操作。该函数成功操作返回0,失败返回-1,错误代码设置成EINVAL,表示signum不是有效的信号代码。
int sigdelset(sigset_t *set, int signum):该函数从信号集set中删除信号signum,其它方面和sigaddset()函数类似,不再赘述。
int sigismember(const sigset_t *set,int signum):这个函数测试信号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);
}
阅读(627) | 评论(0) | 转发(0) |