GNU/Linux >> Tutoriels Linux >  >> Linux

Fondamentaux des signaux Linux - Partie I

Qu'est-ce qu'un signal ? Les signaux sont des interruptions logicielles.

Un programme robuste doit gérer les signaux. En effet, les signaux sont un moyen de fournir des événements asynchrones à l'application.

Un utilisateur appuyant sur ctrl+c, un processus envoyant un signal pour tuer un autre processus, etc. sont autant de cas où un processus doit gérer le signal.

Signaux Linux

Sous Linux, chaque signal a un nom qui commence par les caractères SIG. Par exemple :

  • Un signal SIGINT généré lorsqu'un utilisateur appuie sur ctrl+c. C'est le moyen de terminer les programmes depuis le terminal.
  • Un SIGALRM est généré lorsque la minuterie définie par la fonction d'alarme se déclenche.
  • Un signal SIGABRT est généré lorsqu'un processus appelle la fonction d'abandon.
  • etc

Lorsque le signal se produit, le processus doit dire au noyau quoi en faire. Il peut y avoir trois options par lesquelles un signal peut être disposé :

  1. Le signal peut être ignoré. En ignorant, nous voulons dire que rien ne sera fait lorsque le signal se produira. La plupart des signaux peuvent être ignorés, mais les signaux générés par des exceptions matérielles telles que la division par zéro, s'ils sont ignorés, peuvent avoir des conséquences étranges. De plus, quelques signaux comme SIGKILL et SIGSTOP ne peuvent pas être ignorés.
  2. Le signal peut être capté. Lorsque cette option est choisie, le processus enregistre une fonction avec le noyau. Cette fonction est appelée par le noyau lorsque ce signal se produit. Si le signal n'est pas fatal pour le processus, alors dans cette fonction, le processus peut gérer le signal correctement ou sinon, il peut choisir de se terminer normalement.
  3. Laissez l'action par défaut s'appliquer. Chaque signal a une action par défaut. Il peut s'agir de terminer le processus, d'ignorer, etc.

Comme nous l'avons déjà dit, deux signaux SIGKILL et SIGSTOP ne peuvent être ignorés. En effet, ces deux signaux permettent à l'utilisateur root ou au noyau de tuer ou d'arrêter n'importe quel processus dans n'importe quelle situation. L'action par défaut de ces signaux est de terminer le processus. Ni ces signaux ne peuvent être détectés ni ignorés.

Que se passe-t-il au démarrage du programme ?

Tout dépend du processus qui appelle exec. Lorsque le processus est démarré, l'état de tous les signaux est soit ignoré soit par défaut. C'est la dernière option qui est plus susceptible de se produire à moins que le processus qui appelle exec ignore les signaux.

C'est la propriété des fonctions exec de changer l'action sur n'importe quel signal pour qu'elle soit l'action par défaut. En termes plus simples, si le parent a une fonction de capture de signal qui est appelée lors de l'occurrence du signal, alors si ce parent exécute un nouveau processus enfant, alors cette fonction n'a aucune signification dans le nouveau processus et, par conséquent, la disposition du même signal est définie sur la valeur par défaut. dans le nouveau processus.

De plus, étant donné que nous avons généralement des processus exécutés en arrière-plan, le shell définit simplement la disposition du signal de sortie comme ignorée car nous ne voulons pas que les processus d'arrière-plan soient terminés par un utilisateur appuyant sur une touche ctrl + c car cela va à l'encontre de l'objectif de faire un processus exécuter en arrière-plan.

Pourquoi les fonctions de capture de signal devraient-elles être réentrantes ?

Comme nous l'avons déjà mentionné, l'une des options de disposition du signal consiste à capter le signal. Dans le code de processus, cela se fait en enregistrant une fonction dans le noyau que le noyau appelle lorsque le signal se produit. Une chose à garder à l'esprit est que la fonction enregistrée par le processus doit être réentrante.

Avant d'expliquer la raison, comprenons d'abord quelles sont les fonctions réentrantes ? Une fonction réentrante est une fonction dont l'exécution peut être arrêtée entre-temps pour n'importe quelle raison (par exemple en raison d'une interruption ou d'un signal), puis peut être ressaisie en toute sécurité avant que ses invocations précédentes ne terminent l'exécution.

Revenant maintenant au problème, supposons qu'une fonction func() soit enregistrée pour un rappel sur une occurrence de signal. Supposons maintenant que cette fonction () était déjà en cours d'exécution lorsque le signal s'est produit. Étant donné que cette fonction est rappelée pour ce signal, l'exécution en cours sur ce signal sera arrêtée par le planificateur et cette fonction sera appelée à nouveau (en raison du signal).

Le problème peut être que si func() fonctionne sur certaines valeurs globales ou structures de données qui sont laissées dans un état incohérent lorsque l'exécution de cette fonction a été arrêtée au milieu, le deuxième appel à la même fonction (en raison du signal) peut entraîner des résultats indésirables.

Nous disons donc que les fonctions de capture de signal doivent être rendues réentrantes.

Reportez-vous à nos articles send-signal-to-process et Linux fuser command pour voir des exemples pratiques sur la façon d'envoyer des signaux à un processus.

Threads et signaux

Nous avons déjà vu dans l'une des sections précédentes que la gestion du signal a sa propre complexité (comme l'utilisation de fonctions réentrantes). Pour ajouter à la complexité, nous avons généralement des applications multi-threads où la gestion du signal devient vraiment compliquée.

Chaque thread a son propre masque de signal privé (un masque qui définit quels signaux sont livrables) mais la façon dont la disposition du signal est effectuée est partagée par tous les threads de l'application. Cela signifie qu'une disposition pour un signal particulier défini par un thread peut facilement être annulée par un autre thread. Dans ce cas, le mécanisme de disposition change pour tous les threads.

Par exemple, un thread A peut choisir d'ignorer un signal particulier, mais un thread B du même processus peut choisir d'attraper le même signal en enregistrant une fonction de rappel dans le noyau. Dans ce cas, la demande faite par le thread A est annulée par la demande du thread B.

Les signaux ne sont délivrés qu'à un seul thread dans n'importe quel processus. Hormis les exceptions matérielles ou l'expiration de la minuterie (qui sont transmises au thread qui a provoqué l'événement), tous les signaux sont transmis arbitrairement au processus.

Pour contrer cette lacune, il existe des API posix telles que pthread_sigmask(), etc. qui peuvent être utilisées.

Dans le prochain article (partie 2) de cette série, nous discuterons de la manière de capturer les signaux dans un processus et expliquerons l'aspect pratique de la gestion des signaux à l'aide d'extraits de code.


Linux
  1. UNIX / Linux :3 façons d'envoyer un signal aux processus

  2. Créer un processus Linux ?

  3. Quand setsid() est-il utile ou pourquoi devons-nous regrouper les processus sous Linux ?

  4. Créer un démon sous Linux

  5. Linux :transformer en service

Comment tuer un processus sous Linux

Commande Pstree sous Linux

Commande Kill sous Linux

Surveillance des processus sous Linux

Linux Bash Scripting Part5 - Signaux et tâches

Qu'est-ce qu'un processus arrêté sous Linux ?