Was ist der Unterschied zwischen Sigaktion und Signal?

143

Ich wollte einer App, die wir hier haben, einen zusätzlichen Signalhandler hinzufügen, und ich bemerkte, dass der Autor sigaction()die anderen Signalhandler eingerichtet hatte. Ich würde benutzen signal(). Um der Konvention zu folgen, sollte ich verwenden, sigaction()aber wenn ich von Grund auf neu schreibe, welche sollte ich wählen?

Matthew Smith
quelle

Antworten:

167

Verwenden sigaction()Sie es, es sei denn, Sie haben sehr zwingende Gründe, dies nicht zu tun.

Die signal()Schnittstelle hat die Antike (und damit die Verfügbarkeit) zu ihren Gunsten und ist im C-Standard definiert. Trotzdem weist es eine Reihe unerwünschter Eigenschaften auf, die sigaction()vermieden werden - es sei denn, Sie verwenden die explizit hinzugefügten Flags sigaction(), um das alte signal()Verhalten originalgetreu zu simulieren .

  1. Die signal()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.
  2. Die signal()Funktion setzt (normalerweise) die Signalaktion SIG_DFLfür fast alle Signale auf (Standard) zurück. Dies bedeutet, dass sich der signal()Handler als erste Aktion neu installieren muss. Es öffnet sich auch ein Fenster der Sicherheitsanfälligkeit zwischen dem Zeitpunkt der Erkennung des Signals und der Neuinstallation des Handlers. Wenn eine zweite Instanz des Signals eintrifft, tritt das Standardverhalten (normalerweise beendet, manchmal mit Vorurteilen - auch bekannt als Core Dump) auf.
  3. Das genaue Verhalten signal()variiert zwischen den Systemen - und die Standards erlauben diese Variationen.

Dies sind im Allgemeinen gute Gründe für die Verwendung sigaction()anstelle von signal(). Die Schnittstelle von sigaction()ist jedoch zweifellos fummeliger.

Unabhängig davon , welche der beiden Sie verwenden, nicht durch die alternativen Signalschnittstellen wie versucht sein sighold(), sigignore(), sigpause()und sigrelse(). Sie sind nominell Alternativen zu sigaction(), aber sie sind kaum standardisiert und in POSIX aus Gründen der Abwärtskompatibilität und nicht für den ernsthaften Gebrauch vorhanden. Beachten Sie, dass der POSIX-Standard besagt, dass ihr Verhalten in Multithread-Programmen undefiniert ist.

Multithread-Programme und -Signale sind eine ganz andere komplizierte Geschichte. AFAIK, beide signal()und sigaction()sind in Multithread-Anwendungen in Ordnung.

Cornstalks beobachtet :

Die Linux-Manpage für signal()sagt:

  Die Auswirkungen signal()eines Multithread-Prozesses sind nicht spezifiziert.

Daher denke ich, dass dies sigaction()das einzige ist, das 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 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 Signalkonzepte .

Daher legt POSIX das Verhalten signal()in einer Multithread-Anwendung klar fest .

Trotzdem sigaction()ist es im Wesentlichen unter allen Umständen vorzuziehen - und tragbarer Multithread-Code sollte verwendet werden, es sigaction()sei denn, es gibt einen überwältigenden Grund, warum dies nicht möglich ist (z. B. "nur Funktionen verwenden, die durch Standard C definiert sind" - und ja, C11-Code kann Multi sein -gewinde). Welches ist im Grunde, was der erste Absatz dieser Antwort auch sagt.

Jonathan Leffler
quelle
12
Diese Beschreibung von signalbezieht sich tatsächlich auf das Verhalten von Unix System V. POSIX erlaubt entweder dieses Verhalten oder das viel vernünftigere BSD-Verhalten, aber da Sie nicht sicher sind, welches Sie erhalten, ist es immer noch am besten, es zu verwenden sigaction.
R .. GitHub STOP HELPING ICE
1
es sei denn, Sie verwenden die Flags, die explizit zu sigaction () hinzugefügt wurden, um das alte signal () - Verhalten originalgetreu zu simulieren. Welche Flaggen wären das (speziell)?
ChristianCuevas
@AlexFritz: In erster Linie SA_RESETHANDauch SA_NODEFER.
Jonathan Leffler
2
@BulatM. Wenn Sie nicht verwenden können sigaction(), müssen Sie im Wesentlichen die Standard C-Spezifikation für verwenden signal(). Dies gibt Ihnen jedoch eine äußerst verarmte Auswahl an Möglichkeiten für das, was Sie tun können. Sie können: Variablen vom Typ (Dateibereich) ändern volatile sig_atomic_t; Rufen Sie eine der 'Quick Exit'-Funktionen auf ( _Exit(), quick_exit()) oder abort(); Anruf signal()mit der aktuellen Signalnummer als Signalargument; Rückkehr. Und das ist es. Alles andere ist garantiert nicht tragbar. Das ist so streng, dass die meisten Leute diese Regeln ignorieren - aber der resultierende Code ist zweifelhaft.
Jonathan Leffler
1
Ausgezeichnete sigaction()Demo von GCC selbst: gnu.org/software/libc/manual/html_node/… ; und eine exzellente signal()Demo von GCC selbst: gnu.org/software/libc/manual/html_node/… . Beachten Sie, dass in der signalDemo vermieden wird, dass der Handler von ignore ( SIG_IGN) geändert wird, wenn dies zuvor absichtlich festgelegt wurde.
Gabriel Staples
8

Für mich war diese Zeile ausreichend, um zu entscheiden:

Die Funktion sigaction () bietet einen umfassenderen und zuverlässigeren Mechanismus zur Steuerung von Signalen. Neue Anwendungen sollten sigaction () anstelle von signal () verwenden.

http://pubs.opengroup.org/onlinepubs/009695399/functions/signal.html#tag_03_690_07

Unabhängig davon, ob Sie bei Null anfangen oder ein altes Programm ändern, sollte Sigaction die richtige Option sein.

San
quelle
5

Sie sind verschiedene Schnittstellen für die Signalfunktionen des Betriebssystems. Man sollte es vorziehen, Sigaction zu verwenden, um wenn möglich zu signalisieren, da signal () ein implementierungsdefiniertes (häufig rennanfälliges) Verhalten aufweist und sich unter Windows, OS X, Linux und anderen UNIX-Systemen anders verhält.

Weitere Informationen finden Sie in diesem Sicherheitshinweis .

ididak
quelle
1
Ich habe mir gerade den glibc-Quellcode angesehen und signal () ruft nur sigaction () auf. Siehe auch oben, wo die MacOS-Manpage dasselbe behauptet.
bmdhacks
das ist gut zu wissen. Ich habe bisher nur Signalhandler gesehen, die verwendet wurden, um Dinge vor dem Beenden ordentlich zu schließen, sodass ich mich normalerweise nicht auf das Verhalten bei der Neuinstallation des Handlers verlassen würde.
Matthew Smith
5

signal () ist Standard C, sigaction () nicht.

Wenn Sie beides verwenden können (dh Sie befinden sich auf einem POSIX-System), verwenden Sie sigaction (). Es ist nicht angegeben, ob signal () den Handler zurücksetzt. Um portabel zu sein, müssen Sie signal () im Handler erneut aufrufen. Schlimmer ist, dass es ein Rennen gibt: Wenn Sie zwei Signale schnell hintereinander erhalten und das zweite vor der Neuinstallation des Handlers übermittelt wird, haben Sie die Standardaktion, die wahrscheinlich darin besteht, Ihren Prozess abzubrechen. sigaction () empfohlen hingegen verwendet garantiert eine „zuverlässige“ Signalsemantik. Sie müssen den Handler nicht neu installieren, da er niemals zurückgesetzt wird. Mit SA_RESTART können Sie auch einige Systemaufrufe zum automatischen Neustart veranlassen (sodass Sie nicht manuell nach EINTR suchen müssen). Sigaction () hat mehr Optionen und ist zuverlässig, so dass seine Verwendung empfohlen wird.

Psst ... sag niemandem, dass ich dir das gesagt habe, aber POSIX hat derzeit eine Funktion bsd_signal (), die sich wie signal () verhält, aber BSD-Semantik gibt, was bedeutet, dass es zuverlässig ist. Die Hauptanwendung ist die Portierung alter Anwendungen, bei denen zuverlässige Signale angenommen wurden, und POSIX empfiehlt die Verwendung nicht.

Huzzefakhan
quelle
POSIX hat keine Funktion bsd_signal()- einige POSIX-Implementierungen haben möglicherweise die Funktion, aber POSIX selbst enthält keine solche Funktion (siehe POSIX ).
Jonathan Leffler
4

Zusamenfassend:

sigaction() ist gut und gut definiert, ist aber eine Linux-Funktion und funktioniert daher nur unter Linux. signal()ist schlecht und schlecht definiert, ist aber eine C-Standardfunktion und funktioniert daher bei allem.

Was sagen die Linux-Manpages dazu?

man 2 signal(siehe hier online ) heißt es:

Das Verhalten von signal () variiert je nach UNIX-Version und hat sich auch historisch zwischen verschiedenen Linux-Versionen geändert. Vermeiden Sie die Verwendung: Verwenden Sie sigaction(2)stattdessen. Siehe Portabilität unten.

Portabilität Die einzige tragbare Verwendung von signal () besteht darin, die Disposition eines Signals auf SIG_DFL oder SIG_IGN zu setzen. Die Semantik bei der Verwendung von signal () zum Einrichten eines Signalhandlers variiert systemübergreifend (und POSIX.1 erlaubt diese Variation ausdrücklich). Verwenden Sie es nicht für diesen Zweck.

Mit anderen Worten: nicht verwenden signal(). Verwenden Sie sigaction()stattdessen!

Was denkt GCC?

Kompatibilitätshinweis: Wie oben erwähnt signal, sollte diese Funktion nach Möglichkeit vermieden werden. sigactionist die bevorzugte Methode.

Quelle: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling

Wenn also sowohl Linux als auch GCC sagen, nicht zu verwenden signal(), sondern sigaction()stattdessen zu verwenden , stellt sich die Frage: Wie zum Teufel verwenden wir diese verwirrende sigaction()Sache?

Anwendungsbeispiele:

Lesen Sie hier das AUSGEZEICHNETE signal()Beispiel von GCC : https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling

Und ihr AUSGEZEICHNETES sigaction()Beispiel hier: https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html

Nachdem ich diese Seiten gelesen hatte, entwickelte ich die folgende Technik für sigaction():

1. sigaction(), da es der richtige Weg ist, einen Signalhandler wie oben beschrieben anzuschließen:

#include <errno.h>  // errno
#include <signal.h> // sigaction()
#include <stdio.h>  // printf()
#include <string.h> // strerror()

#define LOG_LOCATION __FILE__, __LINE__, __func__ // Format: const char *, unsigned int, const char *
#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;\n"
                   "  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 /programming/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 signal()obwohl es keine gute Möglichkeit ist, einen Signalhandler wie oben beschrieben anzuschließen, ist es dennoch gut zu wissen, wie man ihn verwendet.

Hier ist der GCC-Demonstrationscode, der kopiert wurde, da er ungefähr so ​​gut ist, wie er nur sein wird:

#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 beachten sollten:

  1. Standardsignale: https://www.gnu.org/software/libc/manual/html_node/Standard-Signals.html#Standard-Signals
    1. Kündigungssignale: https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html#Termination-Signals
  2. Grundlegende Signalverarbeitung, einschließlich des offiziellen GCC- signal()Verwendungsbeispiels: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
  3. Offizielles GCC- sigaction()Verwendungsbeispiel: https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html
  4. Signalsätze, einschließlich sigemptyset()und sigfillset(); 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:

  1. TutorialsPoint C ++ Signal Handling [mit ausgezeichnetem Demo-Code]: https://www.tutorialspoint.com/cplusplus/cpp_signal_handling.htm
  2. https://www.tutorialspoint.com/c_standard_library/signal_h.htm
Gabriel Staples
quelle
2

Von der signal(3)Manpage:

BESCHREIBUNG

 This signal() facility is a simplified interface to the more
 general sigaction(2) facility.

Beide rufen dieselbe zugrunde liegende Fazilität auf. Sie sollten vermutlich nicht die Antwort eines einzelnen Signals mit beiden manipulieren, aber das Mischen sollte nicht dazu führen, dass etwas kaputt geht ...

dmckee --- Ex-Moderator Kätzchen
quelle
Das steht nicht auf meiner Manpage! Alles, was ich bekomme, ist "BESCHREIBUNG Der signal () -Systemaufruf installiert einen neuen Signalhandler für das Signal mit Nummernsignum." Ich muss auf das nützliche Manpages-Paket aktualisieren .
Matthew Smith
1
Das ist von den Mac OS X 10.5-Seiten.
dmckee --- Ex-Moderator Kätzchen
Wird auch anhand des Quellcodes von glibc überprüft. signal () ruft nur sigaction ()
bmdhacks
2
Dies gilt jedoch nicht für alle Signalimplementierungen. Wenn Sie "Sigaction" -Verhalten vorschreiben möchten, verlassen Sie sich nicht auf diese Annahme.
Ben Burns
1

Ich würde auch vorschlagen, sigaction () über signal () zu verwenden und möchte noch einen Punkt hinzufügen. sigaction () bietet Ihnen weitere Optionen, z. B. die PID des abgelaufenen Prozesses (möglich mit der Struktur siginfo_t).

Pradyumna Kaushik
quelle
0

Ich würde signal () verwenden, da es zumindest theoretisch portabler ist. Ich werde jeden Kommentator abstimmen, der ein modernes System entwickeln kann, das keine POSIX-Kompatibilitätsschicht hat und signal () unterstützt.

Zitat aus der GLIBC-Dokumentation :

Es ist möglich, sowohl die Signal- als auch die Sigaktionsfunktion in einem einzigen Programm zu verwenden, aber Sie müssen vorsichtig sein, da sie auf etwas seltsame Weise interagieren können.

Die Sigaktionsfunktion gibt mehr Informationen als die Signalfunktion an, sodass der Rückgabewert vom Signal nicht den gesamten Bereich der Sigaktionsmöglichkeiten ausdrücken kann. Wenn Sie das Signal zum Speichern und späteren Wiederherstellen einer Aktion verwenden, kann ein mit Sigaction erstellter Handler möglicherweise nicht ordnungsgemäß wiederhergestellt werden.

Verwenden Sie immer Sigaction, um Probleme zu speichern und wiederherzustellen, wenn Ihr Programm überhaupt Sigaction verwendet, um Probleme zu vermeiden. Da die Sigaktion allgemeiner ist, kann sie jede Aktion ordnungsgemäß speichern und wiederherstellen, unabhängig davon, ob sie ursprünglich mit Signal oder Sigaktion erstellt wurde.

Wenn Sie auf einigen Systemen eine Aktion mit Signal einrichten und diese dann mit Sigaktion untersuchen, stimmt die Handleradresse, die Sie erhalten, möglicherweise nicht mit der mit Signal angegebenen überein. Es ist möglicherweise nicht einmal zur Verwendung als Aktionsargument mit Signal geeignet. Sie können sich jedoch darauf verlassen, dass Sie es als Argument für die Unterzeichnung verwenden. Dieses Problem tritt auf dem GNU-System nie auf.

Sie sollten also den einen oder anderen Mechanismus konsistent in einem einzigen Programm verwenden.

Portabilitätshinweis: Die grundlegende Signalfunktion ist ein Merkmal von ISO C, während die Sigaktion Teil des POSIX.1-Standards ist. Wenn Sie Bedenken hinsichtlich der Portabilität auf Nicht-POSIX-Systeme haben, sollten Sie stattdessen die Signalfunktion verwenden.

Copyright (C) 1996-2008 Freie Software Foundation, Inc.

Es wird die Erlaubnis erteilt, dieses Dokument gemäß den Bestimmungen der GNU Free Documentation License, Version 1.2 oder einer späteren von der Free Software Foundation veröffentlichten Version zu kopieren, zu verteilen und / oder zu ändern. ohne unveränderliche Abschnitte, ohne Texte auf der Vorderseite und ohne Texte auf der Rückseite. Eine Kopie der Lizenz finden Sie im Abschnitt "GNU Free Documentation License".

bmdhacks
quelle
0

Vom Manpage- Signal (7)

Ein prozessgesteuertes Signal kann an einen der Threads geliefert werden, bei denen das Signal derzeit nicht blockiert ist. Wenn bei mehr als einem der Threads das Signal entsperrt ist, wählt der Kernel einen beliebigen Thread aus, an den das Signal geliefert werden soll.

Und ich würde sagen, dass dieses "Problem" für Signal (2) und Sigaktion (2) besteht . Seien Sie also vorsichtig mit Signalen und Pthreads.

... und Signal (2) scheint unter Linux mit glibc Sigaction (2) aufzurufen .

robert.berger
quelle