2013-01-09 14 views
8

Sono nuovo alla gestione dei segnali in Unix tramite C e ho esaminato alcuni tutorial su di esso (per puro interesse).Un programma C può continuare l'esecuzione dopo che un segnale è stato gestito?

Le mie domande sono, è possibile continuare l'esecuzione di un programma oltre il punto in cui viene gestito un segnale?

Capisco che la funzione di gestione del segnale esegue la pulizia ma, nello spirito di gestione delle eccezioni (come in C++), è possibile che quel segnale sia gestito nello stesso modo e che il programma continui a funzionare normalmente?

Al momento catch passa in un ciclo infinito (presumibilmente un modo per uscire sarebbe chiamare exit(1)).

La mia intenzione è che b venga assegnato 1 e che il programma termini con grazia (se ciò è ovviamente possibile).

Ecco il mio codice:

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

Inoltre, come C è procedurale, come mai il gestore di segnale dichiarato prima della dichiarazione incriminata è effettivamente chiamato dopo che quest'ultimo ha eseguito?

Infine, per fare in modo che la funzione di gestione esegua correttamente la pulizia, tutte le variabili di quelle da pulire in caso di eccezione devono essere dichiarate prima della funzione, giusto?

Grazie in anticipo per le vostre risposte e scuse se alcune delle precedenti sono molto ovvie.

+2

Caso di utilizzo comune: molti processi (daemon) possono gestire un segnale 'SIGHUP' che li fa caricare nuovamente il file di configurazione. –

risposta

9

Sì, questo è ciò che i gestori di segnale sono per. Ma alcuni segnali devono essere gestiti appositamente per consentire al programma di continuare (ad esempio SIGSEGV, SIGFPE, ...).

See uomo signaction:

Secondo POSIX, il comportamento di un processo è indefinito dopo ignora un SIGFPE, SIGILL, o il segnale SIGSEGV che non era generato da uccisione (2) o rilanciare (3). La divisione intera per zero ha un risultato indefinito. Su alcune architetture genera un segnale SIGFPE . (Anche la divisione del numero intero più negativo di -1 può generare SIGFPE.) Ignorare questo segnale potrebbe portare a un ciclo infinito .

In questo momento, si sono ignorando il segnale, non facendo nulla per evitare che accada (di nuovo). È necessario il contesto di esecuzione nel gestore del segnale e correggerlo manualmente, il che implica la sovrascrittura di alcuni registri.

Se SA_SIGINFO è specificato in sa_flags, allora sa_sigaction (anziché sa_handler) stabilisce la funzione del segnale di gestione per signum. Questa funzione riceve il numero del segnale come primo argomento, un puntatore in un siginfo_t come secondo argomento e un puntatore a un ucontext_t (cast to void *) come terzo argomento. (Comunemente, la funzione del gestore non fa uso di terzo argomento. Vedere getContext (2) per maggiori informazioni su ucontext_t.)

Il contesto consente l'accesso ai registri al momento del guasto e deve essere cambiato per consentire al tuo programma di continuare. Vedi questo lkml post. Come già menzionato, siglongjmp potrebbe anche essere un'opzione. Il post offre anche una soluzione piuttosto riutilizzabile per la gestione dell'errore, senza dover fare le variabili globali ecc .:

E poiché si gestisce YOUSELF, si ha alcuna flessibilità che si desidera a con la gestione degli errori. Ad esempio, è possibile fare il salto di gestione errori ad un certo punto specificato nella vostra funzione con qualcosa di simile questo:

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

Quindi, il vostro gestore per SIGFPE ha solo bisogno di fare qualcosa di simile

context.eip = context.ecx;

+0

Grazie mille per la risposta informativa. Ho fatto qualcosa di simile a 'SIGALRM' e ha catturato l'eccezione e proceduto normalmente, non sapevo che' SIGFPE' era speciale. Se non ti sta chiedendo troppo, potresti dirmi brevemente come devo integrare il codice nel post per gestire questa eccezione? Presumo che avrei bisogno di dichiarare il codice assembly nella routine 'main', ma non sono sicuro di dove mettere l'etichetta o la funzione' sigfpe_handler' menzionata nel post (suppongo di non poterla dare come argomento per 'signal' dato che l'argomento pointer assume solo il segnale int come argomento). – Nobilis

+1

Dovrai usare '' sigaction'' al posto di '' signal''. La struttura '' sigaction'' è più versatile nella quantità di opzioni che fornisce. Per l'altra parte, per favore duplice lettura del post lkml, non penso di poter scrivere quel codice per te. –

+0

Sarebbe tutto ok, grazie, solo bisogno di un po 'di informazioni sull'utilizzo. – Nobilis

5

In generale, sì, l'esecuzione continua dopo la restituzione del gestore. Ma se il segnale era causato da da un errore hardware (come un'eccezione in virgola mobile o un errore di segmentazione), non è possibile annullare tale errore e pertanto il programma verrà terminato a prescindere.

In altre parole, è necessario distinguere tra segnali e cose che causano segnali. I segnali da soli sono perfettamente soddisfacenti e gestibili, ma non sempre consentono di correggere gli errori che causano i segnali.

(Alcuni segnali sono speciali, come ABRT e STOP, nel senso che anche se si solleva manualmente un tale segnale con kill, non è ancora possibile "prevenirne gli effetti". E ovviamente KILL non può nemmeno essere gestito completamente.)

+1

SIGSTOP non può essere catturato o ignorato, proprio come SIGKILL. – Demi

+0

la tua risposta solleva un punto degno di nota "distinguere tra segnali e cose che causano segnali". Grazie. – Pbd

4

Se si sa cosa si sta facendo, è possibile impostare il puntatore dell'istruzione in modo che punti subito dopo l'istruzione incriminata. Di seguito è riportato il mio esempio per x86 (32 bit e 64 bit). Non provare a casa o in prodotti reali !!!

#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; 
} 
Problemi correlati