错误情况及原因分析

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

     /*
* 在信号处理函数中调用longjmp的错误情况
*/
#include <errno.h>
#include <setjmp.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#define BUFSIZE 512
jmp_buf env; void err_exit(char *fmt,...);
int err_dump(char *fmt,...);
int err_ret(char *fmt,...); void alrm_handler(int signo)
{
printf("Get the SIG_ALRM\n");
longjmp(env,);
}
void send_signal()
{
int count = ; if(SIG_ERR == signal(SIGALRM,alrm_handler))
err_exit("[signal]: "); alarm();
if( != setjmp(env)) {
pause();
} else {
count++;
} /* 使这个信号只能发送一次 */
if( == count) {
alarm();
pause();
}
} int main(int argc,char *argv[])
{
send_signal();
return ;
}

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

  

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

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

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

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

修正版本1

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

     /*
* 信号处理函数中调用longjmp函数的修正版本1
*/ #include <errno.h>
#include <setjmp.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h> #define BUFSIZE 512 jmp_buf env; void err_exit(char *fmt,...);
int err_dump(char *fmt,...);
int err_ret(char *fmt,...); void alrm_handler(int signo)
{
printf("Get the SIG_ALRM\n");
longjmp(env,);
}
void send_signal()
{
sigset_t sigset,oldset;
int count = ; if(SIG_ERR == signal(SIGALRM,alrm_handler))
err_exit("[signal]: "); alarm();
if( != setjmp(env)) {
pause();
} else {
count++;
} /* 检测SIGALRM信号是否被阻塞 */
if(- == sigprocmask(,NULL,&sigset))
err_exit("[sigprocmask]");
if(sigismember(&sigset,SIGALRM)) {
printf("Sigalrm has been blocked\n");
/* 将SIGALRM信号取消阻塞 */
if(- == sigdelset(&sigset,SIGALRM))
err_exit("[sigdelset]");
if(- == sigprocmask(SIG_SETMASK,&sigset,&oldset))
err_exit("[sigprocmask]");
} /* 使这个信号只能发送一次 */
if( == count) {
alarm();
pause();
}
} int main(int argc,char *argv[])
{
send_signal();
return ;
}

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

  

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

修正版本2

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

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

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

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

     /*
* 在信号处理函数中调用longjmp修正版本2
*
* 将jmp系列的函数改成sigjmp系列的
*/ #include <errno.h>
#include <setjmp.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h> #define BUFSIZE 512 sigjmp_buf env; void err_exit(char *fmt,...);
int err_dump(char *fmt,...);
int err_ret(char *fmt,...); void alrm_handler(int signo)
{
printf("Get the SIG_ALRM\n");
siglongjmp(env,);
}
void send_signal()
{
int count = ;
if(SIG_ERR == signal(SIGALRM,alrm_handler))
err_exit("[signal]: "); alarm();
if( != sigsetjmp(env,)) {
pause();
} else {
count++;
} if( == count) {
alarm();
pause();
}
} int main(int argc,char *argv[])
{
send_signal();
return ;
}

  

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

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

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

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

    最近面试的时候被问到一个问题是,在C++中,能否在类的成员函数中调用delete this,后来网上查了一下资料,关于这个问题说得比较好的有http://blog.sina.com.cn/s/blog ...

  2. 线程的函数中调用MFC对话框类的变量

    线程的函数中调用MFC对话框类的变量多线程传输文件的对话框 现在想要在对话框上添加一个进度条 为进度条映射变量m_progress这就需要在传输一段文件后就更新m_progress的值使进度条前进 也 ...

  3. Js文件函数中调用另一个Js文件函数的方法

    在项目中Js文件需要完成某一功能,但这一功能的大部分代码在另外一个Js文件已经完成,只需要调用这个文件实现功能.那么如何调用:一个Js文件函数中调用另一个Js文件函数的方法? (直接代码说明) 示例d ...

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

    一.在回调函数中调用自定义方法: 回调函数中不能直接使用this,需要在外面定义 var that = this 然后 that.自定义的方法.如下: //删除 onDelete: function ...

  5. JNI:在线程或信号处理函数中访问自定义类

    在写一个Tomcat应用,类需要被信号处理函数回调,可是在单独的程序中测试没用问题: void OnSingalHandler(int sig) { ... JNIEnv* env=NULL; if ...

  6. 在成员函数中调用虚函数(关于多态的注意事项)------新标准c++程序设计

    类的成员函数之间可以互相调用.在成员函数(静态成员函数.构造函数和析构函数除外)中调用其他虚成员函数的语句是多态的.例如: #include<iostream> using namespa ...

  7. 探究为什么FreeRTOS 有些API不能在中断服务函数中调用,转而需要调用带ISR的版本

    用了好久的FreeRTOS以前只是知道,如果在中断服务程序中调用某一些FreeRTOS的API函数时需要注意,如果有ISR版本的一定要调用末尾带ISR的函数,并且中断服务程序要调用freeRTOS的A ...

  8. Vue在一个函数中调用另外一个函数

    如:在vue的methods中一个函数调用另外一个函数 this.$options.methods.函数名字(); (这样的话要注意,this的指向已经指向了这个实例而不是指向全局,所以可能会报错说b ...

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

    //php闭包实现函数的自调用,也就是实现递归 function closure($n,$counter,$max){ //匿名函数,这里函数的参数加&符号是,引址调用参数自己 $fn = f ...

随机推荐

  1. iOS 图片拉伸 resizableImageWithCapInsets

    UIImage *image =  [[UIImage imageNamed:@"test.png"] resizableImageWithCapInsets:UIEdgeInse ...

  2. PDF出力相关资料

    http://itext.2136553.n4.nabble.com/iText-SetFieldProperty-method-not-working-for-some-parameters-set ...

  3. editplus格式化xml文档

    使用editplus打开xml文档,发现文件内容没有格式化,非常难看,使用IE9打开这个xml文档是格式化的,但是IE9打开不能编辑. 在网上找了一些资料发现能够通过一个小插件来是editplus格式 ...

  4. 微信红包签名算法 C#代码实现

    string stringA = "appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=100001 ...

  5. Highcharts 对数组的要求

    function Reflush(phaid,proid) { $.post('GetProjectSummer.ashx', { proid: proid, phaid: phaid }, func ...

  6. esriSRGeoCS3Type Constants

    ArcGIS Developer Help  (Geometry)     esriSRGeoCS3Type Constants More available geographic coordinat ...

  7. [HDU 2955]Robberies (动态规划)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2955 题意是给你一个概率P,和N个银行 现在要去偷钱,在每个银行可以偷到m块钱,但是有p的概率被抓 问 ...

  8. 页面设计--Grid列表

    Grid列表控件 功能:主要实现Html Table功能,支持多表头.固定表头.固定列.输出.组合查询.编辑功能 优点:可以快速的通过数据集合生成多表头.多样式的列表.通过简单的拖拉实现多层表头. G ...

  9. 【转载】eclipse调试arm裸机程序

    一.集成开发环境 软件部分:eclipse , GDB Server , Jlink软件 硬件部分:Jlink硬件 准备工作1:从SD/NOR Flash启动,格式化nand flash 准备工作2: ...

  10. JDBC数据更新

    在JDBC中通常用Statement类的对象实现对数据库的更新(增.删.查.改)操作 //1.获取数据库连接 connection = getConnection(); //2.准备sql语句 Str ...