2013-01-09 95 views
8

我是通过C在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; 
} 

此外,由于C是程序性的,怎么就违规语句之前声明的信号处理程序实际上是所谓后者已被执行之后?

最后,为了让处理函数能够正确地进行清理,在发生异常时需要清理的所有变量都需要在函数之前声明,对吧?

在此先感谢您的回答并道歉,如果上述某些内容非常明显。

+2

常见用例:很多(守护进程)进程可以处理'SIGHUP'信号,使它们重新加载配置文件。 –

回答

9

是的,这就是信号处理程序的用途。但是一些信号需要专门处理才能让程序继续运行(例如SIGSEGV,SIGFPE,...)。

见人signaction:

根据POSIX,一个过程的行为是它忽略一个SIGFPE,SIGILL或SIGSEGV信号,这是不被杀死(2)产生或提高 (3后未定义)。整数除零的结果未定义。在某些体系结构中,它将生成一个 SIGFPE信号。 (也可以将最负的整数除以-1生成SIGFPE。)忽略此信号可能会导致无限循环 。

现在,你无视信号,通过没有做任何事情,以防止它发生(再次)。您需要信号处理程序中的执行上下文并手动修复它,这会涉及覆盖一些寄存器。

如果其中sa_flags指定SA_SIGINFO,然后sa_sigaction(而不是 sa_handler)指定为正负号的信号处理功能。这个 函数接收信号编号作为它的第一个参数,一个指针 作为它的第二个参数和一个指向ucontext_t (转换为void *)作为它的第三个参数的指针。 (通常,处理程序 功能不作任何使用第三个参数的。请参阅有关ucontext_t进一步信息 的getContext(2)。)

上下文允许访问寄存器在故障发生时和需要改变以允许你的程序继续。请参阅lkml post。如上所述,siglongjmp也可能是一个选项。该帖子还提供了处理错误相当重用的解决方案,而无需进行变量的全局等:

因为你处理它youself,你有你想要 任何灵活性的错误处理。例如,可以使故障处理程序 跳转到一些指定的点在你的函数类似 这样:

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

+0

非常感谢您提供的信息丰富的答案。我用'SIGALRM'做了类似的事情,它确实捕获了异常并正常进行,不知道'SIGFPE'是特殊的。 如果问题不是太多,可否请简单告诉我如何在帖子中集成代码以处理此异常?我假设我需要在'main'例程中声明汇编代码,但我不确定将标签或函数中提到的'sigfpe_handler'函数放在哪里(我想我不能把它作为参数'signal'因为指针参数只将信号int作为参数)。 – Nobilis

+1

您需要使用''sigaction''来代替''signal''。 ''sigaction''结构在它所提供的选项数量上更加通用。对于其他部分,请仔细阅读lkml文章,我不认为我可以为您编写该代码。 –

+0

这将是好的,谢谢,只需要一些关于使用情况的信息。 – Nobilis

5

一般来说,是的,执行程序返回后继续执行。但是如果信号是由于硬件错误(例如浮点异常或分段错误)导致,您无法撤消该错误,因此您的程序将被终止,无论如何。

换句话说,你必须区分信号和导致信号的东西。信号本身是完美的,可以操作的,但它们并不总是让你修复导致信号的错误

(某些信号是特殊的,如ABRT和STOP,即使您只是用kill手动提升这样的信号,您仍然无法“防止其影响”,当然KILL甚至不能处理完毕。)

+1

就像SIGKILL一样,SIGSTOP不能被捕获或忽略。 – Demi

+0

你的回答提出了一个值得注意的观点:“区分信号和导致信号的事物”。谢谢。 – Pbd

4

如果您知道自己在做什么,可以将指令指针设置为在违规指令之后立即指向。以下是我的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; 
}