我是Unix中通过C进行信号处理的新手,我一直在寻找一些关于它的教程(出于纯粹的兴趣).
我的问题是,是否有可能在处理信号之后继续执行程序?
我知道信号处理功能可以进行清理,但是在异常处理的精神下(例如在C++中),是否可以以相同的方式处理该信号并使程序继续正常运行?
目前catch进入一个无限循环(可能是一种退出的方式是打电话exit(1)).
我的意图b是分配1并使程序优雅地完成(如果可能的话).
这是我的代码:
#include <signal.h>
#include <stdio.h>
int a = 5;
int b = 0;
void catch(int sig)
{
printf("Caught the signal, will handle it now\n");
b = 1;
}
int main(void)
{
signal(SIGFPE, catch);
int c = a / b;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
另外,由于C是程序性的,为什么在执行后者之后实际调用了有问题的语句之前声明的信号处理程序?
最后,为了使处理函数能够正确地进行清理,所有在异常情况下需要清理的变量都需要在函数之前声明,对吧?
如果上面的一些内容非常明显,请提前感谢您的回答和道歉.
Jon*_*fer 11
是的,这就是信号处理程序的用途.但是为了允许程序继续(例如SIGSEGV,SIGFPE,......),需要专门处理一些信号.
请参阅sigaction:
根据POSIX,在忽略不是由kill(2)或raise(3)生成的SIGFPE,SIGILL或SIGSEGV信号之后,进程的行为是未定义的.整数除零具有未定义的结果.在某些架构上,它将生成SIGFPE信号.(也将最负整数除以-1可能会生成SIGFPE.)忽略此信号可能会导致无限循环.
现在,你是忽略的信号,通过没有做任何事情,以防止它发生(再次).您需要信号处理程序中的执行上下文并手动修复它,这涉及覆盖一些寄存器.
如果在sa_flags中指定了SA_SIGINFO,则sa_sigaction(而不是sa_handler)指定signum的信号处理函数.此函数接收信号编号作为其第一个参数,指向siginfo_t作为其第二个参数的指针,以及指向ucontext_t(cast to void*)的指针作为其第三个参数.(通常,处理函数不会使用第三个参数.有关ucontext_t的更多信息,请参阅getcontext(2).)
上下文允许在发生故障时访问寄存器,需要更改以允许程序继续.看到这个lkml帖子.如上所述,siglongjmp也可能是一种选择.该帖子还提供了一个可重复使用的解决方案来处理错误,而不必使变量全局化等:
而且因为你自己处理它,你有任何你想要的灵活性与错误处理.例如,您可以使用以下内容将错误处理程序跳转到函数中的某个指定点:
__label__ error_handler;
__asm__("divl %2"
:"=a" (low), "=d" (high)
:"g" (divisor), "c" (&&error_handler))
... do normal cases ...
error_handler:
... check against zero division or overflow, so whatever you want to ..
Run Code Online (Sandbox Code Playgroud)
然后,您的SIGFPE处理程序只需要执行类似的操作
context.eip = context.ecx;
通常,是的,在处理程序返回后继续执行.但是,如果信号是造成由硬件故障(如浮点异常或分段错误),你有没有撤消的错误的方式,所以你的程序将被终止无关.
换句话说,您必须区分信号和导致信号的事物.信号本身非常精细且可以处理,但它们并不总能让您修复导致信号的错误.
(某些信号是特殊的,例如ABRT和STOP,在某种意义上,即使您只是手动提升这样的信号kill,您仍然无法"防止其影响".当然,KILL甚至根本无法处理.)
如果您知道自己在做什么,可以将指令指针设置为在违规指令之后指向右侧.下面是我的x86(32位和64位)示例.不要在家里或真正的产品尝试!
#define _GNU_SOURCE /* Bring REG_XXX names from /usr/include/sys/ucontext.h */
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <ucontext.h>
static void sigaction_segv(int signal, siginfo_t *si, void *arg)
{
ucontext_t *ctx = (ucontext_t *)arg;
/* We are on linux x86, the returning IP is stored in RIP (64bit) or EIP (32bit).
In this example, the length of the offending instruction is 6 bytes.
So we skip the offender ! */
#if __WORDSIZE == 64
printf("Caught SIGSEGV, addr %p, RIP 0x%lx\n", si->si_addr, ctx->uc_mcontext.gregs[REG_RIP]);
ctx->uc_mcontext.gregs[REG_RIP] += 6;
#else
printf("Caught SIGSEGV, addr %p, EIP 0x%x\n", si->si_addr, ctx->uc_mcontext.gregs[REG_EIP]);
ctx->uc_mcontext.gregs[REG_EIP] += 6;
#endif
}
int main(void)
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = sigaction_segv;
sa.sa_flags = SA_SIGINFO;
sigaction(SIGSEGV, &sa, NULL);
/* Generate a seg fault */
*(int *)NULL = 0;
printf("Back to normal execution.\n");
return 0;
}
Run Code Online (Sandbox Code Playgroud)