1.sigsuspend()函数作用详解

一个错误示例:

参考APUE中的代码:
1)头文件:#include <signal.h>
2)一个保护临界区代码的错误实例:(sigprocmask()和pause()实现)
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
 
void handler(int sig)    //信号处理函数的实现
{
   printf("SIGINT sig");
}
int main()
{
    sigset_t new,old;
    struct sigaction act;
    act.sa_handler = handler;  //信号处理函数handler
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGINT, &act, 0);  //准备捕捉SIGINT信号
    sigemptyset(&new);
    sigaddset(&new, SIGINT);
    sigprocmask(SIG_BLOCK, &new, &old);  //将SIGINT信号阻塞,同时保存当前信号集
    printf("Blocked");
    sigprocmask(SIG_SETMASK, &old, NULL);  //取消阻塞
    pause();
    return 0;
}
上面实例的问题是:本来期望pause()之后,来SIGINT信号,可以结束程序;可是,如果当“取消阻塞”和“pause”之间,正好来了SIGINT信号,结果程序因为pause的原因会一直挂起。。。
解决的方式,当然是sigsuspend()函数了。

一个正确示例

使用sigsuspend()的程序
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void handler(int sig)   //信号处理程序
{
   if(sig == SIGINT)
      printf("SIGINT sig");
   else if(sig == SIGQUIT)
      printf("SIGQUIT sig");
   else
      printf("SIGUSR1 sig");
}
 
int main()
{
    sigset_t new,old,wait;   //三个信号集
    struct sigaction act;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGINT, &act, 0);    //可以捕捉以下三个信号:SIGINT/SIGQUIT/SIGUSR1
    sigaction(SIGQUIT, &act, 0);
    sigaction(SIGUSR1, &act, 0);
   
    sigemptyset(&new);
    sigaddset(&new, SIGINT);  //SIGINT信号加入到new信号集中
    sigemptyset(&wait);
    sigaddset(&wait, SIGUSR1);  //SIGUSR1信号加入wait
    sigprocmask(SIG_BLOCK, &new, &old);       //将SIGINT阻塞,保存当前信号集到old中
   
    //临界区代码执行    
  
    if(sigsuspend(&wait) != -1)  //程序在此处挂起;用wait信号集替换new信号集。即:过来SIGUSR1信  号,阻塞掉,程序继续挂起;过来其他信号,例如SIGINT,则会唤醒程序。执行sigsuspend的原子操作。注意:如果“sigaddset(&wait, SIGUSR1);”这句没有,那么wait信号集是空的,则此处不会阻塞任何信号,即过来任何信号均会唤醒程序。
        printf("sigsuspend error");
    printf("After sigsuspend");
    sigprocmask(SIG_SETMASK, &old, NULL);
    return 0;
}

问题来了,为什么sigsuspend要与sigprocmask配合使用?

是因为原子操作。

继续采用APUE的示例:

sigemptyset(&new_mask);
sigemptyset(&zero_mask);      // 清空信号集zero_mask
sigaddset(&new_mask, SIGQUIT);
sigprocmask(SIG_BLOCK, &new_mask, &old_mask);   // 阻塞SIGQUIT


while( quitflag == 0 )
{
    sigsuspend(&zero_mask);   // 将信号掩码替换为空,等待SIGQUIT信号处理函数将quitflag置1
}


sigprocmask(SIG_SETMASK, &old_mask, NULL);      // 恢复信号掩码


为什么sigsuspend要与sigprocmask配合使用?
是为了原子操作。
如果之前没有调用sigprocmask()屏蔽SIGQUIT信号,那么SIGQUIT信号随时都能发生,
假定恰恰在判断quitflag == 0之后,信号发生,调用信号处理程序,quitflag = 1,从信号处理程序返回后,开始调用sigsuspend()..
如果此后没有第二个SIGQUIT信号,那么程序将一直阻塞在sigsuspend(),虽然此时quitflag = 1

调用sigprocmask()屏蔽SIGQUIT信号之后,即使信号发生,也将延迟递交,直到sigsuspend()解除信号屏蔽。


sigsuspend的原子操作是:
(1)设置新的mask阻塞当前进程(上面是用wait替换new,即阻塞SIGUSR1信号)
(2)收到SIGUSR1信号,阻塞,程序继续挂起;收到其他信号,恢复原先的mask(即包含SIGINT信号的)。
(3)调用该进程设置的信号处理函数(程序中如果先来SIGUSR1信号,然后过来SIGINT信号,则信号处理函数会调用两次,打印不同的内容。第一次打印SIGINT,第二次打印SIGUSR1,因为SIGUSR1是前面阻塞的)
(4)待信号处理函数返回,sigsuspend返回了。(sigsuspend将捕捉信号和信号处理函数集成到一起了)


在nginx中也采用了信号操作:

    sigset_t           set;

    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);
    sigaddset(&set, SIGALRM);
    sigaddset(&set, SIGIO);
    sigaddset(&set, SIGINT);
    sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_NOACCEPT_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_TERMINATE_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));

    //阻塞上述信号,仅仅是阻塞,不是丢弃,在sigsuspend函数被调用后,被阻塞的信号依然会传递到进程中
    if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "sigprocmask() failed");
    }

    //清空set,以重复利用
    sigemptyset(&set);

    for ( ;; ) {

        。。。。。。

        //此时的set是空的,所以任何信号都可以使得sigsuspend返回。请记得sigsuspend是在调用了信号处理函数后,才返回的。

        //如果在调用sigsuspend之前已经阻塞了NGX_RECONFIGURE_SIGNAL信号,在调用sigsuspend后又有NGX_REOPEN_SIGNAL信号,

        //那么信号处理函数会被调用两次,先处理NGX_REOPEN_SIGNAL信号,再处理NGX_RECONFIGURE_SIGNAL信号。

        sigsuspend(&set); 

        。。。。。。

        //ngx_terminate和ngx_quit等全局标志位,都是在信号处理函数中置位的

        if (ngx_terminate) {

              。。。。。。

        }

        if (ngx_quit) {

              。。。。。。

        }

    }


Logo

更多推荐