在信号处理函数中调用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 ...
随机推荐
- MySQL处理数据库和表的常用命令
MySQL处理数据库和表的常用命令 [导读] 学习如何管理和导航MySQL数据库和表是要掌握的首要任务之一,下面的内容将主要对MySQL的数据库和表的一些常用命令进行总结,一些我们不得不掌握的命令,一 ...
- oracle中的exists 和not exists 用法详解
有两个简单例子,以说明 “exists”和“in”的效率问题 1) select * from T1 where exists(select 1 from T2 where T1.a=T2.a) ; ...
- tomcat7 内存溢出 java.lang.OutOfMemoryError: Java heap space
在{tomcat7_home}bin/catalina.sh最上面添加: JAVA_OPTS="-server -Xms800m -Xmx800m -XX:PermSize=128M -XX ...
- Android应用框架浅析
http://blog.csdn.net/yanbober/article/category/3206943 Android应用层View绘制流程与源码分析 http://blog.csdn.ne ...
- java学习___File类的创建
一.使用File创建文件 File file = new File("."+File.separator+"data.dat"); //如果不存在则创建 fil ...
- C#下的Redis 学习
Redis作为世界上最快的分布式NoSQL的数据存储,在高性能构架中离不开他的身影 Centos下安装Redis wget http://download.redis.io/redis-stable. ...
- [Uva 10085] The most distant state (BFS)
题目链接:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem& ...
- How to Resize a Datafile (文档 ID 1029252.6)
APPLIES TO: Oracle Database - Enterprise Edition - Version 9.2.0.1 and laterInformation in this docu ...
- 基本配置6-被忽悠进了CentOS 6
今天突然想到原来配置的apache2与tomcat的连接,测试发现有2个小问题: 1.<%=path%>丢失问题:原来配置的 ProxyPass /xxx http://192.168.1 ...
- IOS开发-文件管理(二)
IOS开发-文件管理(二) 五.Plist文件 String方式添加 NSString *path = [NSHomeDirectory( ) stringByAppen ...