错误情况及原因分析

  前两天看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,这样我们就可以解决这个问题了。

转载于:https://www.cnblogs.com/bwangel23/p/4591482.html

在信号处理函数中调用longjmp相关推荐

  1. mysql编写函数 求1 n 偶数之和,编写求1 2 3 - n的函数.在main函数中调用该函数

    输入两个正整数m,n,编写求阶乘的函数,计算m!/(n!*(m-n)!). C语言函数解答,谢谢. #includelongfactorial(intm,intn){longsum=1,sum1=1; ...

  2. 不要在循环,条件或嵌套函数中调用 Hook

    只在最顶层使用 Hook 不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们.遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用.这让 ...

  3. C语言编程>第六周 ① 编写一个录入函数:用来从键盘接收n个整型数并存放在一个整型数组中。 在主函数中调用该函数计算出这n个整数之和。

    例题:编写一个录入函数:用来从键盘接收n个整型数并存放在一个整型数组中. 在主函数中调用该函数计算出这n个整数之和. 代码如下: /*代码分析:录入函数需要两个参数:一个参数是需要用来存储数字的数组, ...

  4. C语言:自定义函数中调用自定义函数的方法

    自定义函数中调用自定义函数 在我们编写C语言程序时,可以将要经常用到的或者有自己单独作用的那一部分代码独立成一个函数,不仅可以简化我们的程序,还可以使我们的程序更加可见话. 正文阿巴阿巴 方法一:直接 ...

  5. vue中在一个函数中调用另外一个函数

    vue中在一个函数中调用另外一个函数 this.$options.methods.函数名.bind(this)();

  6. c语言习题 定义函数 areaT,功能是求梯形面积。要求在主函数中输入上底(用变量 a存储)、下底(用变量 b 存储)、和高(用变量 h 存储),在主函数中调用函数 areaT,输出梯形面积(用变量

    定义函数 areaT,功能是求梯形面积.要求在主函数中输入上底(用变量 a存储).下底(用变量 b 存储).和高(用变量 h 存储),在主函数中调用函数 areaT,输出梯形面积(用变量 s 存储)的 ...

  7. c++主函数中调用类内函数的方法

    c++主函数中调用类内函数的方法 以基数排序为例: #include <iostream> #include <vector> using namespace std;//基数 ...

  8. c语言函数中调用的参数太多

    c语言函数中调用的参数太多问题 问题展示 问题分析 解决方法 问题展示 (图中是我遇到的情况) 问题分析 大家可以看到,在函数中,指针变量和后面的整数变量都成了灰色 解决方法 图中问题只需将中文逗号, ...

  9. C++回调函数中调用Python函数出现的死锁问题调试及解决

    一.查找死锁原因: 1.使用gdb exe指令进入gdb命令行,再输入r运行可执行文件 gdb /home/sdhm/catkin_ws/devel/lib/gpd_ros/gpd_server GN ...

  10. 如何在matlab sfunction 函数中调用自己写的函数?

    自己编写了一个s函数,有几个参数引用了自己写的几个函数,在脚本中可以正确运行,但在写成s函数,进行 simulink 仿真的时候,已知提示"too many input auguments& ...

最新文章

  1. java中文getbytes为3,java 中文乱码问题
  2. Linux 进程、端口、IP、连接数等查询脚本
  3. Gibbs sampling [Gibbs采样]
  4. springboot扫描组件_springboot多模块包扫描问题的解决方法
  5. 重磅 | 数据库自治服务DAS论文入选全球顶会SIGMOD,领航“数据库自动驾驶”新时代
  6. oracle更改编码
  7. Reactor和Proactor对比以及优缺点 (netty的底层原理reactor模型)
  8. mysql编译和yum安装哪个好_Centos7下PHP源码编译和通过yum安装的区别和以后的选择...
  9. 设计模式六大原则你都知道吗?
  10. NYOJ-背包问题(贪心)
  11. 网络软工个人作业4——Alpha阶段个人总结
  12. 如何将无线鼠标连接到Mac电脑?
  13. Atitit 物联网之道 艾龙著 attilax著 1. 理论基础(控制理论 信息理论) 2 2. 1.5 物联网的关键技术12 2 2.1. 1.5.1 网络与通信技术12 1.5.2 无线传感
  14. pdf2image报错,pdf2image完整安装
  15. TCP报文-选项字段
  16. RADIUS协议指南
  17. 文件误删除如何找回呢?四步妙招解决
  18. 深度学习之美 第五章 学习笔记
  19. Elastic:使用 Elastic Stack 来监督系统日志及指标
  20. Elastic Searchable snapshot功能初探 三 (frozen tier)

热门文章

  1. iOS开发判断字符串为null
  2. 强大的代码编辑工具:Nova for mac v7.3中文版
  3. 看雪学院荣获TSRC 2018年度峰会优秀合作伙伴
  4. js模块的封装(-)
  5. [javase] 1.请从键盘随机输入10个整数保存到List中,并按倒序、从大到小的顺序显示出来...
  6. 比较各种SpA分类标准
  7. 学习.net 2.0需要讲究一下策略
  8. SSH框架总结(框架分析+环境搭建+实例源码下载
  9. Redis 读写分离技术,你了解多少?
  10. 毕业三五年,怎么拿到百万年薪?