2013-01-09 96 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; 
}