Da printf
es nicht wiedereintrittsfähig ist, sollte es nicht sicher sein, es in einem Signalhandler zu verwenden. Aber ich habe viele Beispielcodes gesehen, die printf
diesen Weg verwenden.
Meine Frage lautet also: Wann müssen wir die Verwendung printf
in einem Signalhandler vermeiden , und gibt es einen empfohlenen Ersatz?
printf
Anruf in diesem Signalhandler? Lösche es.Antworten:
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 .Beachten Sie im folgenden Beispiel, dass der Signalhandler ding () ein Flag
alarm_fired
auf 1 setzt, wenn SIGALRM abgefangen wird, und dass deralarm_fired
Wert der Hauptfunktion überprüft wird, um printf unter bestimmten Bedingungen korrekt aufzurufen.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 werdensigaction()
kann.Liste der autorisierten Funktionen auf der Handbuchseite . Das Aufrufen dieser Funktion im Signalhandler ist sicher.
quelle
volatile sigatomic_t alarm_fired;
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 aufruftmalloc()
, 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:
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:
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:Infolgedessen verwenden Sie entweder
write()
ohne die vonprintf()
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:
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 dasstrlen()
ist ein gutes Beispiel, zusammen mitstrchr()
,strstr()
usw. Auf der anderen Seite, anderen Funktionen wiestrtok()
,strcoll()
undstrxfrm()
sind ziemlich komplex und wahrscheinlich nicht asynchronsignalsicher. Weilstrtok()
der Status zwischen den Aufrufen erhalten bleibt und der Signalhandler nicht leicht erkennen konnte, ob ein Teil des verwendeten Codesstrtok()
durcheinander geraten würde. Die Funktionenstrcoll()
undstrxfrm()
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 wiestrcoll()
und stoßenstrxfrm()
.<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 werdenabs()
. 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 abertime()
ausdrücklich erlaubt)<uchar.h>
- Gebietsschemaabhängig<wchar.h>
- Gebietsschemaabhängig<wctype.h>
- GebietsschemaabhängigDas 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.
quelle
<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, umstrlen()
den Anruf von einem Signalhandler unsicher zu machen .<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.Vermeiden Sie es immer, wird sagen: Nur nicht
printf()
in Signalhandlern verwenden.Zumindest auf POSIX-konformen Systemen können Sie
write(STDOUT_FILENO, ...)
stattdessen verwendenprintf()
. Die Formatierung ist jedoch möglicherweise nicht einfach: Drucken Sie int vom Signalhandler mithilfe von Schreib- oder asynchronen Funktionenquelle
Always avoid it.
bedeutet? Vermeidenprintf()
?printf()
in Signalhandlern vermieden werden soll.2
Punkt, überprüfen Sie OP und fragen Sie, wie Sie die Verwendungprintf()
in Signalhandlern vermeiden können .Zu Debugging-Zwecken habe ich ein Tool geschrieben, das überprüft, ob Sie tatsächlich nur Funktionen in der
async-signal-safe
Liste 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/sigaction
und anschließendes vorübergehendes Entführen derPLT
Einträge unsicherer Funktionen. Dadurch werden Aufrufe unsicherer Funktionen an einen Wrapper umgeleitet.quelle
Implementieren Sie Ihren eigenen Async-Signal-Safe
snprintf("%d
und 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
Globaleint
SignalargumentDies deckt grundsätzlich alle interessanten Anwendungsfälle ab.
Die Tatsache, dass
strcpy
auch 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:
Kompilieren und ausführen:
Nach fünfzehnmaligem Drücken von Strg + C zeigt das Terminal Folgendes an:
wo
2
ist die Signalnummer fürSIGINT
.Getestet unter Ubuntu 18.04. GitHub stromaufwärts .
quelle
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) :
Wenn es Ihnen wichtig ist , um welches Signal es sich handelt, kann das Byte in der Pipe die Signalnummer sein.
quelle
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.
quelle