在信号处理函数中调用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\n");
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\n");
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\n");
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\n");
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,这样我们就可以解决这个问题了。

posted @ 2015-06-21 08:10  黑翼天使23  阅读(553)  评论(0编辑  收藏  举报