在信号处理函数中调用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 ...
随机推荐
- linux free命令(转载)
来源:http://www.cnblogs.com/peida/archive/2012/12/25/2831814.html free命令可以显示Linux系统中空闲的.已用的物理内存及swap内存 ...
- Xcode7--坑无法运行iOS9以下的模拟器
Unable to open liblaunch_sim.dylib. Try reinstalling Xcode or the simulator 解决办法 一.找到目标文件 /Applicati ...
- Python进阶02 文本文件的输入输出
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! Python具有基本的文本文件读写功能.Python的标准库提供有更丰富的读写功能 ...
- [ActionScript 3.0] AS3.0 给flash事件传递参数的方法
有时我们想要给flash内置的事件(比如MouseEvent)传递参数,这时我们可以用到下面的方法. import flash.events.MouseEvent; mc.addEventListen ...
- java String 的方法 (01)
1.int compareTo(String other) 按照字典的顺序,如果字符串位于String other 之前,则返回一个负数,如果字符串位于othere之后,则返回一个正数,如果两个字符串 ...
- (转)C#调用非托管Win 32 DLL
转载学习收藏,原文地址http://www.cnblogs.com/mywebname/articles/2291876.html 背景 在项目过程中,有时候你需要调用非C#编写的DLL文件,尤其在使 ...
- python3 pickle, json
pickle 有dump ,dumps ,load,loads等方法.区别在于dumps不会写入到文件. import pickle string = ['a', 2341, 'adsf'] p_st ...
- html 去掉input 获取焦点时的边框
html中,当input标签获取焦点的时候(例如,当光标放在input框中准备输入值时), input标签外围会出现边框,有的时候我们需要去掉这个边框,可以使用css的outline:none;属性将 ...
- OpenGL的学习与认识
OpenGL: Open Graphics Library 一套三维图形处理库,也是该领域的工业标准.是一个定义了一个跨编程语言,跨平台的编程接口规格的专业的图形程序接口.它用于三维图像(二维的亦可) ...
- cocos2d-x的环境的搭建
1.首先提出一个我从开始接触cocosstudio和cocos2d-x的认识的误区,就是cocosstudio和cocos2d-x的区别是什么呢? cocosstudio是辅助工具,只不过它可以帮助我 ...