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

时间: 2024-10-15 04:04:48

在信号处理函数中调用longjmp的相关文章

回调函数中调用类中的非静态成员变量或非静态成员函数

有关这方面的问题,首先说一点: 回调函数必须是静态成员函数或者全局函数来实现回调函数,大概原因是普通的C++成员函数都隐含了一个函数参数,即this指针,C++通过传递this指针给成员函数从而实现函数可以访问类的特定对象的数据成员.由于this指针的原因,使得一个普通成员函数作为回调函数时就会因为隐含的this指针问题使得函数参数个数不匹配,从而导致回调函数编译失败. 基于上面的理论,如何在类中封装回调函数呢? 回调函数只能是全局函数或者静态成员函数,但是由于全局函数会破坏封装性,所以只能用静

在类的成员函数中调用delete this

在类的成员函数中能不能调用delete this?答案是肯定的,能调用,而且很多老一点的库都有这种代码.假设这个成员函数名字叫release,而delete this就在这个release方法中被调用,那么这个对象在调用release方法后,还能进行其他操作,如调用该对象的其他方法么?答案仍然是肯定 的,调用release之后还能调用其他的方法,但是有个前提:被调用的方法不涉及这个对象的数据成员和虚函数.说到这里,相信大家都能明白为什么会这样 了. 根本原因在于delete操作符的功能和类对象的

wx: wx.showModal 回调函数中调用自定义方法

一.在回调函数中调用自定义方法: 回调函数中不能直接使用this,需要在外面定义 var that = this 然后 that.自定义的方法.如下: //删除 onDelete: function (e) { var that = this; wx.showModal({ title: '提示', content: '确定要删除?', success: function (res) { if (res.confirm) { that.onEdit(e); } } }) }, //编辑 onEd

成员函数中调用构造析构函数

示例如下: class MyClass{public:    MyClass(int a) : _a(a) { }    MyClass(const MyClass& rhs){        new(this)MyClass(rhs._a); // placement new    }    MyClass & operator = (const MyClass& rhs){        this->~MyClass();        new(this)MyClass(

php 在 匿名函数中 调用自身。。

//php闭包实现函数的自调用,也就是实现递归 function closure($n,$counter,$max){ //匿名函数,这里函数的参数加&符号是,引址调用参数自己 $fn = function (&$n,&$counter,&$max=1) use(&$fn){//use参数传递的是函数闭包函数自身 $n++; if($n < $max){//递归点,也就是递归的条件 $counter .=$n.'<br />'; //递归调用自己

写4个同名方法,实现两个整数、两个实数,一个实数一个整数,一个整数一个实数之间的求和。在主调函数中调用这4个方法计算相关的值。(方法的重载)

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace ConsoleApplication7 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 //整数相加 14 int a = 1; 1

java如何在函数中调用主函数的数组

import javax.swing.JOptionPane; public class Test { /** * @zdz */ public static void main(String[] args) { double aArray[]= new double[]{1,2,3,4,5}; printArray(aArray); } public static void printArray(double aArray[]) { String output=""; aArray[

map函数或reduce函数中如何调用第三方jar包

一般我们在mapreduce程序中调用第三方jar包时会出现找不到jar包的问题,检查发现jar包就在相应路径,mapreduce任务就是找不到.仔细想想会发现,这个jar包是放在执行mapreduce主程序机器上的内存中,一般为客户端机器.而我们在map或者reduce函数中调用该jar包时是在集群的机器上的内存中调用,这样怎么可以调用.可以使用以下方法: 1 把jar包提前放在集群每天机器上. 2 和集群调用mysql驱动程序一样,先将jar包放入hdfs,然后通过mysql的distrib

Linux信号、信号处理和信号处理函数

信号(signal)是一种软件中断,它提供了一种处理异步事件的方法,也是进程间惟一的异步通信方式.在Linux系统中,根据POSIX标准扩展以后的信号机制,不仅可以用来通知某种程序发生了什么事件,还可以给进程传递数据. 一.信号的来源 信号的来源可以有很多种试,按照产生条件的不同可以分为硬件和软件两种. 1.  硬件方式 当用户在终端上按下某键时,将产生信号.如按下组合键后将产生一个SIGINT信号. 硬件异常产生信号:除数据.无效的存储访问等.这些事件通常由硬件(如:CPU)检测到,并将其通知