Jak uniknąć używania printf w obsłudze sygnału?

86

Ponieważ printfnie jest ponownie wprowadzany, używanie go w programie obsługi sygnału nie powinno być bezpieczne. Ale widziałem wiele przykładowych kodów, które używają printftego sposobu.

Więc moje pytanie brzmi: kiedy powinniśmy unikać używania printfw module obsługi sygnału i czy istnieje zalecany zamiennik?

Yu Hao
źródło
12
Prosta i niezbyt przydatna odpowiedź na pytanie w twoim tytule: Widzisz to printfwezwanie w tym sygnalizatorze? Usuń to.
Keith Thompson
6
Cześć Yu Hao! Myślę, że znajdziesz link bardzo interesujący do przeczytania. „Użyj funkcji ponownego wejścia dla bezpieczniejszej obsługi sygnału” Przeczytałem to po tak długim czasie, chciałbym podzielić się z wami tym sztucznym. Mam nadzieję, że ci się podoba.
Grijesh Chauhan

Odpowiedzi:

58

Możesz użyć jakiejś zmiennej flag, ustawić tę flagę wewnątrz funkcji obsługi sygnału i na podstawie tej printf()funkcji wywołania flagi w main () lub innej części programu podczas normalnej pracy.

Nie jest bezpieczne wywoływanie wszystkich funkcji, takich jak printfz wnętrza programu obsługi sygnału. Przydatną techniką jest użycie procedury obsługi sygnału do ustawienia a, flaga następnie sprawdzenie tego flag w programie głównym i wydrukowanie komunikatu, jeśli jest to wymagane.

Zauważ, że w poniższym przykładzie program obsługi sygnału ding () ustawia flagę alarm_firedna 1 jako przechwycony SIGALRM, aw funkcji głównej alarm_firedwartość jest sprawdzana, aby warunkowo poprawnie wywołać printf.

static int alarm_fired = 0;
void ding(int sig) // can be called asynchronously
{
  alarm_fired = 1; // set flag
}
int main()
{
    pid_t pid;
    printf("alarm application starting\n");
    pid = fork();
    switch(pid) {
        case -1:
            /* Failure */
            perror("fork failed");
            exit(1);
        case 0:
            /* child */
            sleep(5);
            kill(getppid(), SIGALRM);
            exit(0);
    }
    /* if we get here we are the parent process */
    printf("waiting for alarm to go off\n");
    (void) signal(SIGALRM, ding);
    pause();
    if (alarm_fired)  // check flag to call printf
      printf("Ding!\n");
    printf("done\n");
    exit(0);
}

Źródła: Beginning Linux Programming, 4th Edition , W tej książce wyjaśniono dokładnie Twój kod (czego chcesz), Rozdział 11: Processes and Signals, strona 484

Ponadto należy zachować szczególną ostrożność podczas pisania funkcji obsługi, ponieważ można je wywoływać asynchronicznie. Oznacza to, że procedura obsługi może zostać wywołana w dowolnym momencie programu w nieprzewidywalny sposób. Jeśli dwa sygnały nadejdą w bardzo krótkim czasie, jeden program obsługi może działać w drugim. Uważa się, że lepszą praktyką jest stwierdzenie volatile sigatomic_t, że dostęp do tego typu jest zawsze atomowy, co pozwala uniknąć niepewności co do przerwania dostępu do zmiennej. (czytaj: Atomic Data Access and Signal Handling for detail expiation).

Przeczytaj Definiowanie obsługi sygnałów : aby dowiedzieć się, jak napisać funkcję obsługi sygnału, która może być ustanowiona za pomocą funkcji signal()lub sigaction().
Lista autoryzowanych funkcji na stronie podręcznika , wywołanie tej funkcji w programie obsługi sygnału jest bezpieczne.

Grijesh Chauhan
źródło
18
Uważa się, że lepszą praktyką jest zadeklarowanievolatile sigatomic_t alarm_fired;
Basile Starynkevitch
1
@GrijeshChauhan: jeśli pracujemy w kodzie produktu, nie możemy wywołać funkcji pauzy, przepływ może być w dowolnym miejscu, kiedy pojawi się sygnał, więc w takim przypadku naprawdę nie wiemy, gdzie zachować "if (alarm_fired) printf (" Ding! \ n ");" W kodzie.
pankaj kushwaha
@pankajkushwaha tak, masz rację, cierpi na stan wyścigu
Grijesh Chauhan
@GrijeshChauhan, Są dwie rzeczy, których nie mogłem zrozumieć. 1. Skąd wiesz, kiedy sprawdzić flagę? Czy zatem kod będzie zawierał wiele punktów kontrolnych w prawie każdym miejscu drukowania. 2. Na pewno będą warunki wyścigu, w których sygnał może być wywołany przed rejestracją sygnału lub sygnał może pojawić się za punktem kontrolnym. Myślę, że to tylko pomoże wydrukować w pewnych warunkach, ale nie rozwiązuje całkowicie problemu.
Darshan b
52

Podstawowym problemem jest to, że jeśli sygnał przerwie się malloc()lub zostanie wykonana podobna funkcja, stan wewnętrzny może być tymczasowo niespójny podczas przenoszenia bloków pamięci między listą wolnych i używanych lub innych podobnych operacji. Jeśli kod w module obsługi sygnału wywołuje funkcję, która następnie wywołuje malloc(), może to całkowicie zepsuć zarządzanie pamięcią.

Standard C przyjmuje bardzo konserwatywne podejście do tego, co można zrobić w module obsługi sygnału:

ISO / IEC 9899: 2011 §7.14.1.1 signalFunkcja

¶5 Jeśli sygnał pojawia się inaczej niż w wyniku wywołania funkcji abortlub raise, zachowanie jest niezdefiniowane, jeśli procedura obsługi sygnału odnosi się do dowolnego obiektu ze statycznym lub wątkowym czasem trwania, który nie jest niepodzielnym obiektem bez blokad innym niż przypisanie wartości do obiektu zadeklarowanego jako volatile sig_atomic_tlub program obsługi sygnału wywołuje dowolną funkcję w bibliotece standardowej inną niż abortfunkcja, _Exitfunkcja, quick_exitfunkcja lub signalfunkcja z pierwszym argumentem równym numerowi sygnału odpowiadającemu sygnałowi, który spowodował wywołanie funkcji treser. Ponadto, jeśli takie wywołanie signalfunkcji skutkuje SIG_ERRzwrotem, wartość errnojest nieokreślona. 252)

252) Jeśli jakikolwiek sygnał jest generowany przez asynchroniczną procedurę obsługi sygnału, zachowanie jest niezdefiniowane.

POSIX jest o wiele bardziej szczodry, jeśli chodzi o to, co możesz zrobić w obsłudze sygnału.

Signal Concepts w edycji POSIX 2008 mówi:

Jeśli proces jest wielowątkowy lub jeśli proces jest jednowątkowy, a procedura obsługi sygnału jest wykonywana inaczej niż w wyniku:

  • Powołanie proces abort(), raise(), kill(), pthread_kill(), lub sigqueue(), aby wygenerować sygnał, który nie jest zablokowany

  • Oczekujący sygnał, który został odblokowany i dostarczony przed wywołaniem, które go odblokowało, powraca

zachowanie jest niezdefiniowane, jeśli program obsługi sygnału odnosi się do dowolnego obiektu innego niż errnoze statycznym czasem przechowywania innym niż przypisanie wartości do obiektu zadeklarowanego jako volatile sig_atomic_t, lub jeśli program obsługi sygnału wywołuje jakąkolwiek funkcję zdefiniowaną w tym standardzie, inną niż jedna z funkcji wymienionych w poniższa tabela.

W poniższej tabeli zdefiniowano zestaw funkcji, które powinny być bezpieczne dla sygnału asynchronicznego. Dlatego aplikacje mogą je wywoływać bez ograniczeń z funkcji przechwytujących sygnały:

_Exit()             fexecve()           posix_trace_event() sigprocmask()
_exit()             fork()              pselect()           sigqueue()
…
fcntl()             pipe()              sigpause()          write()
fdatasync()         poll()              sigpending()

Wszystkie funkcje niewymienione w powyższej tabeli są uważane za niebezpieczne w odniesieniu do sygnałów. W obecności sygnałów, wszystkie funkcje zdefiniowane w tym tomie POSIX.1-2008 powinny zachowywać się tak, jak zdefiniowano, gdy są wywoływane z lub przerywane przez funkcję przechwytującą sygnał, z jednym wyjątkiem: gdy sygnał przerywa niebezpieczną funkcję i sygnał - funkcja przechwytująca wywołuje niebezpieczną funkcję, zachowanie jest niezdefiniowane.

Operacje, które uzyskują wartość errnoi operacje przypisujące wartość, errnosą bezpieczne dla sygnału asynchronicznego.

Gdy sygnał jest dostarczany do wątku, jeśli działanie tego sygnału określa zakończenie, zatrzymanie lub kontynuowanie, cały proces powinien być odpowiednio zakończony, zatrzymany lub kontynuowany.

Jednak printf()rodzina funkcji jest wyraźnie nieobecna na tej liście i może nie być wywoływana bezpiecznie z obsługi sygnału.

POSIX 2016 zmiana rozszerza listę bezpiecznych funkcji, obejmujących w szczególności duża liczba funkcji z <string.h>, co jest szczególnie cenne uzupełnienie (lub był to szczególnie frustrujące niedopatrzenie). Lista jest teraz:

_Exit()              getppid()            sendmsg()            tcgetpgrp()
_exit()              getsockname()        sendto()             tcsendbreak()
abort()              getsockopt()         setgid()             tcsetattr()
accept()             getuid()             setpgid()            tcsetpgrp()
access()             htonl()              setsid()             time()
aio_error()          htons()              setsockopt()         timer_getoverrun()
aio_return()         kill()               setuid()             timer_gettime()
aio_suspend()        link()               shutdown()           timer_settime()
alarm()              linkat()             sigaction()          times()
bind()               listen()             sigaddset()          umask()
cfgetispeed()        longjmp()            sigdelset()          uname()
cfgetospeed()        lseek()              sigemptyset()        unlink()
cfsetispeed()        lstat()              sigfillset()         unlinkat()
cfsetospeed()        memccpy()            sigismember()        utime()
chdir()              memchr()             siglongjmp()         utimensat()
chmod()              memcmp()             signal()             utimes()
chown()              memcpy()             sigpause()           wait()
clock_gettime()      memmove()            sigpending()         waitpid()
close()              memset()             sigprocmask()        wcpcpy()
connect()            mkdir()              sigqueue()           wcpncpy()
creat()              mkdirat()            sigset()             wcscat()
dup()                mkfifo()             sigsuspend()         wcschr()
dup2()               mkfifoat()           sleep()              wcscmp()
execl()              mknod()              sockatmark()         wcscpy()
execle()             mknodat()            socket()             wcscspn()
execv()              ntohl()              socketpair()         wcslen()
execve()             ntohs()              stat()               wcsncat()
faccessat()          open()               stpcpy()             wcsncmp()
fchdir()             openat()             stpncpy()            wcsncpy()
fchmod()             pause()              strcat()             wcsnlen()
fchmodat()           pipe()               strchr()             wcspbrk()
fchown()             poll()               strcmp()             wcsrchr()
fchownat()           posix_trace_event()  strcpy()             wcsspn()
fcntl()              pselect()            strcspn()            wcsstr()
fdatasync()          pthread_kill()       strlen()             wcstok()
fexecve()            pthread_self()       strncat()            wmemchr()
ffs()                pthread_sigmask()    strncmp()            wmemcmp()
fork()               raise()              strncpy()            wmemcpy()
fstat()              read()               strnlen()            wmemmove()
fstatat()            readlink()           strpbrk()            wmemset()
fsync()              readlinkat()         strrchr()            write()
ftruncate()          recv()               strspn()
futimens()           recvfrom()           strstr()
getegid()            recvmsg()            strtok_r()
geteuid()            rename()             symlink()
getgid()             renameat()           symlinkat()
getgroups()          rmdir()              tcdrain()
getpeername()        select()             tcflow()
getpgrp()            sem_post()           tcflush()
getpid()             send()               tcgetattr()

W rezultacie albo kończysz używanie write()bez obsługi formatowania zapewnianej przez printf()et al, albo ustawisz flagę, którą testujesz (okresowo) w odpowiednich miejscach w kodzie. Technika ta jest sprawnie wykazano w odpowiedzi przez Grijesh Chauhan .


Standardowe funkcje C i bezpieczeństwo sygnału

chqrlie zadaje interesujące pytanie, na które mam tylko częściową odpowiedź:

Jak to się dzieje, że większość funkcji tekstowych z <string.h>lub funkcje klas znaków z <ctype.h>i wiele innych funkcji biblioteki standardowej C nie znajduje się na powyższej liście? Implementacja musiałaby być celowo zła, aby strlen()wywołanie z modułu obsługi sygnału było niebezpieczne.

Dla wielu z tych funkcji w <string.h>, trudno jest zrozumieć, dlaczego nie zostały one uznane za bezpieczne asynchroniczny sygnał, a ja zgadza się, strlen()jest najlepszym przykładem, wraz z strchr(), strstr()itd Z drugiej strony, inne funkcje, takie jak strtok(), strcoll()i strxfrm()są raczej złożone i prawdopodobnie nie będą bezpieczne dla sygnału asynchronicznego. Ponieważ strtok()zachowuje stan między wywołaniami, a program obsługi sygnału nie może łatwo stwierdzić, czy jakaś część używanego kodu strtok()byłaby pomieszana. Funkcje strcoll()i strxfrm()działają z danymi zależnymi od ustawień regionalnych, a ładowanie ustawień regionalnych obejmuje różnego rodzaju ustawienia stanu.

Wszystkie funkcje (makra) z <ctype.h>są wrażliwe na ustawienia regionalne i dlatego mogą napotkać te same problemy, co strcoll()i strxfrm().

Trudno mi zrozumieć, dlaczego funkcje matematyczne z <math.h>nie są bezpieczne dla sygnału asynchronicznego, chyba że dzieje się tak dlatego, że może na nie wpływać SIGFPE (wyjątek zmiennoprzecinkowy), chociaż mniej więcej jedyny raz, kiedy widzę jeden z tych dni, dotyczy liczby całkowitej dzielenie przez zero. Podobna niepewność powstaje <complex.h>, <fenv.h>i <tgmath.h>.

Niektóre funkcje <stdlib.h>mogą być wyłączone - abs()na przykład. Inne są szczególnie problematyczne: malloc()a rodzina jest najlepszym przykładem.

Podobną ocenę można przeprowadzić dla innych nagłówków w Standardzie C (2011) używanych w środowisku POSIX. (Standard C jest tak restrykcyjny, że nie interesuje go analizowanie w czystym środowisku Standard C). Te oznaczone jako „zależne od ustawień regionalnych” są niebezpieczne, ponieważ manipulowanie lokalizacjami może wymagać alokacji pamięci itp.

  • <assert.h>- Prawdopodobnie nie jest bezpieczny
  • <complex.h>- Prawdopodobnie bezpieczny
  • <ctype.h> - Nie jest bezpieczne
  • <errno.h> - Bezpieczny
  • <fenv.h>- Prawdopodobnie nie jest bezpieczny
  • <float.h> - Brak funkcji
  • <inttypes.h> - Funkcje zależne od lokalizacji (niebezpieczne)
  • <iso646.h> - Brak funkcji
  • <limits.h> - Brak funkcji
  • <locale.h> - Funkcje zależne od lokalizacji (niebezpieczne)
  • <math.h>- Prawdopodobnie bezpieczny
  • <setjmp.h> - Nie jest bezpieczne
  • <signal.h> - Dozwolony
  • <stdalign.h> - Brak funkcji
  • <stdarg.h> - Brak funkcji
  • <stdatomic.h>- Prawdopodobnie bezpieczne, prawdopodobnie niebezpieczne
  • <stdbool.h> - Brak funkcji
  • <stddef.h> - Brak funkcji
  • <stdint.h> - Brak funkcji
  • <stdio.h> - Nie jest bezpieczne
  • <stdlib.h> - Nie wszystkie są bezpieczne (niektóre są dozwolone, inne nie)
  • <stdnoreturn.h> - Brak funkcji
  • <string.h> - Nie wszystko jest bezpieczne
  • <tgmath.h>- Prawdopodobnie bezpieczny
  • <threads.h>- Prawdopodobnie nie jest bezpieczny
  • <time.h>- Zależne od lokalizacji (ale time()jest wyraźnie dozwolone)
  • <uchar.h> - Zależne od lokalizacji
  • <wchar.h> - Zależne od lokalizacji
  • <wctype.h> - Zależne od lokalizacji

Analiza nagłówków POSIX byłaby… trudniejsza, ponieważ jest ich dużo, a niektóre funkcje mogą być bezpieczne, ale wiele nie będzie… ale także prostsze, ponieważ POSIX mówi, które funkcje są bezpieczne dla sygnału asynchronicznego (niewiele z nich). Zwróć uwagę, że nagłówek taki jak <pthread.h>ma trzy bezpieczne funkcje i wiele niebezpiecznych funkcji.

Uwaga: Prawie cała ocena funkcji i nagłówków C w środowisku POSIX to częściowo wyuczone domysły. Nie ma sensu ostateczne oświadczenie organu normalizacyjnego.

Jonathan Leffler
źródło
Jak to się dzieje, że większość funkcji tekstowych z <string.h>lub funkcje klas znaków z <ctype.h>i wiele innych funkcji biblioteki standardowej C nie znajduje się na powyższej liście? Implementacja musiałaby być celowo zła, aby strlen()wywołanie z modułu obsługi sygnału było niebezpieczne.
chqrlie,
@chqrlie: ciekawe pytanie - zobacz aktualizację (nie było sposobu, aby rozsądnie dopasować tak wiele do komentarzy).
Jonathan Leffler
Dziękuję za dogłębną analizę. Jeśli chodzi o <ctype.h>rzeczy, jest to specyficzne dla lokalizacji i może powodować problemy, jeśli sygnał przerywa funkcję ustawiania ustawień regionalnych, ale po załadowaniu ustawień regionalnych używanie ich powinno być bezpieczne. Wydaje mi się, że w niektórych złożonych sytuacjach ładowanie danych regionalnych może odbywać się przyrostowo, przez co funkcje <ctype.h>stają się niebezpieczne. Wniosek pozostaje: jeśli masz wątpliwości, wstrzymaj się od głosu.
chqrlie
@chqrlie: Zgadzam się, że morał tej historii powinien być taki, że w razie wątpliwości wstrzymaj się od głosu . To miłe podsumowanie.
Jonathan Leffler
13

Jak uniknąć używania printfw obsłudze sygnału?

  1. Zawsze tego unikaj, powie: Po prostu nie używaj printf()w obsłudze sygnałów.

  2. Przynajmniej w systemach zgodnych z POSIX możesz używać write(STDOUT_FILENO, ...)zamiast printf(). Jednak formatowanie może nie być łatwe: Wydrukuj int z programu obsługi sygnału przy użyciu funkcji zapisu lub funkcji asynchronicznych

alk
źródło
1
Alk Always avoid it.znaczy? Unikać printf()?
Grijesh Chauhan
2
@GrijeshChauhan: Tak, ponieważ OP pytał, kiedy unikać używania printf()w obsłudze sygnałów.
alk
Powiedz +1 za 2punkt, sprawdź OP z pytaniem Jak unikać używania printf()w obsłudze sygnału?
Grijesh Chauhan
7

Do celów debugowania napisałem narzędzie, które sprawdza, czy w rzeczywistości wywołujesz tylko funkcje z async-signal-safelisty i wyświetla komunikat ostrzegawczy dla każdej niebezpiecznej funkcji wywoływanej w kontekście sygnału. Chociaż nie rozwiązuje to problemu chęci wywoływania funkcji niezabezpieczonych asynchronicznie z kontekstu sygnału, pomaga przynajmniej znaleźć przypadki, w których zrobiłeś to przypadkowo.

Kod źródłowy znajduje się na GitHub . Działa poprzez przeciążenie signal/sigaction, a następnie tymczasowe przejmowanie PLTwpisów niebezpiecznych funkcji; Powoduje to przekierowanie wywołań niebezpiecznych funkcji do opakowania.

dwks
źródło
Żądanie funkcji GCC: gcc.gnu.org/ml/gcc-help/2012-03/msg00210.html
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1

Zaimplementuj własny bezpieczny sygnał asynchroniczny snprintf("%di korzystaj z niegowrite

Nie jest tak źle, jak myślałem, jak przekonwertować int na string w C? ma kilka implementacji.

Ponieważ istnieją tylko dwa interesujące typy danych, do których mają dostęp procedury obsługi sygnałów:

  • sig_atomic_t globalne
  • int argument sygnału

to w zasadzie obejmuje wszystkie interesujące przypadki użycia.

Fakt, że strcpyjest również bezpieczny dla sygnału, czyni wszystko jeszcze lepszym.

Poniższy program POSIX drukuje na standardowe wyjście, ile razy otrzymał SIGINT do tej pory, którą można wyzwolić Ctrl + C, oraz identyfikator sygnału i.

Możesz wyjść z programu za pomocą Ctrl + \(SIGQUIT).

main.c:

#define _XOPEN_SOURCE 700
#include <assert.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>

/* Calculate the minimal buffer size for a given type.
 *
 * Here we overestimate and reserve 8 chars per byte.
 *
 * With this size we could even print a binary string.
 *
 * - +1 for NULL terminator
 * - +1 for '-' sign
 *
 * A tight limit for base 10 can be found at:
 * /programming/8257714/how-to-convert-an-int-to-string-in-c/32871108#32871108
 *
 * TODO: get tight limits for all bases, possibly by looking into
 * glibc's atoi: /programming/190229/where-is-the-itoa-function-in-linux/52127877#52127877
 */
#define ITOA_SAFE_STRLEN(type) sizeof(type) * CHAR_BIT + 2

/* async-signal-safe implementation of integer to string conversion.
 *
 * Null terminates the output string.
 *
 * The input buffer size must be large enough to contain the output,
 * the caller must calculate it properly.
 *
 * @param[out] value  Input integer value to convert.
 * @param[out] result Buffer to output to.
 * @param[in]  base   Base to convert to.
 * @return     Pointer to the end of the written string.
 */
char *itoa_safe(intmax_t value, char *result, int base) {
    intmax_t tmp_value;
    char *ptr, *ptr2, tmp_char;
    if (base < 2 || base > 36) {
        return NULL;
    }

    ptr = result;
    do {
        tmp_value = value;
        value /= base;
        *ptr++ = "ZYXWVUTSRQPONMLKJIHGFEDCBA9876543210123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[35 + (tmp_value - value * base)];
    } while (value);
    if (tmp_value < 0)
        *ptr++ = '-';
    ptr2 = result;
    result = ptr;
    *ptr-- = '\0';
    while (ptr2 < ptr) {
        tmp_char = *ptr;
        *ptr--= *ptr2;
        *ptr2++ = tmp_char;
    }
    return result;
}

volatile sig_atomic_t global = 0;

void signal_handler(int sig) {
    char key_str[] = "count, sigid: ";
    /* This is exact:
     * - the null after the first int will contain the space
     * - the null after the second int will contain the newline
     */
    char buf[2 * ITOA_SAFE_STRLEN(sig_atomic_t) + sizeof(key_str)];
    enum { base = 10 };
    char *end;
    end = buf;
    strcpy(end, key_str);
    end += sizeof(key_str);
    end = itoa_safe(global, end, base);
    *end++ = ' ';
    end = itoa_safe(sig, end, base);
    *end++ = '\n';
    write(STDOUT_FILENO, buf, end - buf);
    global += 1;
    signal(sig, signal_handler);
}

int main(int argc, char **argv) {
    /* Unit test itoa_safe. */
    {
        typedef struct {
            intmax_t n;
            int base;
            char out[1024];
        } InOut;
        char result[1024];
        size_t i;
        InOut io;
        InOut ios[] = {
            /* Base 10. */
            {0, 10, "0"},
            {1, 10, "1"},
            {9, 10, "9"},
            {10, 10, "10"},
            {100, 10, "100"},
            {-1, 10, "-1"},
            {-9, 10, "-9"},
            {-10, 10, "-10"},
            {-100, 10, "-100"},

            /* Base 2. */
            {0, 2, "0"},
            {1, 2, "1"},
            {10, 2, "1010"},
            {100, 2, "1100100"},
            {-1, 2, "-1"},
            {-100, 2, "-1100100"},

            /* Base 35. */
            {0, 35, "0"},
            {1, 35, "1"},
            {34, 35, "Y"},
            {35, 35, "10"},
            {100, 35, "2U"},
            {-1, 35, "-1"},
            {-34, 35, "-Y"},
            {-35, 35, "-10"},
            {-100, 35, "-2U"},
        };
        for (i = 0; i < sizeof(ios)/sizeof(ios[0]); ++i) {
            io = ios[i];
            itoa_safe(io.n, result, io.base);
            if (strcmp(result, io.out)) {
                printf("%ju %d %s\n", io.n, io.base, io.out);
                assert(0);
            }
        }
    }

    /* Handle the signals. */
    if (argc > 1 && !strcmp(argv[1], "1")) {
        signal(SIGINT, signal_handler);
        while(1);
    }

    return EXIT_SUCCESS;
}

Skompiluj i uruchom:

gcc -std=c99 -Wall -Wextra -o main main.c
./main 1

Po piętnastokrotnym naciśnięciu Ctrl + C terminal wyświetla:

^Ccount, sigid: 0 2
^Ccount, sigid: 1 2
^Ccount, sigid: 2 2
^Ccount, sigid: 3 2
^Ccount, sigid: 4 2
^Ccount, sigid: 5 2
^Ccount, sigid: 6 2
^Ccount, sigid: 7 2
^Ccount, sigid: 8 2
^Ccount, sigid: 9 2
^Ccount, sigid: 10 2
^Ccount, sigid: 11 2
^Ccount, sigid: 12 2
^Ccount, sigid: 13 2
^Ccount, sigid: 14 2

gdzie 2jest numer sygnału dla SIGINT.

Testowane na Ubuntu 18.04. GitHub upstream .

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
0

Jedną z technik, która jest szczególnie użyteczna w programach, które mają pętlę wyboru, jest zapisanie pojedynczego bajtu potokiem po odebraniu sygnału, a następnie obsługa sygnału w pętli wyboru. Coś w tym kierunku (obsługa błędów i inne szczegóły pominięte dla zwięzłości) :

static int sigPipe[2];

static void gotSig ( int num ) { write(sigPipe[1], "!", 1); }

int main ( void ) {
    pipe(sigPipe);
    /* use sigaction to point signal(s) at gotSig() */

    FD_SET(sigPipe[0], &readFDs);

    for (;;) {
        n = select(nFDs, &readFDs, ...);
        if (FD_ISSET(sigPipe[0], &readFDs)) {
            read(sigPipe[0], ch, 1);
            /* do something about the signal here */
        }
        /* ... the rest of your select loop */
    }
}

Jeśli obchodzi Cię, który to był sygnał, bajt w dół potoku może być numerem sygnału.

John Hascall
źródło
-1

Możesz używać printf w programach obsługi sygnałów, jeśli używasz biblioteki pthread. unix / posix określa, że ​​printf jest atomowy dla wątków, patrz odpowiedź Dave'a Butenhof tutaj: https://groups.google.com/forum/#!topic/comp.programming.threads/1-bU71nYgqw Zauważ, że aby uzyskać wyraźniejszy obraz wyjścia printf, powinieneś uruchomić swoją aplikację w konsoli (w Linuksie użyj ctl + alt + f1, aby uruchomić konsolę 1), zamiast pseudo-tty utworzonego przez GUI.

drlolly
źródło
3
Programy obsługi sygnałów nie działają w jakimś oddzielnym wątku, działają w kontekście wątku, który był uruchomiony, gdy nastąpiło przerwanie sygnału. Ta odpowiedź jest całkowicie błędna.
itaych