Linux中对异常的处理
异常处理程序发送相应的信号给发生异常的当前进程,或者进行故障恢复,然后返回到断点处执行。
例如,若执行了非法操作,CPU就产生6号异常(#UD),在对应的异常处理程序中,向当前进程发送一个SIGILL信号,以通知当前进程终止运行。
采用向发生异常的进程发送信号的机制实现异常处理,可尽快完成在内核态的异常处理过程,因为异常处理过程越长,嵌套执行异常的可能性越大,而异常嵌套执行会付出较大的代价。
并不是所有异常处理都只是发送一个信号到发生异常的进程。
例如对于14号页故障异常(#PE),需要判断是否访问越级、越权或越界等,若发生了这些无法恢复的故障,则页故障处理程序发送SIGSEGV信号给发生页故障异常的进程;若只是缺页,则页故障处理程序负责把所缺失页面从磁盘装入主存,然后返回到发生缺页故障的指令继续执行。
阶段
所有异常处理程序的结构是一致的,都可划分成以下3个部分:
准备阶段:在内核栈保存通用寄存器内容(称为现场信息),这部分大多用汇编语言程序实现。
处理阶段:采用C函数进行具体处理。函数名由do_
前缀和处理程序名组成,如do_overflow
为溢出处理函数。
大部分函数的处理方式:保存硬件出错码(如果有的话)和异常类型号,然后向当前进程发送一个信号。当前进程接收到信号后,若有对应信号处理程序,则转到信号处理程序执行;若没有,则调用内核abort例程执行,以终止当前进程。
恢复阶段:恢复保存在内核栈中的各个寄存器的内容,切换到用户态并返回到当前进程的断点处继续执行。
用户进程——>movl–>页故障—>OS的页故障处理程序,检测到地址越界或访问越权,发送“SIGSEGV”信号给用户进程。
demo
没有异常处理程序
#include <cstdio>
#include <cstdlib>
#include <setjmp.h>
sigjmp_buf buf;
void FLPhandler(int sig)
{
printf("error type is SIGFPE!\n");
siglongjmp(buf, 1);
}
int main()
{
int a, t;
if (!sigsetjmp(buf, 1)) {
printf("starting\n");
a = 100;
t = 0;
a = a / t;
}
printf("I am still alive...\n");
exit(0);
}
运行结果是
m@ubuntu:~$ ./a.out
starting
Floating point exception (core dumped)
有异常处理程序
#include <cstdio>
#include <cstdlib>
#include <setjmp.h>
#include <signal.h>
sigjmp_buf buf;
void FLPhandler(int sig)
{
printf("error type is SIGFPE!\n");
siglongjmp(buf, 1);
}
int main()
{
int a, t;
signal(SIGFPE,FLPhandler);
if (!sigsetjmp(buf, 1)) {
printf("starting\n");
a = 100;
t = 0;
a = a / t;
}
printf("I am still alive...\n");
exit(0);
}
结果
m@ubuntu:~$ ./a.out
starting
error type is SIGFPE!
I am still alive...