Ich wollte gerade einen zusätzlichen Signal-Handler zu einer App hinzufügen, die wir hier haben, und mir ist aufgefallen, dass der Autor ihn verwendet hat sigaction()
um die anderen Signalhandler einzurichten. Ich wollte verwenden signal()
. Um der Konvention zu folgen, sollte ich verwenden sigaction()
aber wenn ich von Grund auf neu schreiben würde, was sollte ich wählen?
Was ist der Unterschied zwischen Signal und Signal?
Matthäus Smith
Jonathan Leffler
Verwenden sigaction()
es sei denn, Sie haben zwingende Gründe, dies nicht zu tun.
Das signal()
Die Schnittstelle hat die Antike (und damit die Verfügbarkeit) zu ihren Gunsten und ist im C-Standard definiert. Trotzdem hat es eine Reihe unerwünschter Eigenschaften sigaction()
vermeidet – es sei denn, Sie verwenden die explizit hinzugefügten Flags sigaction()
um es zu ermöglichen, das Alte originalgetreu zu simulieren signal()
Verhalten.
- Das
signal()
Die Funktion blockiert nicht (notwendigerweise) das Eintreffen anderer Signale, während der aktuelle Handler ausgeführt wird.sigaction()
kann andere Signale blockieren, bis der aktuelle Handler zurückkehrt. - Das
signal()
Funktion (normalerweise) setzt die Signalaktion zurück aufSIG_DFL
(Standard) für fast alle Signale. Dies bedeutet, dass diesignal()
Handler muss sich als erste Aktion selbst neu installieren. Es öffnet auch ein Sicherheitsfenster zwischen dem Zeitpunkt, an dem das Signal erkannt wird, und der Neuinstallation des Handlers, in dem beim Eintreffen einer zweiten Instanz des Signals das Standardverhalten (normalerweise beenden, manchmal mit Vorurteilen – auch bekannt als Core-Dump) auftritt. - Das genaue Verhalten von
signal()
variiert zwischen den Systemen – und die Standards erlauben diese Variationen.
Dies sind im Allgemeinen gute Gründe für die Verwendung sigaction()
Anstatt von signal()
. Allerdings ist die Schnittstelle von sigaction()
ist unbestreitbar fummeliger.
Welche der beiden Sie auch verwenden, lassen Sie sich nicht von den alternativen Signalschnittstellen wie z
sighold()
,
sigignore()
,
sigpause()
und
sigrelse()
. Sie sind nominell Alternativen zu sigaction()
, aber sie sind nur knapp standardisiert und in POSIX eher aus Gründen der Abwärtskompatibilität als für den ernsthaften Gebrauch vorhanden. Beachten Sie, dass der POSIX-Standard besagt, dass ihr Verhalten in Multithread-Programmen undefiniert ist.
Multithreaded-Programme und -Signale sind eine ganz andere komplizierte Geschichte. AFAIK, beides signal()
und sigaction()
sind in Multithread-Anwendungen in Ordnung.
Cornstalks beobachtet:
Die Linux-Manpage für
signal()
sagt:
Die Effekte von
signal()
in einem Multithread-Prozess sind nicht spezifiziert.So denke ich
sigaction()
ist die einzige, die sicher in einem Multithread-Prozess verwendet werden kann.
Das ist interessant. Die Linux-Handbuchseite ist in diesem Fall restriktiver als POSIX. POSIX spezifiziert für signal()
:
Wenn der Prozess Multithreading ist oder wenn der Prozess Singlethreading ist und ein Signalhandler ausgeführt wird, außer als Ergebnis von:
- Der Prozess ruft
abort()
,raise()
,kill()
,pthread_kill()
odersigqueue()
um ein nicht blockiertes Signal zu erzeugen- Ein anstehendes Signal wird entsperrt und geliefert, bevor der Anruf, der es entsperrt hat, zurückkehrt
das Verhalten ist undefiniert, wenn sich der Signal-Handler auf ein anderes Objekt als bezieht
errno
mit statischer Speicherdauer anders als durch Zuweisen eines Werts zu einem als deklarierten Objektvolatile sig_atomic_t
oder wenn der Signal-Handler eine andere in diesem Standard definierte Funktion als eine der in aufgelisteten Funktionen aufruft Signalkonzepte.
POSIX spezifiziert also eindeutig das Verhalten von signal()
in einer Multithread-Anwendung.
Nichtsdestotrotz, sigaction()
ist unter praktisch allen Umständen vorzuziehen – und portabler Multithread-Code sollte verwendet werden sigaction()
es sei denn, es gibt einen zwingenden Grund, warum dies nicht möglich ist (z. B. „nur Funktionen verwenden, die von Standard C definiert sind“ – und ja, C11-Code kann Multithreading sein). Was im Grunde auch der einleitende Absatz dieser Antwort sagt.
-
Diese Beschreibung von
signal
ist eigentlich das Verhalten des Unix System V. POSIX erlaubt entweder dieses Verhalten oder das viel vernünftigere BSD-Verhalten, aber da Sie nicht sicher sein können, welches Sie bekommen, ist es immer noch am besten, es zu verwendensigaction
.– R.. GitHub HÖR AUF, EIS ZU HELFEN
15. August 2011 um 4:08 Uhr
-
es sei denn, Sie verwenden die explizit zu sigaction() hinzugefügten Flags, damit es das alte Signal()-Verhalten originalgetreu simulieren kann. Welche Flaggen wären das (konkret)?
– Christian Cuevas
30. Oktober 2014 um 1:20 Uhr
-
@AlexFritz: In erster Linie
SA_RESETHAND
AuchSA_NODEFER
.– Jonathan Leffler
30. Oktober 2014 um 1:28 Uhr
-
@BulatM. Wenn Sie nicht verwenden können
sigaction()
dann sind Sie im Wesentlichen verpflichtet, die Standard-C-Spezifikation für zu verwendensignal()
. Das gibt Ihnen jedoch eine extrem verarmte Reihe von Optionen für das, was Sie tun können. Sie können: Variablen des Typs (Dateibereich) ändernvolatile sig_atomic_t
; Rufen Sie eine der ‘Quick Exit’-Funktionen auf (_Exit()
,quick_exit()
) oderabort()
; Anrufsignal()
mit der aktuellen Signalnummer als Signalargument; Rückkehr. Und das ist es. Alles andere ist nicht garantiert tragbar. Das ist so streng, dass die meisten Leute diese Regeln ignorieren – aber der resultierende Code ist zwielichtig.– Jonathan Leffler
27. Oktober 2016 um 16:14 Uhr
-
Exzellent
sigaction()
Demo von GCC selbst: gnu.org/software/libc/manual/html_node/…; und ausgezeichnetsignal()
Demo von GCC selbst: gnu.org/software/libc/manual/html_node/…. Beachten Sie, dass in dersignal
Demo vermeiden sie es, den Handler von Ignorieren (SIG_IGN
), wenn es zuvor absichtlich so eingestellt war.– Gabriel Staples
12. November 2019 um 3:21 Uhr
Gabriel Staples
Zusamenfassend:
sigaction()
(sehen hier und hier) ist gut und wohldefiniert, aber eine POSIX-Funktion und funktioniert daher nur auf Linux- oder POSIX-Systemen. signal()
(sehen hier und hier) ist schlecht und schlecht definiert, aber eine C-Standardfunktion und funktioniert daher mit allem.
Was sagen die Linux Manpages dazu?
man 2 signal
(sehen Sie es hier online) Zustände:
Das Verhalten von
signal()
variiert zwischen UNIX-Versionen und hat sich auch historisch zwischen verschiedenen Linux-Versionen verändert. Vermeiden Sie seine Verwendung: verwendensigaction(2)
stattdessen. Siehe Portabilität unten.
Portabilität
Die einzige tragbare Verwendung von
signal()
ist es, eine Signaldisposition zu setzen
SIG_DFL
oderSIG_IGN
. Die Semantik bei der Verwendungsignal()
um einen systemübergreifenden Signal-Handler einzurichten (und POSIX.1 erlaubt diese Variation ausdrücklich); verwenden Sie es nicht für diesen Zweck.
Mit anderen Worten: tun nicht verwenden signal()
. Verwenden sigaction()
stattdessen!
Diese Position wird in der nächsten Zeile wiederholt, in der es heißt (Hervorhebung hinzugefügt):
POSIX.1 löste das Problem der Portabilität, indem es spezifizierte
sigaction(2)
das eine explizite Kontrolle der Semantik bereitstellt, wenn ein Signalhandler aufgerufen wird; Verwenden Sie stattdessen diese Schnittstellesignal()
.
Was denkt GCC?
Aus https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling (Betonung hinzugefügt):
Kompatibilitätshinweis: Wie oben gesagt für
signal
, Diese Funktion sollte nach Möglichkeit vermieden werden.sigaction
ist die bevorzugte Methode.
Also, wenn sowohl Linux als auch GCC sagen, nicht zu verwenden signal()
aber zu verwenden sigaction()
Stattdessen stellt sich die Frage: Wie zum Teufel verwenden wir diese Verwirrung sigaction()
Ding!?
Anwendungsbeispiele:
Lesen Sie GCCs AUSGEZEICHNET signal()
Beispiel hier: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
Und ihre AUSGEZEICHNETEN sigaction()
Beispiel hier: https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html
Nachdem ich diese Seiten gelesen hatte, kam ich auf die folgende Technik für sigaction()
:
1. sigaction()
da es der richtige Weg ist, einen Signal-Handler wie oben beschrieben anzuhängen:
#include <errno.h> // errno
#include <signal.h> // sigaction()
#include <stdio.h> // printf()
#include <string.h> // strerror()
// Format: const char *, unsigned int, const char *
#define LOG_LOCATION __FILE__, __LINE__, __func__
#define LOG_FORMAT_STR "file: %s, line: %u, func: %s: "
/// @brief Callback function to handle termination signals, such as
/// Ctrl + C
/// @param[in] signal Signal number of the signal being handled by this
/// callback function
/// @return None
static void termination_handler(const int signal)
{
switch (signal)
{
case SIGINT:
printf("\nSIGINT (%i) (Ctrl + C) signal caught.\n", signal);
break;
case SIGTERM:
printf("\nSIGTERM (%i) (default `kill` or `killall`) signal caught.\n",
signal);
break;
case SIGHUP:
printf("\nSIGHUP (%i) (\"hang-up\") signal caught.\n", signal);
break;
default:
printf("\nUnk signal (%i) caught.\n", signal);
break;
}
// DO PROGRAM CLEANUP HERE, such as freeing memory, closing files, etc.
exit(signal);
}
/// @brief Set a new signal handler action for a given signal
/// @details Only update the signals with our custom handler if they are NOT
/// set to "signal ignore" (`SIG_IGN`), which means they are currently
/// intentionally ignored. GCC recommends this "because non-job-control
/// shells often ignore certain signals when starting children, and it is
/// important for children to respect this." See
/// https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
/// and
/// https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html.
/// Note that termination signals can be found here:
/// https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html#Termination-Signals
/// @param[in] signal Signal to set to this action
/// @param[in] action Pointer to sigaction struct, including the callback
/// function inside it, to attach to this signal
/// @return None
static inline void set_sigaction(int signal, const struct sigaction *action)
{
struct sigaction old_action;
// check current signal handler action to see if it's set to SIGNAL IGNORE
sigaction(signal, NULL, &old_action);
if (old_action.sa_handler != SIG_IGN)
{
// set new signal handler action to what we want
int ret_code = sigaction(signal, action, NULL);
if (ret_code == -1)
{
printf(LOG_FORMAT_STR "sigaction failed when setting signal to "
"%i; errno = %i: %s\n",
LOG_LOCATION, signal, errno, strerror(errno));
}
}
}
int main(int argc, char *argv[])
{
//...
// Register callbacks to handle kill signals; prefer the Linux function
// `sigaction()` over the C function `signal()`: "It is better to use
// sigaction if it is available since the results are much more reliable."
// Source:
// https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
// and
// https://stackoverflow.com/questions/231912/what-is-the-difference-between-sigaction-and-signal/232711#232711.
// See here for official gcc `sigaction()` demo, which this code is modeled
// after:
// https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html
// Set up the structure to specify the new action, per GCC's demo.
struct sigaction new_action;
new_action.sa_handler = termination_handler; // set callback function
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = 0;
// SIGINT: ie: Ctrl + C kill signal
set_sigaction(SIGINT, &new_action);
// SIGTERM: termination signal--the default generated by `kill` and
// `killall`
set_sigaction(SIGTERM, &new_action);
// SIGHUP: "hang-up" signal due to lost connection
set_sigaction(SIGHUP, &new_action);
//...
}
2. Und für signal()
auch wenn es keine gute Möglichkeit ist, einen Signal-Handler wie oben beschrieben anzuhängen, ist es dennoch gut zu wissen, wie man ihn benutzt.
Hier ist der GCC-Demonstrationscode, der kopiert und eingefügt wurde, da er so gut wie möglich ist:
#include <signal.h>
void
termination_handler (int signum)
{
struct temp_file *p;
for (p = temp_file_list; p; p = p->next)
unlink (p->name);
}
int
main (void)
{
…
if (signal (SIGINT, termination_handler) == SIG_IGN)
signal (SIGINT, SIG_IGN);
if (signal (SIGHUP, termination_handler) == SIG_IGN)
signal (SIGHUP, SIG_IGN);
if (signal (SIGTERM, termination_handler) == SIG_IGN)
signal (SIGTERM, SIG_IGN);
…
}
Die wichtigsten Links, die Sie kennen sollten:
- Standardsignale: https://www.gnu.org/software/libc/manual/html_node/Standard-Signals.html#Standard-Signals
- Beendigungssignale: https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html#Termination-Signals
- Grundlegende Signalbehandlung, einschließlich offizieller GCC
signal()
Anwendungsbeispiel: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling - Offizieller GCC
sigaction()
Anwendungsbeispiel: https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html - Signalsätze, einschließlich
sigemptyset()
undsigfillset()
; Ich verstehe diese immer noch nicht genau, weiß aber, dass sie wichtig sind: https://www.gnu.org/software/libc/manual/html_node/Signal-Sets.html
Siehe auch:
- Meine Antwort auf “Wie man manuell ein beliebiges Signal an einen laufenden Prozess sendet” und “Wie man ein beliebiges Signal in Ihrem Programm abfängt” (z. B. in Bash).
- TutorialsPoint C++-Signalverarbeitung [with excellent demo code]: https://www.tutorialspoint.com/cplusplus/cpp_signal_handling.htm
- https://www.tutorialspoint.com/c_standard_library/signal_h.htm
-
Ich verstehe nicht
sigemptyset
entweder– jcarlosweb
4. September um 13:37 Uhr
Für mich war diese folgende Zeile genug, um zu entscheiden:
Die Funktion sigaction() bietet einen umfassenderen und zuverlässigeren Mechanismus zum Steuern von Signalen; neue Anwendungen sollten sigaction() statt signal() verwenden
http://pubs.opengroup.org/onlinepubs/009695399/functions/signal.html#tag_03_690_07
Egal, ob Sie bei Null anfangen oder ein altes Programm modifizieren, sigaction sollte die richtige Option sein.
Sie sind verschiedene Schnittstellen für die Signaleinrichtungen von OS. Man sollte es vorziehen, wenn möglich sigaction zum Signalisieren zu verwenden, da signal() ein implementierungsdefiniertes (oft Race-anfälliges) Verhalten hat und sich auf Windows-, OS X-, Linux- und anderen UNIX-Systemen anders verhält.
Sieh dir das an Sicherheitshinweis für Details.
signal() ist Standard-C, sigaction() nicht.
Wenn Sie beides verwenden können (das heißt, Sie befinden sich auf einem POSIX-System), verwenden Sie sigaction(); Es ist nicht angegeben, ob signal() den Handler zurücksetzt, was bedeutet, dass Sie signal() innerhalb des Handlers erneut aufrufen müssen, um portierbar zu sein. Was noch schlimmer ist, ist, dass es ein Rennen gibt: Wenn Sie zwei Signale kurz hintereinander erhalten und das zweite geliefert wird, bevor Sie den Handler neu installieren, haben Sie die Standardaktion, die wahrscheinlich darin besteht, Ihren Prozess zu beenden.
sigaction(), hingegen verwendet garantiert eine „zuverlässige“ Signalsemantik. Sie müssen den Handler nicht neu installieren, da er nie zurückgesetzt wird. Mit SA_RESTART können Sie auch einige Systemaufrufe automatisch neu starten lassen (damit Sie nicht manuell nach EINTR suchen müssen).
sigaction() hat mehr Optionen und ist zuverlässig, daher wird seine Verwendung empfohlen.
Psst … erzählen Sie niemandem, dass ich Ihnen das gesagt habe, aber POSIX hat derzeit eine Funktion bsd_signal(), die sich wie signal() verhält, aber BSD-Semantik gibt, was bedeutet, dass sie zuverlässig ist. Seine Hauptverwendung ist das Portieren alter Anwendungen, die von zuverlässigen Signalen ausgegangen sind, und POSIX rät davon ab, es zu verwenden.
-
POSIX hat keine Funktion
bsd_signal()
— Einige POSIX-Implementierungen können die Funktion haben, aber POSIX selbst enthält keine solche Funktion (siehe Posix).– Jonathan Leffler
13. August 2019 um 16:22 Uhr
Greg Hewgill
Von dem signal(3)
Manpage:
BEZEICHNUNG
This signal() facility is a simplified interface to the more general sigaction(2) facility.
Beide rufen dieselbe zugrunde liegende Einrichtung auf. Sie sollten vermutlich nicht die Antwort auf ein einzelnes Signal mit beiden manipulieren, aber das Mischen sollte nicht dazu führen, dass etwas kaputt geht …
-
POSIX hat keine Funktion
bsd_signal()
— Einige POSIX-Implementierungen können die Funktion haben, aber POSIX selbst enthält keine solche Funktion (siehe Posix).– Jonathan Leffler
13. August 2019 um 16:22 Uhr
pradyumna kaushik
Ich würde auch vorschlagen, sigaction() über signal() zu verwenden und möchte einen weiteren Punkt hinzufügen. sigaction() gibt Ihnen mehr Optionen wie die PID des Prozesses, der gestorben ist (möglich mit der Struktur siginfo_t).