Wie man ein Signal an Kinderprozesse aus einem Bash -Skript ausbreitet

Wie man ein Signal an Kinderprozesse aus einem Bash -Skript ausbreitet

Angenommen, wir schreiben ein Skript, das ein oder mehrere lang laufende Prozesse hervorbringt. Wenn dieses Skript ein Signal wie z Sigint oder Sigterm, Wir möchten wahrscheinlich, dass seine Kinder auch beendet werden (normalerweise, wenn der Elternteil stirbt, überlebt die Kinder). Möglicherweise möchten wir auch einige Aufräumarbeiten ausführen, bevor das Skript selbst beendet ist. Um unser Ziel erreichen zu können, müssen wir zunächst über Prozessgruppen und die Ausführung eines Prozesses im Hintergrund lernen.

In diesem Tutorial lernen Sie:

  • Was ist eine Prozessgruppe
  • Der Unterschied zwischen Vordergrund- und Hintergrundprozessen
  • So führen Sie ein Programm im Hintergrund aus
  • So verwenden Sie die Schale Warten eingebaut, um auf einen im Hintergrund ausgeführten Prozess zu warten
  • Wie man untergeordnete Prozesse beendet, wenn der Elternteil ein Signal empfängt
Wie man ein Signal an Kinderprozesse aus einem Bash -Skript ausbreitet

Softwareanforderungen und Konventionen verwendet

Softwareanforderungen und Linux -Befehlszeilenkonventionen
Kategorie Anforderungen, Konventionen oder Softwareversion verwendet
System Verteilung unabhängig
Software Keine spezifische Software benötigt
Andere Keiner
Konventionen # - erfordert, dass gegebene Linux -Befehle mit Root -Berechtigungen entweder direkt als Stammbenutzer oder mit Verwendung von ausgeführt werden können sudo Befehl
$ - Erfordert, dass die angegebenen Linux-Befehle als regelmäßiger nicht privilegierter Benutzer ausgeführt werden können

Ein einfaches Beispiel

Erstellen wir ein sehr einfaches Skript und simulieren den Start eines langen Laufprozesses:

#!/Bin/Bash Trap "Echo -Signal empfangen!"Sigint Echo" Die Skriptpid ist $ "Schlaf 30 
Kopieren

Das erste, was wir im Skript gemacht haben, war, eine Falle zum Fangen zu erstellen Sigint und drucken Sie eine Nachricht, wenn das Signal empfangen wird. Wir haben unser Drehbuch drucken lassen PID: Wir können bekommen, indem wir die erweitern $$ Variable. Als nächstes haben wir das ausgeführt schlafen Befehl zum Simulieren eines langen Laufprozesses (30 Sekunden).

Wir speichern den Code in einer Datei (sagen wir, er heißt prüfen.Sch), machen Sie es ausführbar und starten Sie es von einem Terminalemulator. Wir erhalten das folgende Ergebnis:

Die SkriptpID ist 101248 

Wenn wir uns auf den Terminalemulator konzentrieren und Strg+C drücken, während das Skript ausgeführt wird, a Sigint Signal wird von unserem gesendet und behandelt fangen:

Die SkriptpID ist 101248 ^csignal empfangen! 

Obwohl die Falle das Signal erwartungsgemäß behandelte, wurde das Drehbuch ohnehin unterbrochen. Warum ist das passiert? Außerdem, wenn wir eine senden Sigint Signal auf das Skript mit dem töten Befehl, das Ergebnis, das wir erhalten, ist ganz anders: Die Falle wird nicht sofort ausgeführt, und das Skript wird fort 30 Sekunden von „Schlafen“). Warum dieser Unterschied? Mal sehen…

Prozessgruppen, Vordergrund- und Hintergrundjobs

Bevor wir die obigen Fragen beantworten, müssen wir das Konzept von besser erfassen Prozessgruppe.

Eine Prozessgruppe ist eine Gruppe von Prozessen, die dasselbe teilen pgid (Prozessgruppe ID). Wenn ein Mitglied einer Prozessgruppe einen Kinderprozess erstellt, wird dieser Prozess Mitglied derselben Prozessgruppe. Jede Prozessgruppe hat einen Führer; Wir können es leicht erkennen, weil es es ist PID und das pgid sind gleich.

Wir können visualisieren PID Und pgid von laufenden Prozessen mit dem ps Befehl. Die Ausgabe des Befehls kann so angepasst werden, dass nur die Felder angezeigt werden: in diesem Fall CMD, PID Und Pgid. Wir tun dies, indem wir die verwenden Option, Bereitstellung einer von Kommas getrennten Liste von Feldern als Argument:

$ ps -a -o pid, pgid, cmd 

Wenn wir den Befehl ausführen, während unser Skript den relevanten Teil der Ausgabe ausführt, die wir erhalten, ist dies Folgendes:

 PID PGID CMD 298349 298349 /Bin /Bash ./prüfen.Sh 298350 298349 Schlaf 30 

Wir können deutlich zwei Prozesse sehen: die PID des ersten ist 298349, Gleich wie es pgid: Dies ist der Leiter der Prozessgruppe. Es wurde erstellt, als wir das Skript starteten, wie Sie in der sehen können CMD Spalte.

Dieser Hauptprozess hat einen Kinderprozess mit dem Befehl gestartet Schlaf 30: Wie erwartet befinden sich die beiden Prozesse in derselben Prozessgruppe.

Als wir Strg-C drückten, während wir uns auf das Terminal konzentrierten, von dem das Skript gestartet wurde, wurde das Signal nicht nur an den übergeordneten Prozess, sondern an die gesamte Prozessgruppe gesendet. Welche Prozessgruppe? Der Vordergrundprozessgruppe des Terminals. Alle Prozesse Mitglied dieser Gruppe werden genannt Vordergrundprozesse, Alle anderen werden genannt Hintergrundprozesse. Hier ist das, was das Bash -Handbuch zu diesem Thema zu sagen hat:

WUSSTEN SIE?Um die Implementierung der Benutzeroberfläche zur Arbeitskontrolle zu erleichtern, behält das Betriebssystem den Begriff einer aktuellen Terminalprozessgruppen -ID bei. Mitglieder dieser Prozessgruppe (Prozesse, deren Prozessgruppen-ID der aktuellen Terminalprozessgruppen-ID entspricht) empfangen Tastatur-generierte Signale wie SIGINT. Diese Prozesse sollen im Vordergrund stehen. Hintergrundprozesse sind diejenigen, deren Prozessgruppen -ID von den Terminals unterscheidet. Solche Prozesse sind immun gegen Tastaturgenerierte Signale.

Als wir die schickten Sigint signalisieren mit dem töten Der Befehl stattdessen haben wir nur auf die PID des übergeordneten Prozesses gerichtet. Bash zeigt ein bestimmtes Verhalten, wenn ein Signal empfangen wird, während es auf ein Programm wartet: Der „Trap -Code“ für dieses Signal wird erst nach Abschluss dieses Vorgangs ausgeführt. Aus diesem Grund wurde die Meldung "empfangener" erst nach der angezeigt schlafen Befehl beendet.

Um zu replizieren, was passiert, wenn wir Strg-c im Terminal mit der Klemme drücken töten Befehl zum Senden des Signals müssen wir auf die Prozessgruppe abzielen. Wir können ein Signal an eine Prozessgruppe senden, indem wir verwenden Die Verneinung der PID des Prozessleiters, Angenommen, die PID des Prozessleiters ist 298349 (Wie im vorherigen Beispiel) würden wir rennen:

$ Kill -2 -298349 

Verwalten Sie die Signalausbreitung von innerhalb eines Skripts aus

Nehmen wir nun an, wir starten ein langjähriges Skript von einer nicht interaktiven Shell und möchten, dass das Skript automatisch die Signalausbreitung verwaltet, sodass beim Empfang eines Signals wie z Sigint oder Sigterm Es beendet sein potenziell langes Kind und führt schließlich einige Aufräumarbeiten aus, bevor er beendet ist. Wie wir das tun können?

Wie wir zuvor können wir die Situation bewältigen, in der ein Signal in einer Falle empfangen wird. Wie wir jedoch gesehen haben, wird der „Trap -Code“ nur nach dem Ausgang des untergeordneten Prozess.

Dies ist nicht das, was wir wollen: Wir möchten, dass der Trap -Code verarbeitet wird, sobald der übergeordnete Prozess das Signal empfängt. Um unser Ziel zu erreichen, müssen wir den Kinderprozess in der durchführen Hintergrund: Wir können dies tun, indem wir die platzieren & Symbol nach dem Befehl. In unserem Fall würden wir schreiben:

#!/Bin/Bash Trap 'Echo -Signal empfangen!'Sigint echo' Die Skriptpid ist $ "Schlaf 30 & 
Kopieren

Wenn wir das Skript auf diese Weise verlassen würden, würde der übergeordnete Prozess direkt nach der Ausführung der Ausführung des Schlaf 30 Befehl, die uns ohne die Chance verlassen, Aufgaben nach dem Ende auszuführen oder unterbrochen zu werden. Wir können dieses Problem durch die Verwendung der Shell lösen Warten eingebaut. Die Hilfe Seite von Warten definiert es so:



Warten auf jeden durch eine ID identifizierten Prozess, der eine Prozess -ID oder eine Jobspezifikation sein kann, und meldet den Kündigungsstatus. Wenn ID nicht angegeben ist, wartet auf alle aktuell aktiven Kinderprozesse und der Rückgabestatus ist Null.

Nachdem wir einen Prozess festgelegt haben, der im Hintergrund ausgeführt werden soll, können wir seine abrufen PID im $! Variable. Wir können es als Argument angeben Warten Damit den übergeordneten Prozess auf sein Kind warten lassen:

#!/Bin/Bash Trap 'Echo -Signal empfangen!'Sigint echo "Die Skriptpid ist $" Schlaf 30 & warte $ $! 
Kopieren

Sind wir fertig? Nein, es gibt immer noch ein Problem: Die Rezeption eines Signals, das in einer Falle im Drehbuch behandelt wurde, verursacht die Warten gebaut, um sofort zurückzukehren, ohne tatsächlich auf die Beendigung des Befehls im Hintergrund zu warten. Dieses Verhalten ist im Bash -Handbuch dokumentiert:

Wenn Bash über das Wait -Bau auf einen asynchronen Befehl wartet, wird der Empfang eines Signals, für das eine Falle festgelegt wurde. Dies ist gut, da das Signal sofort behandelt wird und die Falle ausgeführt wird, ohne auf das Kündigen des Kindes warten zu müssen, bringt aber ein Problem auf Prozess verlassen.

Um dieses Problem zu lösen, müssen wir verwenden Warten wieder, vielleicht als Teil der Falle selbst. So könnte unser Skript am Ende aussehen:

#!/bin/bash cleanup () echo "Reinigung ..." # Unser Aufräumcode geht hierher Trap 'Echo -Signal empfangen!; Kill "$ child_pid"; Warten Sie "$ child_pid"; Cleanup 'Sigint Sigterm echo "Die Skriptpid ist $" Sleep 30 & Child_pid = "$!"Warte" $ child_pid " 
Kopieren

In dem Skript haben wir a erstellt Aufräumen Funktion, wo wir unseren Reinigungscode einfügen und unsere gemacht haben fangen fangen auch die Sigterm Signal. Hier ist, was passiert, wenn wir dieses Skript ausführen und eines dieser beiden Signale an sie senden:

  1. Das Skript wird gestartet und das Schlaf 30 Der Befehl wird im Hintergrund ausgeführt;
  2. Der PID des Kinderprozesses wird in der „gespeichert“ child_pid Variable;
  3. Das Skript wartet auf die Beendigung des Kinderprozesses
  4. Das Skript erhält a Sigint oder Sigterm Signal
  5. Der Warten Der Befehl kehrt sofort zurück, ohne auf die Kündigung des Kindes zu warten

Zu diesem Zeitpunkt wird die Falle ausgeführt. Drin:

  1. A Sigterm Signal (die töten Standard) wird an die gesendet child_pid;
  2. Wir Warten Um sicherzustellen, dass das Kind nach Erhalt dieses Signals gekündigt wird.
  3. Nach Warten Gibt zurück, wir führen die aus Aufräumen Funktion.

Propagieren Sie das Signal an mehrere Kinder

Im obigen Beispiel haben wir mit einem Skript gearbeitet, das nur einen Kinderprozess hatte. Was ist, wenn ein Drehbuch viele Kinder hat und was ist, wenn einige von ihnen eigene Kinder haben??

Im ersten Fall eine schnelle Möglichkeit, das zu bekommen Pids Von allen Kindern ist es, die zu verwenden Jobs -p Befehl: Dieser Befehl zeigt die PIDs aller aktiven Jobs in der aktuellen Shell an. Wir können als benutzen töten sie zu beenden. Hier ist ein Beispiel:

#!/bin/bash cleanup () echo "Reinigung ..." # Unser Aufräumcode geht hierher Trap 'Echo -Signal empfangen!; töten $ (jobs -p); Warten; Cleanup 'Sigint Sigterm echo "Die Skriptpid ist $" Sleep 30 & Sleep 40 & Wait 
Kopieren

Das Skript startet zwei Prozesse im Hintergrund: mithilfe der Verwendung Warten ohne Argumente eingebaut, warten wir auf alle und halten den Elternprozess am Leben. Wenn das Sigint oder Sigterm Signale werden vom Skript empfangen, wir senden eine Sigterm An beiden von ihnen, dass ihre Pids von der zurückgegeben werden Jobs -p Befehl (Arbeit ist selbst eine integrierte Shell. Wenn wir sie verwenden, wird ein neuer Prozess nicht erstellt.

Wenn die Kinder ihre eigenen Kinder haben und wir sie alle beenden, wenn der Vorfahr ein Signal erhält, können wir ein Signal an die gesamte Prozessgruppe senden, wie wir zuvor gesehen haben.

Dies stellt jedoch ein Problem dar, da wir durch das Senden eines Terminierungssignals an die Prozessgruppe eine Schleife „Signal-Signal-/Signal eingeschlossen“ eingeben würden. Denken Sie darüber nach: in der fangen für Sigterm Wir senden eine Sigterm Signal an alle Mitglieder der Prozessgruppe; Dies schließt das übergeordnete Skript selbst ein!

Um dieses Problem zu lösen und dennoch eine Reinigungsfunktion nach Beendigung von untergeordneten Prozessen auszuführen, müssen wir die ändern fangen für Sigterm Kurz bevor wir das Signal an die Prozessgruppe senden, zum Beispiel:

#!/bin/bash cleanUp () echo "Reinigung ..." # Unser Aufräumcode geht hierher Trap 'Trap "" Sigterm; töte 0; Warten; Cleanup 'Sigint Sigterm echo "Die Skriptpid ist $" Sleep 30 & Sleep 40 & Wait 
Kopieren

In der Falle vor dem Senden Sigterm In der Prozessgruppe haben wir die geändert Sigterm Trap, so dass der übergeordnete Prozess das Signal ignoriert und nur seine Nachkommen davon betroffen sind. Beachten Sie auch, dass wir in der Falle die Prozessgruppe verwendet haben, um die Prozessgruppe zu signalisieren töten mit 0 als PID. Dies ist eine Art Abkürzung: Wenn die PID weitergereicht an töten Ist 0, alle Prozesse in der aktuell Prozessgruppe werden signalisiert.

Schlussfolgerungen

In diesem Tutorial haben wir uns über Prozessgruppen und den Unterschied zwischen Vordergrund- und Hintergrundprozessen gelernt. Wir haben gelernt, dass Strg-c a sendet Sigint Signal auf die gesamte Vordergrundprozessgruppe des Steuerterminals, und wir haben gelernt, wie man ein Signal an eine Prozessgruppe sendet töten. Wir haben auch gelernt, wie man ein Programm im Hintergrund ausführt und wie man die benutzt Warten Schale eingebaut, um zu warten, bis es ausgeht, ohne die übergeordnete Shell zu verlieren. Schließlich haben wir gesehen, wie man ein Skript so einstellt. Habe ich etwas verpasst? Haben Sie Ihre persönlichen Rezepte, um die Aufgabe zu erfüllen?? Zögern Sie nicht, es mich wissen zu lassen!

Verwandte Linux -Tutorials:

  • Bash -Hintergrundprozessmanagement verleihen
  • Multi-Thread-Bash-Skript- und Prozessmanagement bei der…
  • Eine Einführung in Linux -Automatisierung, Tools und Techniken
  • So führen Sie den Befehl im Hintergrund unter Linux aus
  • Mastering -Bash -Skriptschleifen beherrschen
  • So installieren Sie Signal unter Linux
  • Wie man Systemanrufe verfolgt, die durch einen Prozess mit Strace auf…
  • Mint 20: Besser als Ubuntu und Microsoft Windows?
  • Vergleich von Linux Apache Pre -Onk -vs -Worker -MPMs
  • So arbeiten Sie mit DNF -Paketgruppen