2013-01-09 13 views
8

Jestem nowy w obsłudze sygnałów w systemie Unix przez C i szukałem na nim kilku tutoriali (z czystego zainteresowania).Czy program C może kontynuować wykonywanie po odebraniu sygnału?

Moje pytanie brzmi, czy możliwe jest kontynuowanie programu po przekroczeniu punktu, w którym sygnał jest obsługiwany?

Rozumiem, że funkcja obsługi sygnału wykonuje czyszczenie, ale w duchu obsługi wyjątków (np. W C++), czy jest możliwe, aby ten sygnał był obsługiwany w ten sam sposób i aby program działał normalnie?

W tej chwili catch przechodzi w nieskończoną pętlę (prawdopodobnie sposobem na zakończenie byłoby wywołanie exit(1)).

Moim zamiarem będzie przypisanie numeru b 1 i ukończenie programu z wdziękiem (o ile to oczywiście możliwe).

Oto mój kod:

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

Ponadto, jak C jest proceduralny, jak przychodzą obsługi sygnału zadeklarowane przed rachunku naruszającego jest faktycznie nazywa po tym jak została wykonana?

Na koniec, aby funkcja obsługi mogła prawidłowo przeprowadzić czyszczenie, wszystkie zmienne, które nie wymagają czyszczenia w przypadku wyjątku, muszą być zadeklarowane przed funkcją, prawda?

Z góry dziękuję za odpowiedzi i przeprosiny, jeśli niektóre z powyższych są bardzo oczywiste.

+2

Wspólny przypadek użycia: wiele procesów (demonów) może obsłużyć sygnał 'SIGHUP', który powoduje, że ponownie ładują plik konfiguracyjny. –

Odpowiedz

9

Tak, do tego służą manipulatory sygnałów. Ale niektóre sygnały muszą być obsługiwane specjalnie w celu umożliwienia kontynuacji programu (np. SIGSEGV, SIGFPE, ...).

See człowiek signaction:

Zgodnie ze standardem POSIX, zachowanie procesu jest niezdefiniowana po zignorowaniu SIGFPE, SIGILL lub SIGSEGV, który nie był generowane przez kill (2) lub podnieść (3). Podział całkowity przez zero ma niezdefiniowany wynik. Na niektórych architekturach generuje sygnał SIGFPE . (Również dzielenie najbardziej ujemnej liczby całkowitej przez -1 może generować SIGFPE.) Ignorowanie tego sygnału może prowadzić do nieskończonej pętli .

Teraz ty ignorując sygnał, by nie robić nic, aby temu zapobiec (znowu). Potrzebujesz kontekstu wykonania w procedurze obsługi sygnału i napraw go ręcznie, co wymaga nadpisania niektórych rejestrów.

Jeśli SA_SIGINFO podano w sa_flags, następnie sa_sigaction (zamiast sa_handler) stanowi funkcję sygnału przeładunek signum. Ta funkcja odbiera numer sygnału jako swój pierwszy argument, wskaźnik do siginfo_t jako swój drugi argument, a wskaźnik do ucontext_t (rzutowany na void *) jako trzeci argument. (Powszechnie, funkcja obsługi nie ma żadnego użycia trzeciego argumentu. Zobacz getContext (2) W celu uzyskania dalszych informacji na temat ucontext_t.)

kontekst pozwala na dostęp do rejestrów w czasie awarii i musi zostać zmieniony, aby twój program mógł być kontynuowany. Zobacz ten lkml post. Jak wspomniano, opcja może być również siglongjmp. Post oferuje także dość wielokrotnego użytku rozwiązanie dla obsługi błędu, bez konieczności dokonywania zmiennych globalnych itp .:

A ponieważ obsługiwać go poczuć, masz elastyczność chcesz się z obsługi błędów. Na przykład, można dokonać obsługi błędu skok do jakiegoś określonego punktu w funkcji coś jak tym:

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

Następnie Twój obsługi dla SIGFPE potrzebuje tylko coś zrobić jak

context.eip = context.ecx;

+0

Dziękuję bardzo za odpowiedź informacyjną. Zrobiłem coś podobnego z 'SIGALRM' i złapałem wyjątek i kontynuowałem normalnie, nie wiedziałem, że' SIGFPE' jest wyjątkowy. Jeśli nie żądasz zbyt wiele, czy możesz krótko powiedzieć mi, jak muszę zintegrować kod w poście, aby obsłużyć ten wyjątek? Zakładam, że musiałbym zadeklarować kod zespołu w "głównej" procedurze, ale nie jestem pewien, gdzie umieścić etykietę lub funkcję 'sigfpe_handler' wspomnianą w poście (chyba nie mogę podać jej jako argumentu 'signal', ponieważ argument wskaźnika przyjmuje tylko sygnał int jako argument). – Nobilis

+1

Musisz użyć '' sigaction'' zamiast '' signal''. Struktura '' sigaction'' jest bardziej uniwersalna pod względem ilości opcji, jakie daje. Z drugiej strony, proszę dwukrotnie przeczytać lkml post, nie sądzę, żebym mógł napisać ten kod dla ciebie. –

+0

To byłoby w porządku, dzięki, po prostu potrzebowałem trochę informacji na temat użycia. – Nobilis

5

Ogólnie, tak, wykonanie jest kontynuowane po powrocie modułu obsługi. Ale jeśli sygnał był spowodowany przez błąd sprzętowy (taki jak wyjątek zmiennoprzecinkowy lub błąd segmentacji), nie ma możliwości cofnięcia tego błędu, a więc twój program zostanie zakończony niezależnie.

Innymi słowy, trzeba rozróżniać sygnały od rzeczy, które powodują sygnały. Sygnały same w sobie są idealnie cienkie i dają się obsłużyć, ale nie zawsze pozwalają na naprawę błędów, które powodują sygnały.

(Niektóre sygnały są specjalne, takie jak ABRT i STOP, w tym sensie, że nawet jeśli podnosisz taki sygnał ręcznie za pomocą kill, nadal nie możesz "zapobiec jego efektom." I oczywiście KILL nie może być nawet w ogóle obsłużony.)

+1

SIGSTOP nie może zostać przechwycony lub zignorowany, podobnie jak SIGKILL. – Demi

+0

Twoja odpowiedź podnosi godną uwagi uwagę "odróżnić sygnały od rzeczy, które powodują sygnały". Dzięki. – Pbd

4

Jeśli wiesz, co robisz, możesz ustawić wskaźnik instrukcji tak, aby wskazywał zaraz po instrukcji powodującej naruszenie. Poniżej znajduje się mój przykład dla x86 (32bit i 64bit). Nie próbuj w domu ani w prawdziwych produktach !!!

#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; 
} 
Powiązane problemy