Quelle est la différence entre sigaction et signal?


143

J'étais sur le point d'ajouter un gestionnaire de signaux supplémentaire à une application que nous avons ici et j'ai remarqué que l'auteur avait utilisé sigaction()pour configurer les autres gestionnaires de signaux. J'allais utiliser signal(). Pour suivre la convention, je devrais utiliser sigaction()mais si j'écrivais à partir de zéro, lequel devrais-je choisir?

Réponses:


167

Utilisation sigaction() sauf si vous avez des raisons très convaincantes de ne pas le faire.

L' signal()interface a l'antiquité (et donc la disponibilité) en sa faveur, et elle est définie dans le standard C. Néanmoins, il a un certain nombre de caractéristiques indésirables qui sigaction()évitent - à moins que vous n'utilisiez les indicateurs explicitement ajoutés pour sigaction()lui permettre de simuler fidèlement l'ancien signal()comportement.

  1. La signal()fonction ne bloque pas (nécessairement) l'arrivée d'autres signaux pendant l'exécution du gestionnaire actuel; sigaction()peut bloquer d'autres signaux jusqu'au retour du gestionnaire actuel.
  2. La signal()fonction réinitialise (généralement) l'action du signal à SIG_DFL(par défaut) pour presque tous les signaux. Cela signifie que lesignal() gestionnaire doit se réinstaller comme première action. Il ouvre également une fenêtre de vulnérabilité entre le moment où le signal est détecté et le gestionnaire est réinstallé pendant lequel si une deuxième instance du signal arrive, le comportement par défaut (généralement se termine, parfois avec préjudice - aka core dump) se produit.
  3. Le comportement exact de signal()varie selon les systèmes - et les normes permettent ces variations.

Ce sont généralement de bonnes raisons d'utiliser sigaction()au lieu de signal(). Cependant, l'interface de sigaction()est indéniablement plus délicate.

Quel que soit les deux que vous utilisez, ne soyez pas tentés par les interfaces de signal alternatives telles que sighold(), sigignore(), sigpause()et sigrelse(). Ils sont nominalement des alternatives à sigaction(), mais ils sont à peine standardisés et sont présents dans POSIX pour une compatibilité descendante plutôt que pour une utilisation sérieuse. Notez que le standard POSIX dit que leur comportement dans les programmes multi-threads n'est pas défini.

Les programmes et signaux multithreads sont une toute autre histoire compliquée. AFAIK, les deux signal()et sigaction()sont OK dans les applications multi-thread.

Cornstalks observe :

La page de manuel Linux pour signal()dit:

  Les effets de signal()dans un processus multithread ne sont pas spécifiés.

Ainsi, je pense que sigaction()c'est le seul qui peut être utilisé en toute sécurité dans un processus multi-thread.

C'est intéressant. La page de manuel Linux est plus restrictive que POSIX dans ce cas. POSIX spécifie pour signal():

Si le processus est multi-thread, ou si le processus est monothread et qu'un gestionnaire de signal est exécuté autrement que par:

  • L'appel de processus abort(), raise(), kill(), pthread_kill()ou sigqueue()pour générer un signal qui est pas bloqué
  • Un signal en attente étant débloqué et délivré avant l'appel qui l'a débloqué, il revient

le comportement est indéfini si le gestionnaire de signal fait référence à un objet autre que celui errnoavec une durée de stockage statique autrement qu'en affectant une valeur à un objet déclaré comme volatile sig_atomic_t, ou si le gestionnaire de signal appelle une fonction définie dans cette norme autre que l'une des fonctions répertoriées dans Concepts de signal .

Ainsi POSIX spécifie clairement le comportement de signal()dans une application multi-thread.

Néanmoins, il sigaction()doit être préféré dans pratiquement toutes les circonstances - et le code multi-thread portable devrait être utilisé à sigaction()moins qu'il n'y ait une raison écrasante pour laquelle il ne le peut pas (comme "n'utiliser que les fonctions définies par le Standard C" - et oui, le code C11 peut être multi -fileté). C'est essentiellement ce que dit également le premier paragraphe de cette réponse.


12
Cette description de signalest en fait le comportement d'Unix System V. POSIX autorise ce comportement ou le comportement BSD beaucoup plus sain, mais comme vous ne pouvez pas être sûr de celui que vous obtiendrez, il est toujours préférable de l'utiliser sigaction.
R .. GitHub STOP HELPING ICE

1
sauf si vous utilisez les indicateurs explicitement ajoutés à sigaction () pour lui permettre de simuler fidèlement l'ancien comportement de signal (). Quels drapeaux seraient-ils (en particulier)?
ChristianCuevas

@AlexFritz: Principalement SA_RESETHAND, aussi SA_NODEFER.
Jonathan Leffler

2
@BulatM. Si vous ne pouvez pas utiliser sigaction(), vous êtes essentiellement obligé d'utiliser la spécification Standard C pour signal(). Cependant, cela vous donne un ensemble d'options extrêmement pauvres pour ce que vous pouvez faire. Vous pouvez: modifier (portée du fichier) des variables de type volatile sig_atomic_t; appeler l'une des fonctions de «sortie rapide» ( _Exit(), quick_exit()) ou abort(); appel signal()avec le numéro de signal actuel comme argument de signal; revenir. Et c'est tout. Rien d'autre n'est garanti pour être portable. C'est tellement strict que la plupart des gens ignorent ces règles - mais le code qui en résulte est douteux.
Jonathan Leffler

1
Excellente sigaction()démo de GCC eux-mêmes: gnu.org/software/libc/manual/html_node / ... ; et une excellente signal()démo de GCC eux-mêmes: gnu.org/software/libc/manual/html_node/… . Notez que dans la signaldémo, ils évitent de changer le gestionnaire d'ignorer ( SIG_IGN) si c'est ce à quoi il était auparavant intentionnellement défini.
Gabriel Staples


5

Ce sont des interfaces différentes pour les installations de signal du système d'exploitation. On devrait préférer utiliser sigaction pour signaler si possible car signal () a un comportement défini par l'implémentation (souvent propice à la course) et se comporte différemment sur Windows, OS X, Linux et autres systèmes UNIX.

Consultez cette note de sécurité pour plus de détails.


1
Je viens de regarder le code source de la glibc et signal () appelle simplement sigaction (). Voir également ci-dessus où la page de manuel MacOS revendique la même chose.
bmdhacks

C'est bon à savoir. Je n'ai jamais vu que des gestionnaires de signaux utilisés pour fermer les choses proprement avant de quitter, donc je ne me fierais généralement pas à un comportement lié à la réinstallation du gestionnaire.
Matthew Smith

5

signal () est le standard C, sigaction () ne l'est pas.

Si vous pouvez utiliser l'un ou l'autre (c'est-à-dire que vous êtes sur un système POSIX), utilisez sigaction (); il n'est pas spécifié si signal () réinitialise le gestionnaire, ce qui signifie que pour être portable, vous devez à nouveau appeler signal () dans le gestionnaire. Ce qui est pire, c'est qu'il y a une course: si vous obtenez deux signaux en succession rapide et que le second est livré avant de réinstaller le gestionnaire, vous aurez l'action par défaut, qui sera probablement de tuer votre processus. sigaction () , d'autre part, est garanti d'utiliser une sémantique de signal «fiable». Vous n'avez pas besoin de réinstaller le gestionnaire, car il ne sera jamais réinitialisé. Avec SA_RESTART, vous pouvez également faire redémarrer automatiquement certains appels système (vous n'avez donc pas à vérifier manuellement EINTR). sigaction () a plus d'options et est fiable, donc son utilisation est encouragée.

Psst ... ne dites à personne que je vous ai dit cela, mais POSIX a actuellement une fonction bsd_signal () qui agit comme signal () mais donne une sémantique BSD, ce qui signifie qu'elle est fiable. Son utilisation principale est le portage d'anciennes applications qui supposaient des signaux fiables, et POSIX ne recommande pas de l'utiliser.


POSIX n'a ​​pas de fonction bsd_signal()- certaines implémentations POSIX peuvent avoir la fonction, mais POSIX lui-même ne contient pas une telle fonction (voir POSIX ).
Jonathan Leffler

4

En bref:

sigaction()est bon et bien défini, mais est une fonction Linux et ne fonctionne donc que sous Linux. signal()est mauvaise et mal définie, mais est une fonction standard C et donc elle fonctionne sur n'importe quoi.

Que disent les pages de manuel Linux à ce sujet?

man 2 signal(voir en ligne ici ) déclare:

Le comportement de signal () varie selon les versions UNIX et a également varié historiquement entre les différentes versions de Linux. Évitez son utilisation: utilisez sigaction(2)plutôt. Voir la portabilité ci-dessous.

Portabilité La seule utilisation portable de signal () est de définir la disposition d'un signal sur SIG_DFL ou SIG_IGN. La sémantique lors de l'utilisation de signal () pour établir un gestionnaire de signaux varie d'un système à l'autre (et POSIX.1 autorise explicitement cette variation); ne l'utilisez pas à cette fin.

En d'autres termes: ne pas utiliser signal(). Utilisez sigaction()plutôt!

Que pense GCC?

Note de compatibilité: comme indiqué ci-dessus pour signal, cette fonction doit être évitée lorsque cela est possible. sigactionest la méthode préférée.

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

Donc, si Linux et GCC disent de ne pas utiliser signal(), mais d'utiliser à la sigaction()place, cela soulève la question: comment diable utilisons-nous cette sigaction()chose déroutante !?

Exemples d'utilisation:

Lisez EXCELLENT signal()exemple de GCC ici: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling

Et leur EXCELLENT sigaction()exemple ici: https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html

Après avoir lu ces pages, j'ai mis au point la technique suivante pour sigaction():

1. sigaction(), car c'est la bonne façon d'attacher un gestionnaire de signaux, comme décrit ci-dessus:

#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. Et pour signal(), même si ce n'est pas un bon moyen d'attacher un gestionnaire de signaux, comme décrit ci-dessus, il est toujours bon de savoir comment l'utiliser.

Voici le code de démonstration GCC copié, car il est à peu près aussi bon qu'il va l'être:

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

Les principaux liens à connaître:

  1. Signaux standard: https://www.gnu.org/software/libc/manual/html_node/Standard-Signals.html#Standard-Signals
    1. Signaux de terminaison: https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html#Termination-Signals
  2. Gestion de base du signal, y compris l' signal()exemple d'utilisation officiel de GCC : https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
  3. sigaction()Exemple d'utilisation officiel de GCC : https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html
  4. Ensembles de signaux, y compris sigemptyset()et sigfillset(); Je ne les comprends toujours pas exactement, mais je sais qu'ils sont importants: https://www.gnu.org/software/libc/manual/html_node/Signal-Sets.html

Voir également:

  1. TutorialsPoint C ++ Signal Handling [avec un excellent code de démonstration]: https://www.tutorialspoint.com/cplusplus/cpp_signal_handling.htm
  2. https://www.tutorialspoint.com/c_standard_library/signal_h.htm

2

Depuis la signal(3)page de manuel:

LA DESCRIPTION

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

Les deux invoquent la même fonction sous-jacente. Vous ne devriez probablement pas manipuler la réponse d'un seul signal avec les deux, mais les mélanger ne devrait pas faire casser quoi que ce soit ...


Ce n'est pas dans ma page de manuel! Tout get I est « DESCRIPTION L'appel système signal () installe un nouveau gestionnaire de signal pour le signal numéro signum. » Je dois passer à l' utile package pages de manuel.
Matthew Smith

1
C'est hors des pages de Mac OS X 10.5.
dmckee --- ex-moderator chaton

Également vérifié à partir du code source de la glibc. signal () appelle juste sigaction ()
bmdhacks

2
Ce n'est cependant pas vrai sur toutes les implémentations de signal. Si vous souhaitez imposer un comportement "sigaction", ne vous fiez pas à cette hypothèse.
Ben Burns

1

Je suggérerais également d'utiliser sigaction () sur signal () et je voudrais ajouter un point de plus. sigaction () vous donne plus d'options telles que pid du processus qui est mort (possible en utilisant la structure siginfo_t).


0

J'utiliserais signal () car il est plus portable, du moins en théorie. Je voterai pour n'importe quel commentateur qui peut proposer un système moderne qui n'a pas de couche de compatibilité POSIX et prend en charge signal ().

Citant la documentation GLIBC :

Il est possible d'utiliser à la fois les fonctions de signal et de sigaction dans un seul programme, mais vous devez faire attention car elles peuvent interagir de manière légèrement étrange.

La fonction de sigaction spécifie plus d'informations que la fonction de signal, de sorte que la valeur de retour du signal ne peut pas exprimer la gamme complète des possibilités de sigaction. Par conséquent, si vous utilisez signal pour enregistrer et rétablir ultérieurement une action, il se peut qu'il ne puisse pas rétablir correctement un gestionnaire qui a été établi avec sigaction.

Pour éviter d'avoir des problèmes en conséquence, utilisez toujours sigaction pour enregistrer et restaurer un gestionnaire si votre programme utilise sigaction du tout. Puisque la sigaction est plus générale, elle peut correctement sauvegarder et rétablir n'importe quelle action, qu'elle ait été établie à l'origine avec signal ou sigaction.

Sur certains systèmes, si vous établissez une action avec signal puis l'examinez avec sigaction, l'adresse du gestionnaire que vous obtenez peut ne pas être la même que celle que vous avez spécifiée avec signal. Il peut même ne pas convenir à une utilisation comme argument d'action avec signal. Mais vous pouvez vous en remettre à l'utiliser comme argument de sigaction. Ce problème ne se produit jamais sur le système GNU.

Il vaut donc mieux utiliser l'un ou l'autre des mécanismes de manière cohérente au sein d'un même programme.

Note de portabilité: La fonction de signal de base est une caractéristique d'ISO C, tandis que sigaction fait partie de la norme POSIX.1. Si vous êtes préoccupé par la portabilité vers des systèmes non POSIX, vous devez utiliser la fonction de signal à la place.

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

La permission est accordée de copier, distribuer et / ou modifier ce document selon les termes de la licence de documentation libre GNU, version 1.2 ou toute version ultérieure publiée par la Free Software Foundation; sans sections invariantes, sans textes de couverture et sans textes de couverture arrière. Une copie de la licence est incluse dans la section intitulée «Licence de documentation libre GNU».


0

Depuis le signal de la page de manuel (7)

Un signal dirigé par le processus peut être délivré à l'un quelconque des threads dont le signal n'est actuellement pas bloqué. Si plus d'un des threads a le signal débloqué, alors le noyau choisit un thread arbitraire auquel délivrer le signal.

Et je dirais que ce "problème" existe pour signal (2) et sigaction (2) . Soyez donc prudent avec les signaux et les pthreads.

... et signal (2) semble appeler sigaction (2) sous Linux avec la glibc.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.