Comment propager un signal aux processus enfants à partir d'un script bash
- 1959
- 500
- Maxence Arnaud
Supposons que nous écrivions un script qui engendre un ou plusieurs processus en cours d'exécution; Si ledit script reçoit un signal tel que Sigint
ou Sigterm
, Nous voulons probablement aussi que ses enfants soient résiliés (normalement lorsque le parent décède, les enfants survivent). Nous pouvons également vouloir effectuer des tâches de nettoyage avant que le script lui-même ne sorte. Pour pouvoir atteindre notre objectif, nous devons d'abord en savoir plus sur les groupes de processus et comment exécuter un processus en arrière-plan.
Dans ce tutoriel, vous apprendrez:
- Qu'est-ce qu'un groupe de processus
- La différence entre les processus de premier plan et de fond
- Comment exécuter un programme en arrière-plan
- Comment utiliser la coquille
attendez
intégré pour attendre un processus exécuté en arrière-plan - Comment résilier les processus enfants lorsque le parent reçoit un signal
Exigences et conventions logicielles utilisées
Catégorie | Exigences, conventions ou version logicielle utilisée |
---|---|
Système | Distribution indépendante |
Logiciel | Aucun logiciel spécifique nécessaire |
Autre | Aucun |
Conventions | # - Exige que les commandes Linux soient exécutées avec des privilèges racine soit directement en tant qu'utilisateur racine, soit par l'utilisation de Sudo commande$ - Exige que les commandes Linux soient exécutées en tant qu'utilisateur non privilégié régulier |
Un exemple simple
Créons un script très simple et simulons le lancement d'un processus de longue date:
#!/ Bin / Bash Trap "Signal d'écho reçu!"Sigint Echo" Le Script Pid est $ "Sleep 30
Copie La première chose que nous avons faite dans le script a été de créer un piège à attraper Sigint
et imprimer un message lorsque le signal est reçu. Nous avons fait imprimer notre script piquer: nous pouvons obtenir en élargissant le $$
variable. Ensuite, nous avons exécuté le dormir
commande pour simuler un processus de longue date (30
secondes).
Nous enregistrons le code dans un fichier (disons qu'il s'appelle test.shot
), le rendre exécutable et le lancer à partir d'un émulateur de terminal. Nous obtenons les résultats suivants:
Le Script Pid est 101248
Si nous nous concentrons sur l'émulateur de terminal et appuyez sur Ctrl + C pendant l'exécution du script, un Sigint
Le signal est envoyé et manipulé par notre piège:
Le script PID est 101248 ^ csignal reçu!
Bien que le piège ait géré le signal comme prévu, le script a été interrompu de toute façon. Pourquoi c'est arrivé? De plus, si nous envoyons un Sigint
signal au script en utilisant le tuer
Commande, le résultat que nous obtenons est très différent: le piège n'est pas immédiatement exécuté, et le script continue jusqu'à ce que le processus de l'enfant ne sorte pas (après 30
secondes de «dormir»). Pourquoi cette différence? Voyons…
Groupes de processus, emplois de premier plan et arrière-plan
Avant de répondre aux questions ci-dessus, nous devons mieux saisir le concept de groupe de processus.
Un groupe de processus est un groupe de processus qui partagent le même PGID (ID de groupe de processus). Lorsqu'un membre d'un groupe de processus crée un processus enfant, ce processus devient membre du même groupe de processus. Chaque groupe de processus a un leader; Nous pouvons facilement le reconnaître parce que c'est piquer et le PGID sont identiques.
Nous pouvons visualiser piquer et PGID de processus en cours d'exécution en utilisant le ps
commande. La sortie de la commande peut être personnalisée afin que seuls les champs qui nous intéressent sont affichés: dans ce cas CMD, Piquer et PGID. Nous le faisons en utilisant le -o
Option, fournir une liste de champs séparée par des virgules comme argument:
$ ps -a -o pid, pgid, cmd
Si nous exécutons la commande pendant que notre script exécute la partie pertinente de la sortie que nous obtenons est la suivante:
PID PGID CMD 298349 298349 / BIN / BASH ./test.SH 298350 298349 sommeil 30
Nous pouvons clairement voir deux processus: le piquer du premier est 298349
, Identique que c'est PGID: C'est le chef de groupe de processus. Il a été créé lorsque nous avons lancé le script comme vous pouvez le voir dans le CMD colonne.
Ce processus principal a lancé un processus d'enfant avec la commande sommeil 30
: Comme prévu, les deux processus sont dans le même groupe de processus.
Lorsque nous avons appuyé sur Ctrl-C tout en nous concentrant sur le terminal à partir duquel le script a été lancé, le signal n'a pas été envoyé uniquement au processus parent, mais à l'ensemble du groupe de processus. Quel groupe de processus? Le groupe de processus de premier plan du terminal. Tous les processus membre de ce groupe sont appelés processus de premier plan, Tous les autres sont appelés Processus en arrière-plan. Voici ce que le manuel Bash a à dire sur la question:
SAVIEZ-VOUS?Pour faciliter la mise en œuvre de l'interface utilisateur au contrôle du travail, le système d'exploitation maintient la notion d'un ID de groupe de processus terminal actuel. Les membres de ce groupe de processus (processus dont l'ID de groupe de processus est égal à l'ID actuel du groupe de processus terminaux) reçoivent des signaux générés par le clavier tels que Sigint. Ces processus seraient au premier plan. Les processus de fond sont ceux dont l'ID de groupe de processus diffère des terminaux; Ces processus sont à l'abri des signaux générés par le clavier.Quand nous avons envoyé le Sigint
signal avec le tuer
Commande, à la place, nous n'avons ciblé que le PID du processus parent; Bash présente un comportement spécifique lorsqu'un signal est reçu pendant qu'il attend qu'un programme se termine: le «code de piège» pour ce signal n'est pas exécuté jusqu'à ce que ce processus ait terminé. C'est pourquoi le message «signal reçu» n'a été affiché qu'après le dormir
Commande sortie.
Pour reproduire ce qui se passe lorsque nous appuyons sur Ctrl-C dans le terminal en utilisant le tuer
Commande pour envoyer le signal, nous devons cibler le groupe de processus. Nous pouvons envoyer un signal à un groupe de processus en utilisant La négation du PID du chef de processus, Alors, en supposant le piquer du leader du processus est 298349
(Comme dans l'exemple précédent), nous courions:
$ kill -2 -298349
Gérer la propagation du signal à l'intérieur d'un script
Maintenant, supposons que nous lançons un long script en cours d'exécution à partir d'un shell non interactif, et nous voulons que ledit script gère automatiquement la propagation du signal, de sorte que lorsqu'il reçoit un signal tel que Sigint
ou Sigterm
Il met fin à son enfant potentiellement long, effectuant finalement certaines tâches de nettoyage avant de sortir. Comment nous pouvons faire ça?
Comme nous l'avons fait précédemment, nous pouvons gérer la situation dans laquelle un signal est reçu dans un piège; Cependant, comme nous l'avons vu, si un signal est reçu tandis que le shell en attente d'un programme se termine, le «code de piège» n'est exécuté qu'après que le processus de l'enfant soit sorti.
Ce n'est pas ce que nous voulons: nous voulons que le code de piège soit traité dès que le processus parent reçoit le signal. Pour atteindre notre objectif, nous devons exécuter le processus enfant dans le arrière-plan: nous pouvons le faire en plaçant le &
symbole après la commande. Dans notre cas, nous écrivions:
#!/ Bin / Bash Trap 'Signal Echo reçu!'Sigint écho "Le script pid est $" sleep 30 &
Copie Si nous quittons le script de cette façon, le processus parent sortirait juste après l'exécution du sommeil 30
commande, nous laissant sans avoir la chance d'effectuer des tâches de nettoyage après sa fin ou est interrompue. Nous pouvons résoudre ce problème en utilisant le shell attendez
intégré. La page d'aide de attendez
le définit de cette façon:
Attend chaque processus identifié par un identifiant, qui peut être un identifiant de processus ou une spécification de travail, et rapporte son statut de résiliation. Si ID n'est pas donné, attend tous les processus enfants actuellement actifs, et l'état de retour est nul.
Après avoir défini un processus à exécuter en arrière-plan, nous pouvons récupérer son piquer dans le $!
variable. Nous pouvons le transmettre comme argument à attendez
Pour faire en sorte que le processus parent attende son enfant:
#!/ Bin / Bash Trap 'Signal Echo reçu!'SIGINT ECHO "Le Script Pid est $" Sleep 30 & Wait $!
Copie Allons-nous fini? Non, il y a toujours un problème: la réception d'un signal manipulé dans un piège à l'intérieur du script, provoque le attendez
intégré pour revenir immédiatement, sans attendre réellement la résiliation de la commande en arrière-plan. Ce comportement est documenté dans le manuel de bash:
Pour résoudre ce problème, nous devons utiliser attendez
Encore une fois, peut-être dans le cadre du piège lui-même. Voici à quoi pourrait ressembler notre script à la fin:
#!/ bin / bash cleanup () echo "nettoyer…" # Notre code de nettoyage va ici Trap 'Signal Echo reçu!; tuer "$ child_pid"; attendez "$ child_pid"; Cleanup 'SIGINT SIGTERM Echo "Le Script Pid est $" Sleep 30 & child_pid = "$!"attendre" $ child_pid "
Copie Dans le script, nous avons créé un nettoyer
fonction où nous pourrions insérer notre code de nettoyage et faire notre piège
attraper aussi le Sigterm
signal. Voici ce qui se passe lorsque nous exécutons ce script et envoyons l'un de ces deux signaux:
- Le script est lancé et le
sommeil 30
La commande est exécutée en arrière-plan; - Le piquer du processus enfant est «stocké» dans le
child_pid
variable; - Le script attend la fin du processus de l'enfant;
- Le script reçoit un
Sigint
ouSigterm
signal - Le
attendez
La commande revient immédiatement, sans attendre la résiliation des enfants;
À ce stade, le piège est exécuté. En elle:
- UN
Sigterm
signal (letuer
par défaut) est envoyé auchild_pid
; - Nous
attendez
Pour s'assurer que l'enfant est interrompu après avoir reçu ce signal. - Après
attendez
retourne, nous exécutons lenettoyer
fonction.
Propager le signal à plusieurs enfants
Dans l'exemple ci-dessus, nous avons travaillé avec un script qui n'avait qu'un seul processus d'enfant. Et si un script a beaucoup d'enfants, et si certains d'entre eux ont leurs propres enfants?
Dans le premier cas, un moyen rapide d'obtenir le pides de tous les enfants, c'est utiliser le emplois -p
Commande: Cette commande affiche les pides de tous les travaux actifs dans le shell actuel. Nous pouvons que utiliser tuer
pour les terminer. Voici un exemple:
#!/ bin / bash cleanup () echo "nettoyer…" # Notre code de nettoyage va ici Trap 'Signal Echo reçu!; tuer $ (jobs -p); attendez; Cleanup 'Sigint Sigterm Echo "Le script pid est $" Sleep 30 & Sleep 40 & Wait
Copie Le script lance deux processus en arrière-plan: en utilisant le attendez
Construit sans arguments, nous les attendons tous et maintenons le processus parent en vie. Quand le Sigint
ou Sigterm
Les signaux sont reçus par le script, nous envoyons un Sigterm
à les deux, faisant revenir leurs pides par le emplois -p
commande (emploi
est lui-même une coque intégrée, donc lorsque nous l'utilisons, un nouveau processus n'est pas créé).
Si les enfants ont des enfants qui leur sont propres et que nous voulons tous les mettre fin lorsque l'ancêtre reçoit un signal, nous pouvons envoyer un signal à l'ensemble du groupe de processus, comme nous l'avons vu auparavant.
Ceci, cependant, présente un problème, car en envoyant un signal de terminaison au groupe de processus, nous entrions une boucle «Signal-Sent / Signal Tapked». Pensez-y: dans le piège
pour Sigterm
Nous envoyons un Sigterm
signal à tous les membres du groupe de processus; Cela inclut le script parent lui-même!
Pour résoudre ce problème et être en mesure d'exécuter une fonction de nettoyage après la fin des processus d'enfants, nous devons modifier le piège
pour Sigterm
Juste avant d'envoyer le signal au groupe de processus, par exemple:
#!/ bin / bash Cleanup () echo "Cleaning Up…" # Notre code de nettoyage va ici trap 'trap "" Sigterm; tuer 0; attendez; Cleanup 'Sigint Sigterm Echo "Le script pid est $" Sleep 30 & Sleep 40 & Wait
Copie Dans le piège, avant d'envoyer Sigterm
Au groupe de processus, nous avons changé le Sigterm
Piège, de sorte que le processus parent ignore le signal et seuls ses descendants sont affectés par celui-ci. Notez également que dans le piège, pour signaler le groupe de processus, nous avons utilisé tuer
avec 0
comme pid. C'est une sorte de raccourci: quand le piquer transmis à tuer
est 0
, Tous les processus dans le actuel Le groupe de processus est signalé.
Conclusions
Dans ce tutoriel, nous avons appris les groupes de processus et quelle est la différence entre les processus de premier plan et de fond. Nous avons appris que Ctrl-C envoie un Sigint
signal à l'ensemble du groupe de processus de premier plan du terminal de contrôle, et nous avons appris à envoyer un signal à un groupe de processus en utilisant tuer
. Nous avons également appris à exécuter un programme en arrière-plan et à utiliser le attendez
Shell intégré pour attendre qu'il quitte sans perdre la coque parentale. Enfin, nous avons vu comment configurer un script afin que lorsqu'il reçoit un signal, il met fin à ses enfants avant de quitter. Ai-je manqué quelque chose? Avez-vous vos recettes personnelles pour accomplir la tâche? N'hésitez pas à me le faire savoir!
Tutoriels Linux connexes:
- Gestion des processus de fond de bash
- Gestion de scripts et de processus de bash multithread au…
- Une introduction à l'automatisation Linux, des outils et des techniques
- Comment exécuter la commande en arrière-plan sur Linux
- Masterring Bash Script Loops
- Comment installer le signal sur Linux
- Comment tracer les appels du système effectués par un processus avec Strace sur…
- Mint 20: Mieux que Ubuntu et Microsoft Windows?
- Comparaison des MPM de Linux Apache PreFork vs Worker
- Comment travailler avec les groupes de packages DNF
- « MySQL Modifier le mot de passe utilisateur
- Comment reconstruire un package à l'aide du système de construction Arch Linux »