Gestion Simple Du Signal Linux

J'ai un programme qui crée de nombreux fils et s'exécute jusqu'à ce que l'alimentation est l'arrêt de l'ordinateur, ou si l'utilisateur utilise kill ou ctrlc pour terminer le processus.

Voici du code et à quoi ressemble la fonction 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 demande quelques choses:

  1. Une manipulation du signal est-elle nécessaire?
    J'ai lu dans ce fil "Linux c catching kill signal for graceful termination" , que apparemment le système d'exploitation va gérer le nettoyage m'. Par conséquent, puis-je simplement remplacer le gestionnaire de signal par une boucle infinie et laisser le système d'exploitation quitter gracieusement les threads, désallouer la mémoire, etc?

  2. Y a-t-il d'autres signaux dont je dois me préoccuper en ce qui concerne la résiliation propre? Ce thread "Comment SIGINT se rapporte-t-il aux autres signaux de terminaison?", était utile pour lister tous les signaux que je peux être concerné, mais combien de manipulation réellement nécessaire?

  3. Est-ce que le terminer la variable dans mon exemple doit ê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 d'utiliser sigaction(). Y a-t-il de très bons exemples pour montrer comment convertir à partir de l'appel signal() précédent? J'ai des problèmes avec la nouvelle structure que je dois créer / passer et comment tout cela s'intègre.

  5. Est le deuxième appel à signal() nécessaire?
    Est-il quelque chose de similaire à ce que je dois être préoccupé par sigaction()?

Pour être clair, tout ce que j'essaie d'accomplir pour que ma boucle principale soit exécutée jusqu'à ctrlc {[9] } ou l'alimentation est déconnectée ou quelque chose de vraiment mauvais se produit.

29
demandé sur Community 2013-07-30 12:33:21

5 réponses

[Q-3] le terminate variable dans mon exemple être volatile? J'ai vu de nombreux exemples où cette variable est volatile, et d'autres où il n'est pas.

Le drapeau terminate devrait ê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é à n'importe quel moment du programme, de manière 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 meilleur pratique pour déclarer volatile sig_atomic_t, ce type sont toujours accessibles de manière atomique, éviter l'incertitude sur l'interruption de l'accès à une variable. volatile indique au compilateur de ne pas optimiser et de le mettre en Registre. (lire: accès aux données atomiques et traitement des signaux pour l'expiation détaillée).
Une autre référence: 24.4.7 accès aux données atomiques et traitement des signaux . En outre, la norme C11 dans 7.14.1.1-5 indique que seuls les objets de volatile sig_atomic_t peuvent être accessibles à partir d'un gestionnaire de signal (accès à d'autres a un comportement indéfini).

[Q-4] j'ai lu que signal() est maintenant obsolète, et d'utiliser sigaction(). Être - il vraiment de bons exemples pour montrer comment convertir de la appel précédent signal()? J'ai des problèmes avec la nouvelle structure qui Je dois créer / passer et comment tout cela va ensemble.

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. Début De Linux Programmation, 4ème édition: dans ce livre, exactement votre code est expliqué avec sigaction() joliment dans "Chapitre 11: processus et signaux".
  2. la documentation sigaction , y compris un exemple (apprentissage rapide).
  3. la bibliothèque C GNU: gestion des signaux
    *je suis parti de 1, actuellement je lis 3 bibliothèque GNU

[Q-5] Est le deuxième appel à signal() nécessaire? Est-il quelque chose de similaire à ce que je dois être préoccupé par sigaction()?

Pourquoi vous le définissez sur default-action avant la fin du programme n'est pas clair pour moi. Je pense que le paragraphe suivant vous donnera une réponse:

La Manipulation Des Signaux

L'appel au signal établit la gestion du signal pour seulement une occurrence d'un signal. Avant d'appeler 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 produit à nouveau. La réinitialisation de la gestion du signal permet d'éviter une boucle infinie si, par exemple, une action effectuée dans le gestionnaire de signal soulève à nouveau le même signal. Si vous voulez que votre gestionnaire soit utilisé pour un signal chaque fois qu'il se produit, vous devez appeler signal dans le gestionnaire pour le rétablir. Vous devriez être prudent dans la réinstallation de la gestion du signal. Par exemple, si vous rétablissez continuellement la gestion SIGINT, Vous risquez de perdre la capacité d'interrompre et de mettre fin à votre programme.

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

Lire une discussion pour plus de référence: quand réactiver les gestionnaires de signaux .

[Q-1a] {[24] } un traitement du signal est-il nécessaire?

Oui, Linux fera le nettoyage pour vous. Par exemple, si vous ne fermez pas un fichier ou un 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 garder 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 immédiatement le socket pour s'assurer que toutes les données ont été transmises, TCP garantit la livraison si cela est possible.

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

Non, le système d'exploitation effectue le nettoyage uniquement après la fin du programme. Pendant qu'un processus s'exécute, les ressources allouées à ce processus ne sont pas réclamées par le système d'exploitation. (Le système D'exploitation ne peut pas savoir si votre processus est dans une boucle infinie ou non - c'est un insoluble problème). Si vous voulez 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 au cas où votre processus se terminerait anormalement par un signal).

[Q] Tout ce que j'essaie d'accomplir pour que ma boucle principale soit exécutée jusqu'à ctrlc {[122] } ou l'alimentation est déconnectée ou quelque chose de vraiment mauvais arrive.

Non, il y a une limite! Vous ne pouvez pas attraper tous les signaux. Certains signaux ne sont pas capturables par exemple SIGKILL et SIGSTOP et les deux sont des signaux de terminaison. En 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.

Donc vous ne pouvez pas faire un 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 TSRs (une sorte d'accrochage en mode noyau). Je me souviens de mon temps de thèse que certains virus ne pouvaient pas être terminés même à partir du gestionnaire de tâches, mais je crois aussi qu'ils trompent l'utilisateur par des autorisations d'administrateur.

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

28
répondu Grijesh Chauhan 2017-05-23 12:34:01

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
répondu Some programmer dude 2013-07-30 08:50:33

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

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

2. Existe-il d'autres signaux que je dois être préoccupé par ce qui concerne la résiliation propre?

  • Tout d'abord, jetez un oeil à sa page: les signaux de la bibliothèque GNU Les signaux de terminaison sont ce que vous prenez soin. Mais jetez un oeil à SIGUSR1 et SIGUSR2, même si vous ne les trouverez jamais dans un logiciel, sauf à des fins de débogage.

  • Tous ces signaux de terminaison doivent être gérés si vous ne voulez pas que votre logiciel se termine tout d'un coup.

3. La variable terminate 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 une norme C.

  • Signal() fonctionne très bien pour moi, mais si vous voulez un exemple : Exemple d'IBM

5
répondu Timothee Tosi 2013-08-06 14:48:41

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

En termes de signaux généralement, vous devriez utiliser sigaction not signal ces jours-ci, cela évite un problème de standardisation.

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

Le Livre de W. Richard Stevens "Advanced Programming in the UNIX Environment" 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 tuez simplement l'application, ce n'est pas nécessaire.

4
répondu Joe 2013-07-30 08:45:20

Tout d'abord - si vous ne savez pas, si vous devez gérer des signaux, alors vous ne le faites probablement pas. les signaux nécessitent une gestion dans certains cas spécifiques, comme la fermeture des 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-ce while (!terminate) sleep(2); n'est pas très bon-dans le pire des cas, il pourrait rendre l'utilisateur (ou même le système) impatient d'avoir à attendre 2s et à envoyer à votre programme un SIGKILL, ce que vous ne pouvez pas gérer.

IMHO la meilleure solution ici serait en utilisant signalfd et sélectionnez , de sorte que vous pouvez terminer votre programme sans avoir à attendre 2s.

0
répondu zoska 2013-08-04 19:54:33