在信号处理函数中调用longjmp
错误情况及原因分析
前两天看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的更多相关文章
- 在类的成员函数中调用delete this
最近面试的时候被问到一个问题是,在C++中,能否在类的成员函数中调用delete this,后来网上查了一下资料,关于这个问题说得比较好的有http://blog.sina.com.cn/s/blog ...
- 线程的函数中调用MFC对话框类的变量
线程的函数中调用MFC对话框类的变量多线程传输文件的对话框 现在想要在对话框上添加一个进度条 为进度条映射变量m_progress这就需要在传输一段文件后就更新m_progress的值使进度条前进 也 ...
- Js文件函数中调用另一个Js文件函数的方法
在项目中Js文件需要完成某一功能,但这一功能的大部分代码在另外一个Js文件已经完成,只需要调用这个文件实现功能.那么如何调用:一个Js文件函数中调用另一个Js文件函数的方法? (直接代码说明) 示例d ...
- wx: wx.showModal 回调函数中调用自定义方法
一.在回调函数中调用自定义方法: 回调函数中不能直接使用this,需要在外面定义 var that = this 然后 that.自定义的方法.如下: //删除 onDelete: function ...
- JNI:在线程或信号处理函数中访问自定义类
在写一个Tomcat应用,类需要被信号处理函数回调,可是在单独的程序中测试没用问题: void OnSingalHandler(int sig) { ... JNIEnv* env=NULL; if ...
- 在成员函数中调用虚函数(关于多态的注意事项)------新标准c++程序设计
类的成员函数之间可以互相调用.在成员函数(静态成员函数.构造函数和析构函数除外)中调用其他虚成员函数的语句是多态的.例如: #include<iostream> using namespa ...
- 探究为什么FreeRTOS 有些API不能在中断服务函数中调用,转而需要调用带ISR的版本
用了好久的FreeRTOS以前只是知道,如果在中断服务程序中调用某一些FreeRTOS的API函数时需要注意,如果有ISR版本的一定要调用末尾带ISR的函数,并且中断服务程序要调用freeRTOS的A ...
- Vue在一个函数中调用另外一个函数
如:在vue的methods中一个函数调用另外一个函数 this.$options.methods.函数名字(); (这样的话要注意,this的指向已经指向了这个实例而不是指向全局,所以可能会报错说b ...
- php 在 匿名函数中 调用自身。。
//php闭包实现函数的自调用,也就是实现递归 function closure($n,$counter,$max){ //匿名函数,这里函数的参数加&符号是,引址调用参数自己 $fn = f ...
随机推荐
- Jquery插件收藏
1.带小图标的多级菜单导航 演示地址:http://js.itivy.com/memu/sample.html 下载地址:http://js.itivy.com/?p=100 效果图: 推荐一个自己 ...
- tomcat 内存溢出
PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,这一部分用于存放Class和Meta的信息,Class在被 Load的时候被放入Perm ...
- spring错误<context:property-placeholder>:Could not resolve placeholder XXX in string value XXX
spring同时集成redis和mongodb时遇到多个资源文件加载的问题 这两天平台中集成redis和mongodb遇到一个问题 单独集成redis和单独集成mongodb时都可以正常启动程序,但是 ...
- gulp构建前端开发环境
1.gulp环境的安装 首先确保你已经正确安装了nodejs环境.然后以全局方式安装gulp: npm install -g gulp 2.建立文件夹 mkdir item 3.初始化项目: npm ...
- 查看用户的SQL执行历史
程序开发少不来SQL,基本都是基于SQL开发,程序仅仅起一个流程控制的作用.但是数据库本身存在许多内置的视图或者内置的表,如果打算研究SQL执行的效率已经SQL执行的历史记录,通过这些视图可以知道. ...
- github里的gist是什么意思
在有关github的客户端中经常遇到gist这个词,如Gists->My Gists,Public Gists. 字典里解释gist为(发言.谈话或文章的)主旨,要点.百度百科的解释太恶心,我不 ...
- IOS开发-jqeurey mobile
有一阵子没写东西了,最近打算重新拾起开发,做点手机上的东东,选中了phonegap.jquery mobile,phonegap的部署网上非常多,有空了再班门弄斧,这里先记下jquery mobile ...
- 使用升级版的 Bootstrap typeahead v1.2.2
上次介绍了 Bootstrap 2 中附带的 typeahead,功能强大,但是使用起来不太方便,作者 Terry Rosen 已经升级了一个新版本 v1.2.2,作出了很大的改进. 下载地址 htt ...
- IBM Appscan基本操作手册
一.操作前提 1.首先下载Appscan的安装包 2.安装Appscan 二.操作流程 1.双击图标,打开Appscan软件 2.打开软件后,页面显示如下: 3.选择“文件-新建”,弹出如下的窗口: ...
- go五笔——基于Google在线五笔制作
go五笔 v0.0.2 加入新世纪版 86版收录几个不常用汉字,其它无更新 下载 86版64位密码: qe7k 86版32位密码: y25a 06版64位密码: d2ug 06版32位密码: bxxz ...