zoukankan      html  css  js  c++  java
  • 在信号处理函数中调用longjmp

    错误情况及原因分析

      前两天看APUE的时候,有个程序要自己制作一个sleep程序,结果在这个程序中就出现了在信号处理函数中调用longjmp函数的情况,结果就出现了错误,具体错误是啥呢,请参见下面这段程序:

     1     /*
     2      * 在信号处理函数中调用longjmp的错误情况
     3      */
     4     #include <errno.h>
     5     #include <setjmp.h>
     6     #include <signal.h>
     7     #include <string.h>
     8     #include <stdlib.h>
     9     #include <stdarg.h>
    10     #include <stdio.h>
    11     #define BUFSIZE 512
    12     jmp_buf env;
    13     
    14     void err_exit(char *fmt,...);
    15     int err_dump(char *fmt,...);
    16     int err_ret(char *fmt,...);
    17     
    18     void alrm_handler(int signo)
    19     {
    20         printf("Get the SIG_ALRM
    ");
    21         longjmp(env,2);
    22     }
    23     void send_signal()
    24     {
    25         int count = 0;
    26     
    27         if(SIG_ERR == signal(SIGALRM,alrm_handler))
    28             err_exit("[signal]: ");
    29     
    30         alarm(1);
    31         if(2 != setjmp(env)) {
    32             pause();
    33         } else {
    34             count++;
    35         }
    36     
    37         /* 使这个信号只能发送一次 */
    38         if(1 == count) {
    39             alarm(1);
    40             pause();
    41         }
    42     }
    43     
    44     int main(int argc,char *argv[])
    45     {
    46         send_signal();
    47         return 0;
    48     }

      在这个程序中,我首先通过alarm函数发送了一个SIGALRM信号,然后在信号处理函数中调用了longjmp,跳跃到了alarm函数的下一句,此时,我再来通过alarm函数再发送一个信号,结果运行的结果如下:

      

      可以看到,我们这个程序只收到了第一个alarm函数发送的信号,然后程序就卡死了,接收不到后面发送的信号了,这是怎么回事,要解决这个问题,我们需要了解一下,一个应用程序处理信号的过程。

      1. 进程被中断,进入内核态检测信号
      2. 设置进程的信号屏蔽字,屏蔽要处理的信号
      3. 进程回到用户态,执行信号处理函数
      4. 进程进入到内核态度,更改进程的信号屏蔽字,取消信号的屏蔽
      5. 进程回到用户态,继续执行

      上面是我自己总结的简要的处理流程,关于更详细的流程,可以参考这个博客:Linux信号处理机制

      看了上面的流程之后,我们就能明白为什么上面的程序会出问题了,因为信号处理程序执行完了之后,还要执行一个操作,就是取消当前进程对这个信号的屏蔽,我们调用了longjmp函数之后,直接跳转到进程的另外一个地方继续执行,并没有把进程中对信号的屏蔽取消掉,所以程序就无法接收到信号了。

    修正版本1

      我们可以来做一个实验,对上面的程序进行一个更改,在longjmp之后手动取消当前进程对这个信号的屏蔽。请看下面这段代码:

     1     /*
     2      * 信号处理函数中调用longjmp函数的修正版本1
     3      */
     4     
     5     #include <errno.h>
     6     #include <setjmp.h>
     7     #include <signal.h>
     8     #include <string.h>
     9     #include <stdlib.h>
    10     #include <stdarg.h>
    11     #include <stdio.h>
    12     
    13     #define BUFSIZE 512
    14     
    15     jmp_buf env;
    16     
    17     void err_exit(char *fmt,...);
    18     int err_dump(char *fmt,...);
    19     int err_ret(char *fmt,...);
    20     
    21     void alrm_handler(int signo)
    22     {
    23         printf("Get the SIG_ALRM
    ");
    24         longjmp(env,2);
    25     }
    26     void send_signal()
    27     {
    28         sigset_t sigset,oldset;
    29         int count = 0;
    30     
    31         if(SIG_ERR == signal(SIGALRM,alrm_handler))
    32             err_exit("[signal]: ");
    33     
    34         alarm(1);
    35         if(2 != setjmp(env)) {
    36             pause();
    37         } else {
    38             count++;
    39         }
    40     
    41         /* 检测SIGALRM信号是否被阻塞 */
    42         if(-1 == sigprocmask(0,NULL,&sigset))
    43             err_exit("[sigprocmask]");
    44         if(sigismember(&sigset,SIGALRM)) {
    45             printf("Sigalrm has been blocked
    ");
    46             /* 将SIGALRM信号取消阻塞 */
    47             if(-1 == sigdelset(&sigset,SIGALRM))
    48                 err_exit("[sigdelset]");
    49             if(-1 == sigprocmask(SIG_SETMASK,&sigset,&oldset))
    50                 err_exit("[sigprocmask]");
    51         }
    52     
    53         /* 使这个信号只能发送一次 */
    54         if(1 == count) {
    55             alarm(1);
    56             pause();
    57         }
    58     }
    59     
    60     int main(int argc,char *argv[])
    61     {
    62         send_signal();
    63         return 0;
    64     }

      上面这段程序的运行结果如下图所示:

      

      从运行结果可以看出,SIGALRM信号是被屏蔽的,当我们取消屏蔽之后,信号就可以继续发送了。

    修正版本2 

        但是这样做是不是太麻烦了,每回都要取消屏蔽,有没有更简单的办法了,当然有啊,当初设计POSIX标准的那些老头子们(或许不是老头子)早都想好了,就是sigsetjmp函数和siglongjmp函数,这个具体怎么用呢?

      具体信息在man文档中是这样说的,这是sigsetjmp函数的声明:
      
      关于savesigs参数是这样说明的:
      
      上面这段话的意思是,如果savesigs不为0的时候,sigsetjmp函数就是在保存现场信息的时候,还额外保存了一个进程信号屏蔽字,当longjmp返回的同时,也会恢复进程的信号屏蔽字。

      这样调用sig系列的jmp函数就能够避免上面那种错误了。


      具体使用可以参考下面这段程序:

     1     /*
     2      * 在信号处理函数中调用longjmp修正版本2
     3      *
     4      * 将jmp系列的函数改成sigjmp系列的
     5      */
     6     
     7     #include <errno.h>
     8     #include <setjmp.h>
     9     #include <signal.h>
    10     #include <string.h>
    11     #include <stdlib.h>
    12     #include <stdarg.h>
    13     #include <stdio.h>
    14     
    15     #define BUFSIZE 512
    16     
    17     sigjmp_buf env;
    18     
    19     void err_exit(char *fmt,...);
    20     int err_dump(char *fmt,...);
    21     int err_ret(char *fmt,...);
    22     
    23     void alrm_handler(int signo)
    24     {
    25         printf("Get the SIG_ALRM
    ");
    26         siglongjmp(env,2);
    27     }
    28     void send_signal()
    29     {
    30         int count = 0;
    31         if(SIG_ERR == signal(SIGALRM,alrm_handler))
    32             err_exit("[signal]: ");
    33     
    34         alarm(1);
    35         if(2 != sigsetjmp(env,1)) {
    36             pause();
    37         } else {
    38             count++;
    39         }
    40     
    41         if(1 == count) {
    42             alarm(1);
    43             pause();
    44         }
    45     }
    46     
    47     int main(int argc,char *argv[])
    48     {
    49         send_signal();
    50         return 0;
    51     }

      

      程序的运行结果如下图所示:

      OK,这样我们就可以解决这个问题了。

  • 相关阅读:
    POJ 1680 Fork() Makes Trouble
    课堂改进意见
    梦断代码 读后感3
    梦断代码 读后感2
    找一问题
    软件评价——搜狗输入法
    《梦断代码》读后感1
    站立会议第十天
    站立会议第九天
    站立会议第八天
  • 原文地址:https://www.cnblogs.com/bwangel23/p/4591482.html
Copyright © 2011-2022 走看看