在信号处理函数中调用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 ...
随机推荐
- 用R在字符串中提取匹配的部分
例如在aaaa12xxxx中提取12,在参考了stackoverflow后比较方便的大致有以下几种方法: 利用sub跟gsub sub(".*?([0-9]+).*", " ...
- 解决Ubuntu 12.10中ZIP文件名乱码的方法
转摘源地址:http://blog.csdn.net/jiangxinyu/article/details/8206395 安装(12.04及以上): 代码: sudo apt-get install ...
- Bridge桥接模式
当我们的功能要在多个维度进行扩展时,各个维度之间可以交叉组合,就可以考虑使用桥接模式. 将抽象部分与实现部分分离,使它们都可以独立的变化. ...
- 移动应用(手机应用)开发IM聊天程序解决方案
这个解决方法已经定制下来很久了,上一段时间比较忙,没有时间整这些东西.最近稍微好些,不怎么加班.所以抽空总结下,同时也分享给大家,也算是给大家一个借鉴吧!或许这并不是最好的解决方案,但只要能满足当前需 ...
- Sublime Text 3083破解/汉化
破解码:—– BEGIN LICENSE —– Andrew Weber Single User License EA7E-855605 813A03DD 5E4AD9E6 6C0EEB94 BC99 ...
- springmvc+ajaxFileUpload上传文件(前后台彻底分离的情况下)
首先是导入jar包: web.xml: <servlet> <servlet-name>mvc-dispatcher</servlet-name> <serv ...
- 安装LINUX X86-64的10201出现链接ins_ctx.mk错误
在安装linux X86-64的Oracle10201时,在链接过程中出现了这个错误. 详细错误信息为: Error in invoking target ‘install’ of makefile ...
- 项目积累——Strus、Hibernate
在Struts-config.xml中对ActionForm Bean的生命周期用scope进行定义,可用的选项有:pageContext(缺省).request(常用).session.applic ...
- Android——TableLayout
TableLayout的行数由开发人员直接指定,即有多少个TableRow对象(或View控件),就有多少行. TableLayout的列数等于含有最多子控件的TableRow的列数.如第一Table ...
- 测试一个域名DNS查询时间的shell脚本
脚本内容: #!/bin/bash #目标域名 site=${site:-www.ptesting.com} for((i=1;i<=10000;i++)) do #COUNTER='e ...