web-dev-qa-db-fra.com

Gestion simple des signaux Linux

J'ai un programme qui crée de nombreux threads et s'exécute jusqu'à ce que l'alimentation soit coupée sur l'ordinateur intégré ou que l'utilisateur utilise kill ou ctrlc pour mettre fin au processus.

Voici du code et à quoi ressemble le main ().

static int terminate = 0;  // does this need to be volatile?

static void sighandler(int signum) { terminate = 1; }

int main() {
  signal(SIGINT, sighandler);
  // ...
  // create objects, spawn threads + allocate dynamic memory
  // ...
  while (!terminate) sleep(2);
  // ...
  // clean up memory, close threads, etc.
  // ...
  signal(SIGINT, SIG_DFL);  // is this necessary?
}

Je me pose quelques questions:

  1. Une gestion du signal est-elle nécessaire?
    J'ai lu dans ce fil "" Linux C attrape le signal de kill pour une terminaison gracieuse " , qu'apparemment le système d'exploitation va gérer le nettoyage pour moi. Par conséquent, puis-je simplement remplacer le gestionnaire de signal par une boucle infinie et laisser le système d'exploitation quitter les threads avec grâce, désallouer la mémoire, etc.?

  2. Y a-t-il d'autres signaux dont je dois m'inquiéter concernant la terminaison propre? Ce fil "Comment SIGINT est-il lié aux autres signaux de terminaison?" , a été utile pour répertorier tous les signaux qui pourraient m'intéresser, mais combien en ont-ils réellement eu besoin?

  3. La variable de fin dans mon exemple doit-elle être volatile? J'ai vu de nombreux exemples où cette variable est volatile et d'autres où elle ne l'est pas.

  4. J'ai lu que signal() est maintenant obsolète et utiliser sigaction(). Existe-t-il de très bons exemples pour montrer comment effectuer une conversion à partir de l'appel signal() précédent? J'ai des problèmes avec la nouvelle structure que je dois créer/transmettre et comment tout cela s'emboîte.

  5. Le deuxième appel à signal() est-il nécessaire?
    Y a-t-il quelque chose de similaire dont je dois me préoccuper pour sigaction()?

Pour être clair, tout ce que j'essaie d'accomplir est d'exécuter ma boucle principale jusqu'à ce que ctrlc ou l'alimentation est coupée ou quelque chose de vraiment grave se produit.

30
It'sPete

[Q-3] La variable terminate dans mon exemple doit-elle être volatile? J'ai vu de nombreux exemples où cette variable est volatile et d'autres où elle ne l'est pas.

L'indicateur terminate doit être volatile sig_atomic_t:

Parce que les fonctions de gestionnaire peuvent être appelées de manière asynchrone. Autrement dit, un gestionnaire peut être appelé à tout moment du programme, de façon imprévisible. Si deux signaux arrivent pendant un intervalle très court, un gestionnaire peut s'exécuter dans un autre. Et il est considéré comme une meilleure pratique de déclarer volatile sig_atomic_t, Ce type est toujours accessible de manière atomique, évite toute incertitude quant à l'interruption de l'accès à une variable. volatile indique au compilateur de ne pas l'optimiser et de le mettre en registre. (lire: Accès aux données atomiques et traitement du signal pour l'expiation détaillée).
Encore une référence: 24.4.7 Accès aux données atomiques et traitement du signal . De plus, la norme C11 du 7.14.1.1-5 indique que seuls les objets de volatile sig_atomic_t Sont accessibles à partir d'un gestionnaire de signaux (l'accès aux autres a un comportement indéfini).

[Q-4] J'ai lu que signal() est maintenant obsolète et utiliser sigaction(). Existe-t-il de très bons exemples pour montrer comment effectuer une conversion à partir de l'appel signal() précédent? J'ai des problèmes avec la nouvelle structure que je dois créer/transmettre et comment tout cela s'emboîte.

L'exemple ci-dessous (et le lien dans les commentaires) peut être utile:

// 1. Prepare struct 
struct sigaction sa;
sa.sa_handler =  sighandler;

// 2. To restart functions if interrupted by handler (as handlers called asynchronously)
sa.sa_flags = SA_RESTART; 

// 3. Set zero 
sigemptyset(&sa.sa_mask);

/* 3b. 
 // uncomment if you wants to block 
 // some signals while one is executing. 
sigaddset( &sa.sa_mask, SIGINT );
*/ 

// 4. Register signals 
sigaction( SIGINT, &sa, NULL );

références:

  1. Beginning Linux Programming, 4th Edition : dans ce livre, exactement votre code est expliqué avec sigaction() bien dans "Chapter 11: Processes and Signals".
  2. La documentation de sigaction , incluant un exemple (apprentissage rapide).
  3. La bibliothèque GNU C: Gestion du signal
    * Je suis parti de 1 , actuellement je lis GNU-library

[Q-5] Le deuxième appel à signal() est-il nécessaire? Existe-t-il quelque chose de similaire dont je dois m'occuper pour sigaction()?

La raison pour laquelle vous le définissez sur l'action par défaut avant la fin du programme n'est pas claire pour moi. Je pense que le paragraphe suivant vous donnera une réponse:

Gestion des signaux

L'appel au signal établit la gestion du signal pour seulement une occurrence d'un signal. Avant l'appel de la fonction de gestion du signal, la bibliothèque réinitialise le signal afin que l'action par défaut soit effectuée si le même signal se reproduit . La réinitialisation de la gestion du signal aide à empêcher une boucle infinie si, par exemple, une action effectuée dans le gestionnaire de signal déclenche à nouveau le même signal. Si vous souhaitez que votre gestionnaire soit utilisé pour un signal chaque fois qu'il se produit, vous devez appeler le signal dans le gestionnaire pour le rétablir. Vous devez être prudent lorsque vous réinstallez le traitement du signal. Par exemple, si vous réinstallez continuellement la gestion de SIGINT, vous risquez de perdre la possibilité d'interrompre et de terminer votre programme.

La fonction signal() définit uniquement le gestionnaire du prochain signal reçu, après quoi le gestionnaire par défaut est rétabli. Il est donc nécessaire que le gestionnaire de signaux appelle signal() si le programme doit continuer à gérer les signaux en utilisant un gestionnaire non par défaut.

Lisez une discussion pour plus de référence: Quand réactiver les gestionnaires de signaux .

[Q-1a] Une gestion du signal est-elle nécessaire?

Oui, Linux fera le nettoyage pour vous. Par exemple, si vous ne fermez pas de fichier ou de socket, Linux effectuera le nettoyage une fois le processus terminé. Mais Linux peut ne pas nécessairement effectuer le nettoyage immédiatement et cela peut prendre un certain temps (peut-être pour maintenir les performances du système élevées ou d'autres problèmes). Par exemple, si vous ne fermez pas un socket tcp et que le programme se termine, le noyau ne fermera pas le socket immédiatement pour garantir que toutes les données ont été transmises, TCP garantit la livraison si possible.

[Q-1b] Par conséquent, puis-je simplement remplacer le gestionnaire de signal avec juste une boucle infinie et laisser le système d'exploitation quitter gracieusement les threads, désallouer le mémoire, etc.?

Non, le système d'exploitation effectue le nettoyage uniquement après la fin du programme. Pendant l'exécution d'un processus, les ressources qui lui sont allouées ne sont pas revendiquées par le système d'exploitation. (L'OS ne peut pas savoir si votre processus est dans une boucle infinie ou non - c'est un problème insoluble ). Si vous souhaitez qu'après la fin du processus, le système d'exploitation effectue les opérations de nettoyage pour vous, vous n'avez pas besoin de gérer les signaux (même si votre processus se termine anormalement par un signal).

[Q] Tout ce que j'essaie d'accomplir pour que ma boucle principale s'exécute jusqu'à ce que ctrlc ou l'alimentation est coupée ou quelque chose de vraiment grave se produit.

Non, il y a une limitation! Vous ne pouvez pas capter tous les signaux. Certains signaux ne sont pas capturables, par ex. SIGKILL et SIGSTOP et les deux sont des signaux de terminaison. Citant un:

- Macro: int SIGKILL

Le signal SIGKILL est utilisé pour provoquer l'arrêt immédiat du programme. Il ne peut pas être manipulé ou ignoré, et est donc toujours fatal. Il est également pas possible de bloquer ce signal.

Vous ne pouvez donc pas faire n programme qui ne peut pas être interrompu (un programme ininterrompu) !

Je ne suis pas sûr mais peut-être que vous pouvez faire quelque chose comme ça dans les systèmes Windows: en écrivant des TSR (une sorte de hook en mode noyau). Je me souviens de ma thèse que certains virus ne pouvaient pas être supprimés même à partir du gestionnaire de tâches, mais je pense également qu'ils trompent l'utilisateur par les autorisations d'administrateur.

J'espère que cette réponse vous aidera.

28
Grijesh Chauhan

Pour utiliser sigaction à la place, vous pouvez utiliser une fonction comme celle-ci:

/*
 * New implementation of signal(2), using sigaction(2).
 * Taken from the book ``Advanced Programming in the UNIX Environment''
 * (first edition) by W. Richard Stevens.
 */
sighandler_t my_signal(int signo, sighandler_t func)
{
    struct sigaction nact, oact;

    nact.sa_handler = func;
    nact.sa_flags = 0;
    # ifdef SA_INTERRUPT
    nact.sa_flags |= SA_INTERRUPT;
    # endif
    sigemptyset(&nact.sa_mask);

    if (sigaction(signo, &nact, &oact) < 0)
        return SIG_ERR;

    return oact.sa_handler;
}
6

1. Une gestion du signal est-elle nécessaire?

  • Dans le cas particulier du lien que vous avez publié, oui. Les exécutions de logiciels qui concernent le réseau nécessitent des opérations particulières, comme avertir le client de la fermeture d'une socket par exemple, afin de ne pas perturber son exécution.
  • Dans votre cas particulier, vous n'avez besoin de gérer aucun signal pour que le processus soit clair et élégant: votre système d'exploitation le fera pour vous.

2. Y a-t-il d'autres signaux dont je dois m'inquiéter concernant la terminaison propre?

  • Tout d'abord, jetez un œil à sa page: Les GNU Signaux de bibliothèque Les signaux de terminaison sont ce que vous recherchez. Mais jetez un œil à SIGUSR1 et SIGUSR2, même si vous Je ne les trouverai jamais dans aucun logiciel, sauf à des fins de débogage.

  • Tous ces signaux de terminaison doivent être traités si vous ne voulez pas que votre logiciel se termine soudainement.

3. La variable de fin dans mon exemple doit-elle être volatile?

  • Absolument pas.

4. J'ai lu que signal() est maintenant obsolète, et utiliser sigaction()

  • Sigaction() est POSIX tandis que le signal est un standard C.

  • Signal() fonctionne bien pour moi, mais si vous voulez un exemple: exemple IBM

5
Timothee Tosi

Il n'est pas nécessaire d'utiliser des signaux pour cela. Une terminaison standard n'a pas besoin d'être interceptée. Vous pouvez avoir des raisons de l'attraper, mais cela dépend de votre application plutôt que de tout ce qui est requis par l'O/S.

En termes de signaux, vous devez généralement utiliser la sigaction et non le signal de nos jours, cela contourne un problème de normalisation.

Les gestionnaires de signaux doivent être écrits pour être réentrants. Cela ne nécessite pas que votre variable de terminaison soit volatile, mais cela pourrait, selon la façon dont vous l'utilisez!

Le livre de W. Richard Stevens "Programmation avancée dans l'environnement UNIX" a un bon exemple de pourquoi et comment gérer les signaux.

Et non, vous n'avez pas à remettre le gestionnaire par défaut avant la fin de votre application, votre gestionnaire n'est valide que pour votre application, donc si vous ne faites que tuer l'application, ce n'est pas nécessaire.

4
Joe

Tout d'abord - si vous ne savez pas si vous devez gérer des signaux, vous ne le faites probablement pas. Les signaux nécessitent une manipulation dans certains cas spécifiques, comme la fermeture de sockets, l'envoi de messages à d'autres processus connectés avant de quitter, ou la gestion du signal SIGPIPE de write () (et probablement beaucoup plus).

Deuxièmement - cette while (!terminate) sleep(2); n'est pas très bonne - dans le pire des cas, cela pourrait rendre l'utilisateur (ou même le système) impatient de devoir attendre 2 secondes et envoyer à votre programme un SIGKILL, que vous ne pouvez pas gérer.

À mon humble avis, la meilleure solution serait en utilisant signalfd et select , vous pouvez donc terminer votre programme sans avoir à attendre 2 secondes.

0
zoska