处理完信号后,C程序能否继续执行?

我是Unix中通过C进行信号处理的新手,我一直在寻找一些关于它的教程(出于纯粹的兴趣)。

我的问题是,是否有可能在处理信号之后继续执行程序?

我知道信号处理function会进行清理,但本着exception处理的精神(例如在C ++中),是否可以以相同的方式处理该信号并使程序继续正常运行?

目前catch进入无限循环(可能是退出的方式是调用exit(1) )。

我的意图是为b分配1并使程序优雅地完成(如果可能的话)。

这是我的代码:

 #include  #include  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; } 

另外,由于C是程序性的,为什么在执行后者之后实际调用有问题的语句之前声明的信号处理程序呢?

最后,为了使处理函数正确地进行清理,所有在exception情况下需要清理的变量都需要在函数之前声明,对吧?

如果以上一些非常明显,请提前感谢您的回答和道歉。

是的,这就是信号处理程序的用途。 但是为了允许程序继续(例如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)。)

上下文允许在发生故障时访问寄存器,需要更改以允许程序继续运行。 看到这个lkmlpost 。 如上所述, siglongjmp也可能是一种选择。 该post还提供了一个可重复使用的解决方案来处理错误,而不必使变量全局化等:

而且因为你自己处理它,你有任何你想要的灵活性与error handling。 例如,您可以使用以下内容将error handling程序跳转到函数中的某个指定点:

  __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 .. 

然后,您的SIGFPE处理程序只需要执行类似的操作

context.eip = context.ecx;

通常,是的,在处理程序返回后继续执行。 但是,如果信号是由硬件错误(例如浮点exception或分段错误)引起的,则无法撤消该错误,因此无论如何都会终止程序。

换句话说,您必须区分信号和导致信号的事物。 信号本身非常精细且可处理,但它们并不总能让您修复导致信号的错误

(某些信号是特殊的,例如ABRT和STOP,在某种意义上说,即使你只是用kill手动提升这样的信号,你仍然无法“防止它的影响”。当然KILL甚至根本无法处理。 )

如果您知道自己在做什么,可以将指令指针设置为在违规指令之后指向右侧。 下面是我的x86(32位和64位)示例。 不要在家里或真正的产品尝试!

 #define _GNU_SOURCE /* Bring REG_XXX names from /usr/include/sys/ucontext.h */ #include  #include  #include  #include  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; }