Wie vermeide ich die Verwendung von printf in einem Signalhandler?


85

Da printfes nicht wiedereintrittsfähig ist, sollte es nicht sicher sein, es in einem Signalhandler zu verwenden. Aber ich habe viele Beispielcodes gesehen, die printfdiesen Weg verwenden.

Meine Frage lautet also: Wann müssen wir die Verwendung printfin einem Signalhandler vermeiden , und gibt es einen empfohlenen Ersatz?


12
Eine einfache und nicht sehr nützliche Antwort auf die Frage in Ihrem Titel: Sehen Sie diesen printfAnruf in diesem Signalhandler? Lösche es.
Keith Thompson

5
Hallo Yu Hao! Ich denke, Sie finden den Link sehr interessant zu lesen. "Verwenden Sie Wiedereintrittsfunktionen für eine sicherere Signalverarbeitung" Ich habe es nach so langer Zeit gelesen, dass ich das Künstliche hier mit Ihnen teilen möchte. Ich hoffe dir gefällt es.
Grijesh Chauhan

Antworten:


57

Sie können eine Flag-Variable verwenden, dieses Flag im Signalhandler setzen und printf()während des normalen Betriebs auf dieser Flag-Aufruffunktion in main () oder einem anderen Teil des Programms basieren .

Es ist nicht sicher, alle Funktionen, z. B. printfinnerhalb eines Signalhandlers, aufzurufen. Eine nützliche Technik besteht darin, einen Signalhandler zu verwenden, um ein zu setzen flagund dies dann flag im Hauptprogramm zu überprüfen und bei Bedarf eine Nachricht zu drucken.

Beachten Sie im folgenden Beispiel, dass der Signalhandler ding () ein Flag alarm_firedauf 1 setzt, wenn SIGALRM abgefangen wird, und dass der alarm_firedWert der Hauptfunktion überprüft wird, um printf unter bestimmten Bedingungen korrekt aufzurufen.

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

Referenz: Beginn der Linux-Programmierung, 4. Ausgabe , In diesem Buch wird genau Ihr Code erklärt (was Sie wollen), Kapitel 11: Prozesse und Signale, Seite 484

Darüber hinaus müssen Sie beim Schreiben von Handlerfunktionen besondere Sorgfalt walten lassen, da diese asynchron aufgerufen werden können. Das heißt, ein Handler kann an jedem Punkt des Programms unvorhersehbar aufgerufen werden. Wenn zwei Signale in einem sehr kurzen Intervall eintreffen, kann ein Handler in einem anderen ausgeführt werden. Es wird als bessere Vorgehensweise angesehen, zu deklarieren volatile sigatomic_t, dass auf diesen Typ immer atomar zugegriffen wird, um Unsicherheiten hinsichtlich der Unterbrechung des Zugriffs auf eine Variable zu vermeiden. (Lesen Sie: Atomic Data Access und Signal Handling für Detail-Sühne).

Lesen Definieren von Signalhandlern : Hier erfahren Sie, wie Sie eine Signalhandlerfunktion schreiben, die mit den Funktionen signal()oder eingerichtet werden sigaction()kann.
Liste der autorisierten Funktionen auf der Handbuchseite . Das Aufrufen dieser Funktion im Signalhandler ist sicher.


17
Es wird als bessere Praxis angesehen, zu erklärenvolatile sigatomic_t alarm_fired;
Basile Starynkevitch


1
@GrijeshChauhan: Wenn wir in einem Produktcode arbeiten, können wir die Pausenfunktion nicht aufrufen. Der Fluss kann überall sein, wenn ein Signal auftritt. In diesem Fall wissen wir wirklich nicht, wo wir "if (alarm_fired) printf (" Ding! \ n ");" in Code.
Pankaj Kushwaha

@pankajkushwaha ja, Sie sind richtig, es leidet unter Rennbedingungen
Grijesh Chauhan

@GrijeshChauhan, Es gibt zwei Dinge, die ich nicht verstehen konnte. 1. Woher wissen Sie, wann Sie die Flagge überprüfen müssen? Es gibt also an fast jedem zu druckenden Punkt mehrere Prüfpunkte im Code. 2. Es wird definitiv Rennbedingungen geben, unter denen das Signal vor der Signalregistrierung aufgerufen werden kann oder das Signal nach dem Kontrollpunkt auftreten kann. Ich denke, dies hilft dem Druck nur unter bestimmten Bedingungen, löst das Problem jedoch nicht vollständig.
Darshan b

52

Das Hauptproblem besteht darin, dass, wenn das Signal unterbricht malloc()oder eine ähnliche Funktion vorliegt, der interne Zustand vorübergehend inkonsistent sein kann, während Speicherblöcke zwischen der freien und der verwendeten Liste oder anderen ähnlichen Operationen verschoben werden. Wenn der Code im Signalhandler eine Funktion aufruft malloc(), die dann aufgerufen wird , kann dies die Speicherverwaltung vollständig ruinieren.

Der C-Standard betrachtet sehr konservativ, was Sie in einem Signalhandler tun können:

ISO / IEC 9899: 2011 §7.14.1.1 Die signalFunktion

¶5 Wenn das Signal nicht als Ergebnis des Aufrufs der Funktion abortoder auftritt raise, ist das Verhalten undefiniert, wenn der Signalhandler auf ein Objekt mit statischer oder Thread-Speicherdauer verweist, das kein sperrfreies atomares Objekt ist, außer durch Zuweisen eines Werts für ein Objekt, das als deklariert ist volatile sig_atomic_t, oder der Signalhandler ruft eine andere Funktion in der Standardbibliothek als die abortFunktion, die _ExitFunktion, die quick_exitFunktion oder die signalFunktion auf, wobei das erste Argument der Signalnummer entspricht, die dem Signal entspricht, das den Aufruf von verursacht hat Handler. Wenn ein solcher Aufruf der signalFunktion zu einer SIG_ERRRückgabe führt, ist der Wert von errnounbestimmt. 252)

252) Wenn ein Signal von einem asynchronen Signalhandler erzeugt wird, ist das Verhalten undefiniert.

POSIX ist viel großzügiger in Bezug auf das, was Sie in einem Signalhandler tun können.

Signal Concepts in der POSIX 2008-Ausgabe lautet:

Wenn der Prozess Multithreading ist oder wenn der Prozess Single-Threaded ist und ein Signalhandler ausgeführt wird, der nicht das Ergebnis von:

  • Der Prozess calling abort(), raise(), kill(), pthread_kill(), oder sigqueue()ein Signal zu erzeugen , das nicht blockiert ist

  • Ein anstehendes Signal, das entsperrt und vor dem nicht gesperrten Anruf zugestellt wird, kehrt zurück

Das Verhalten ist undefiniert, wenn der Signalhandler auf ein anderes Objekt als errnomit einer anderen statischen Speicherdauer verweist, als indem er einem als deklarierten Objekt einen Wert zuweist volatile sig_atomic_t, oder wenn der Signalhandler eine in diesem Standard definierte Funktion außer einer der in aufgeführten Funktionen aufruft die folgende Tabelle.

Die folgende Tabelle definiert eine Reihe von Funktionen, die asynchron-signal-sicher sein sollen. Daher können Anwendungen sie ohne Einschränkung über Signalfangfunktionen aufrufen:

_Exit()             fexecve()           posix_trace_event() sigprocmask()
_exit()             fork()              pselect()           sigqueue()

fcntl()             pipe()              sigpause()          write()
fdatasync()         poll()              sigpending()

Alle Funktionen, die nicht in der obigen Tabelle aufgeführt sind, gelten in Bezug auf Signale als unsicher. Bei Vorhandensein von Signalen müssen sich alle Funktionen, die durch dieses Volume von POSIX.1-2008 definiert sind, so verhalten, wie sie definiert sind, wenn sie von einer Signalfangfunktion aufgerufen oder unterbrochen werden, mit einer einzigen Ausnahme: Wenn ein Signal eine unsichere Funktion und das Signal unterbricht. Die Fangfunktion ruft eine unsichere Funktion auf, das Verhalten ist undefiniert.

Operationen, die den Wert von erhalten, errnound Operationen, denen ein Wert zugewiesen wird, errnomüssen asynchron-signal-sicher sein.

Wenn ein Signal an einen Thread gesendet wird und die Aktion dieses Signals die Beendigung, den Stopp oder die Fortsetzung angibt, muss der gesamte Prozess beendet, gestoppt oder fortgesetzt werden.

Die printf()Funktionsfamilie fehlt jedoch insbesondere in dieser Liste und kann von einem Signalhandler möglicherweise nicht sicher aufgerufen werden.

Das POSIX 2016- Update erweitert die Liste der sicheren Funktionen um insbesondere eine große Anzahl von Funktionen <string.h>, was eine besonders wertvolle Ergänzung darstellt (oder ein besonders frustrierendes Versehen war). Die Liste ist jetzt:

_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()

Infolgedessen verwenden Sie entweder write()ohne die von printf()et al. Bereitgestellte Formatierungsunterstützung oder setzen ein Flag, das Sie (regelmäßig) an geeigneten Stellen in Ihrem Code testen. Diese Technik wird in der Antwort von Grijesh Chauhan geschickt demonstriert .


Standard C-Funktionen und Signalsicherheit

chqrlie stellt eine interessante Frage, auf die ich nur eine teilweise Antwort habe:

Wie kommt es, dass die meisten Zeichenfolgenfunktionen <string.h>oder die Zeichenklassenfunktionen von <ctype.h>und viele weitere C-Standardbibliotheksfunktionen nicht in der obigen Liste enthalten sind? Eine Implementierung müsste absichtlich böse sein, um strlen()den Anruf von einem Signalhandler unsicher zu machen .

Für viele der Funktionen in <string.h>, ist es schwer zu sehen , warum wurden sie nicht Asynchron-Signal sicher erklärt, und ich würde zustimmen das strlen()ist ein gutes Beispiel, zusammen mit strchr(), strstr()usw. Auf der anderen Seite, anderen Funktionen wie strtok(), strcoll()und strxfrm()sind ziemlich komplex und wahrscheinlich nicht asynchronsignalsicher. Weil strtok()der Status zwischen den Aufrufen erhalten bleibt und der Signalhandler nicht leicht erkennen konnte, ob ein Teil des verwendeten Codes strtok()durcheinander geraten würde. Die Funktionen strcoll()und strxfrm()arbeiten mit länderspezifischen Daten, und das Laden des Gebietsschemas umfasst alle Arten von Statuseinstellungen.

Die Funktionen (Makros) von <ctype.h>sind alle länderspezifisch und können daher auf dieselben Probleme wie strcoll()und stoßen strxfrm().

<math.h>Es fällt mir schwer zu verstehen, warum die mathematischen Funktionen von nicht asynchronsignalsicher sind, es sei denn, sie könnten durch eine SIGFPE (Gleitkomma-Ausnahme) beeinflusst werden, obwohl ich heutzutage nur eine dieser Zahlen für Ganzzahlen sehe Durch Null teilen. Ähnliche Unsicherheit ergibt sich aus <complex.h>, <fenv.h>und <tgmath.h>.

Einige der Funktionen in <stdlib.h>könnten beispielsweise ausgenommen werden abs(). Andere sind besonders problematisch: malloc()und Familie sind Paradebeispiele.

Eine ähnliche Bewertung könnte für die anderen Header in Standard C (2011) vorgenommen werden, die in einer POSIX-Umgebung verwendet werden. (Standard C ist so restriktiv, dass kein Interesse daran besteht, sie in einer reinen Standard C-Umgebung zu analysieren.) Die als "Gebietsschema-abhängig" gekennzeichneten sind unsicher, da für die Bearbeitung von Gebietsschemas möglicherweise eine Speicherzuweisung usw. erforderlich ist.

  • <assert.h>- Wahrscheinlich nicht sicher
  • <complex.h>- Möglicherweise sicher
  • <ctype.h> - Nicht sicher
  • <errno.h> - Sicher
  • <fenv.h>- Wahrscheinlich nicht sicher
  • <float.h> - Keine Funktionen
  • <inttypes.h> - Gebietsschemasensitive Funktionen (unsicher)
  • <iso646.h> - Keine Funktionen
  • <limits.h> - Keine Funktionen
  • <locale.h> - Gebietsschemasensitive Funktionen (unsicher)
  • <math.h>- Möglicherweise sicher
  • <setjmp.h> - Nicht sicher
  • <signal.h> - Erlaubt
  • <stdalign.h> - Keine Funktionen
  • <stdarg.h> - Keine Funktionen
  • <stdatomic.h>- Möglicherweise sicher, wahrscheinlich nicht sicher
  • <stdbool.h> - Keine Funktionen
  • <stddef.h> - Keine Funktionen
  • <stdint.h> - Keine Funktionen
  • <stdio.h> - Nicht sicher
  • <stdlib.h> - Nicht alle sicher (einige sind erlaubt, andere nicht)
  • <stdnoreturn.h> - Keine Funktionen
  • <string.h> - Nicht alle sicher
  • <tgmath.h>- Möglicherweise sicher
  • <threads.h>- Wahrscheinlich nicht sicher
  • <time.h>- Gebietsschemaabhängig (ist aber time()ausdrücklich erlaubt)
  • <uchar.h> - Gebietsschemaabhängig
  • <wchar.h> - Gebietsschemaabhängig
  • <wctype.h> - Gebietsschemaabhängig

Das Analysieren der POSIX-Header wäre… schwieriger, da es viele davon gibt und einige Funktionen möglicherweise sicher sind, viele jedoch nicht… aber auch einfacher, da POSIX angibt, welche Funktionen für asynchrone Signale sicher sind (nicht viele von ihnen). Beachten Sie, dass ein Header wie <pthread.h>drei sichere Funktionen und viele unsichere Funktionen hat.

NB: Fast die gesamte Bewertung von C-Funktionen und -Headern in einer POSIX-Umgebung ist eine halbherzige Vermutung. Es macht keinen Sinn, eine endgültige Aussage eines Normungsgremiums zu treffen.


Wie kommt es, dass die meisten Zeichenfolgenfunktionen <string.h>oder die Zeichenklassenfunktionen von <ctype.h>und viele weitere C-Standardbibliotheksfunktionen nicht in der obigen Liste enthalten sind? Eine Implementierung müsste absichtlich böse sein, um strlen()den Anruf von einem Signalhandler unsicher zu machen .
Chqrlie

@chqrlie: interessante Frage - siehe das Update (es gab keine Möglichkeit, so viel sinnvoll in Kommentare zu passen).
Jonathan Leffler

Vielen Dank für Ihre eingehende Analyse. In Bezug auf das <ctype.h>Material ist es länderspezifisch und kann Probleme verursachen, wenn das Signal eine Funktion zur Einstellung des Gebietsschemas unterbricht. Sobald das Gebietsschema geladen ist, sollte die Verwendung sicher sein. Ich denke, in einigen komplexen Situationen könnte das Laden der Gebietsschemadaten schrittweise erfolgen, wodurch die Funktionen <ctype.h>unsicher werden. Die Schlussfolgerung bleibt: Wenn Sie Zweifel haben, enthalten Sie sich.
Chqrlie

@chqrlie: Ich stimme zu, dass die Moral der Geschichte sein sollte. Wenn Sie Zweifel haben, enthalten Sie sich . Das ist eine schöne Zusammenfassung.
Jonathan Leffler

13

Wie vermeide ich die Verwendung printfin einem Signalhandler?

  1. Vermeiden Sie es immer, wird sagen: Nur nicht printf()in Signalhandlern verwenden.

  2. Zumindest auf POSIX-konformen Systemen können Sie write(STDOUT_FILENO, ...)stattdessen verwenden printf(). Die Formatierung ist jedoch möglicherweise nicht einfach: Drucken Sie int vom Signalhandler mithilfe von Schreib- oder asynchronen Funktionen


1
Alk Always avoid it.bedeutet? Vermeiden printf()?
Grijesh Chauhan

2
@GrijeshChauhan: Ja, da das OP gefragt hat, wann die Verwendung printf()in Signalhandlern vermieden werden soll.
Alk

Alk +1 für 2Punkt, überprüfen Sie OP und fragen Sie, wie Sie die Verwendung printf()in Signalhandlern vermeiden können .
Grijesh Chauhan

7

Zu Debugging-Zwecken habe ich ein Tool geschrieben, das überprüft, ob Sie tatsächlich nur Funktionen in der async-signal-safeListe aufrufen , und eine Warnmeldung für jede unsichere Funktion druckt, die in einem Signalkontext aufgerufen wird. Es löst zwar nicht das Problem, nicht asynchronsichere Funktionen aus einem Signalkontext aufrufen zu wollen, hilft Ihnen jedoch zumindest dabei, Fälle zu finden, in denen Sie dies versehentlich getan haben.

Der Quellcode ist auf GitHub . Es funktioniert durch Überladen signal/sigactionund anschließendes vorübergehendes Entführen der PLTEinträge unsicherer Funktionen. Dadurch werden Aufrufe unsicherer Funktionen an einen Wrapper umgeleitet.



1

Implementieren Sie Ihren eigenen Async-Signal-Safe snprintf("%dund verwenden Sie ihnwrite

Es ist nicht so schlimm wie ich dachte: Wie konvertiere ich ein int in einen String in C?hat mehrere Implementierungen.

Da es nur zwei interessante Datentypen gibt, auf die Signalhandler zugreifen können:

  • sig_atomic_t Globale
  • int Signalargument

Dies deckt grundsätzlich alle interessanten Anwendungsfälle ab.

Die Tatsache, dass strcpyauch signal sicher ist, macht die Dinge noch besser.

Das folgende POSIX-Programm druckt, um anzugeben, wie oft SIGINT bisher empfangen wurde, mit dem Sie auslösen können Ctrl + C, sowie die Signal-ID und.

Sie können das Programm mit Ctrl + \(SIGQUIT) beenden.

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

Kompilieren und ausführen:

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

Nach fünfzehnmaligem Drücken von Strg + C zeigt das Terminal Folgendes an:

^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

wo 2ist die Signalnummer für SIGINT.

Getestet unter Ubuntu 18.04. GitHub stromaufwärts .


0

Eine Technik, die besonders in Programmen mit einer Auswahlschleife nützlich ist, besteht darin, beim Empfang eines Signals ein einzelnes Byte in eine Pipe zu schreiben und dann das Signal in der Auswahlschleife zu verarbeiten. Etwas in diese Richtung (Fehlerbehandlung und andere Details wurden der Kürze halber weggelassen) :

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 */
    }
}

Wenn es Ihnen wichtig ist , um welches Signal es sich handelt, kann das Byte in der Pipe die Signalnummer sein.


-1

Sie können printf in Signalhandlern verwenden, wenn Sie die pthread-Bibliothek verwenden. unix / posix gibt an, dass printf für Threads atomar ist, siehe Antwort von Dave Butenhof hier: https://groups.google.com/forum/#!topic/comp.programming.threads/1-bU71nYgqw Beachten Sie dies, um ein klareres Bild zu erhalten Bei der Ausgabe von printf sollten Sie Ihre Anwendung in einer Konsole ausführen (unter Linux verwenden Sie ctl + alt + f1, um Konsole 1 zu starten) und nicht in einer von der GUI erstellten Pseudo-Datei.


3
Signalhandler werden nicht in einem separaten Thread ausgeführt, sondern im Kontext des Threads, der zum Zeitpunkt der Signalunterbrechung ausgeführt wurde. Diese Antwort ist völlig falsch.
Itaych
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.