Jak propagować sygnał do procesów dziecka ze skryptu Bash

Jak propagować sygnał do procesów dziecka ze skryptu Bash

Załóżmy, że piszemy skrypt, który spawnuje jeden lub więcej długich procesów; Jeśli wspomniany skrypt otrzyma sygnał taki jak Sigint Lub Sigterm, Prawdopodobnie chcemy, aby jego dzieci również zostały zakończone (zwykle, gdy rodzic umiera, dzieci przetrwają). Możemy również chcieć wykonać zadania oczyszczające, zanim sam skrypt wyjdzie. Aby móc osiągnąć nasz cel, musimy najpierw dowiedzieć się o grupach procesowych i jak wykonać proces w tle.

W tym samouczku się nauczysz:

  • Co to jest grupa procesowa
  • Różnica między procesami pierwszego planu i tła
  • Jak wykonać program w tle
  • Jak używać skorupy Czekać wbudowane, aby czekać na proces wykonany w tle
  • Jak zakończyć procesy dziecka, gdy rodzic otrzymuje sygnał
Jak propagować sygnał do procesów dziecka ze skryptu Bash

Zastosowane wymagania i konwencje oprogramowania

Wymagania oprogramowania i konwencje linii poleceń Linux
Kategoria Wymagania, konwencje lub wersja oprogramowania
System Niezależny dystrybucja
Oprogramowanie Brak konkretnego oprogramowania
Inny Nic
Konwencje # - Wymaga, aby podane polecenia Linux są wykonywane z uprawnieniami root bezpośrednio jako użytkownik root lub za pomocą sudo Komenda
$ - Wymaga, aby podane polecenia Linux zostały wykonane jako zwykły użytkownik niepewny

Prosty przykład

Utwórzmy bardzo prosty skrypt i symuluj uruchomienie długiego procesu:

#!/BIN/BASH TRAP „Otrzymano sygnał echo!„Sigint Echo” Skrypt PID to $ „Sleep 30 
Kopiuj

Pierwszą rzeczą, którą zrobiliśmy w scenariuszu, było stworzenie pułapki do złapania Sigint i wydrukuj wiadomość po odbieraniu sygnału. Wydrukowaliśmy nasz skrypt pid: Możemy zdobyć rozszerzenie $$ zmienny. Następnie wykonaliśmy spać polecenie symulacji długotrwałego procesu (30 sekundy).

Zapisujemy kod w pliku (powiedzmy, że się nazywa test.cii), uczyń go wykonywaniem i uruchom go z emulatora terminalu. Otrzymujemy następujący wynik:

Skrypt PID to 101248 

Jeśli koncentrujemy się na emulatorze terminalu i naciśnij Ctrl+C podczas uruchomienia skryptu, a Sigint sygnał jest wysyłany i obsługiwany przez nasz pułapka:

Skrypt PID jest 101248 ^Csignal otrzymany! 

Chociaż pułapka obsługiwała sygnał zgodnie z oczekiwaniami, skrypt i tak został przerwany. Dlaczego tak się stało? Ponadto, jeśli wyślemy Sigint sygnał do skryptu za pomocą zabić Polecenie, wynik, który otrzymujemy, jest zupełnie inny: pułapka nie jest natychmiast wykonywana, a skrypt trwa, dopóki proces dziecięcy nie wyjdzie (po 30 sekundy „spania”). Dlaczego ta różnica? Zobaczmy…

Grupy procesowe, na pierwszym planie i zadania w tle

Zanim odpowiemy na powyższe pytania, musimy lepiej zrozumieć koncepcję grupa procesowa.

Grupa procesowa to grupa procesów, które dzielą to samo PGID (identyfikator grupy procesowej). Kiedy członek grupy procesowej tworzy proces potomny, proces ten staje się członkiem tej samej grupy procesowej. Każda grupa procesowa ma lidera; Możemy go łatwo rozpoznać, ponieważ to pid i PGID są takie same.

Możemy wizualizować pid I PGID uruchamiania procesów za pomocą Ps Komenda. Wyjście polecenia można było dostosować, aby wyświetlano tylko pola, które jesteśmy zainteresowani: w tym przypadku CMD, Pid I PGID. Robimy to za pomocą -o Opcja, zapewnianie odcinanej przecinki listy pól jako argumentu:

$ ps -a -o pid, pGID, cmd 

Jeśli uruchomimy polecenie, gdy nasz skrypt uruchamia odpowiednią część danych wyjściowych, jest następująca:

 PID PGID CMD 298349 298349 /BIN /BASH ./test.SH 298350 298349 Sleep 30 

Możemy wyraźnie zobaczyć dwa procesy: pid pierwszego jest 298349, Tak samo jak to PGID: To jest lider grupy procesów. Został stworzony, kiedy uruchomiliśmy skrypt, jak widać w CMD kolumna.

Ten główny proces uruchomił proces dziecka z poleceniem Sleep 30: Zgodnie z oczekiwaniami dwa procesy są w tej samej grupie procesów.

Kiedy naciskaliśmy CTRL-C, skupiając się na terminalu, z którego uruchomiono skrypt, sygnał nie został wysłany tylko do procesu nadrzędnego, ale do całej grupy procesów. Która grupa procesowa? Grupa procesów na pierwszym planie terminala. Wszystkie procesy członek tej grupy są nazywane Procesy na pierwszym planie, Wszystkie pozostałe są nazywane Procesy w tle. Oto, co ma do powiedzenia podręcznik Bash w tej sprawie:

CZY WIEDZIAŁEŚ?Aby ułatwić wdrożenie interfejsu użytkownika do kontroli zadań, system operacyjny utrzymuje pojęcie bieżącego identyfikatora grupy procesów terminali. Członkowie tej grupy procesowej (procesy, których identyfikator grupy procesu jest równy bieżącemu identyfikatorowi grupy procesowej), odbierają sygnały generowane klawiaturą, takie jak Sigint. Mówi się, że te procesy są na pierwszym planie. Procesy tła to te, których identyfikator grupy procesu różni się od terminala; Takie procesy są odporne na sygnały generowane przez klawiaturę.

Kiedy wysłaliśmy Sigint sygnał z zabić Zamiast tego polecenie celowaliśmy tylko w PID procesu nadrzędnego; Bash wykazuje określone zachowanie, gdy sygnał jest odbierany, gdy czeka na zakończenie programu: „Kod pułapki” dla tego sygnału nie jest wykonywana, dopóki proces ten. Dlatego komunikat „otrzymany sygnał” został wyświetlony dopiero po spać Komenda wyszła.

Aby powtórzyć to, co dzieje się, gdy naciśniemy CTRL-C w terminalu za pomocą zabić polecenie, aby wysłać sygnał, musimy kierować się do grupy procesowej. Możemy wysłać sygnał do grupy procesowej za pomocą negacja PID lidera procesu, Załóżmy więc pid lidera procesu jest 298349 (Jak w poprzednim przykładzie), uruchomimy:

$ Kill -2 -298349 

Zarządzaj propagacją sygnału od wnętrza skryptu

Załóżmy teraz, że uruchamiamy długotrwały skrypt z nie interaktywnej powłoki i chcemy, aby wspomniany skrypt zarządzał propagacją sygnału automatycznie, aby po otrzymaniu sygnału takiego jak sygnał Sigint Lub Sigterm kończy swoje potencjalnie długotrwałe dziecko, ostatecznie wykonując zadania oczyszczające przed wyjściem. Jak możemy to zrobić?

Podobnie jak wcześniej, możemy poradzić sobie z sytuacją, w której sygnał jest odbierany w pułapce; Jednak, jak widzieliśmy, jeśli sygnał zostanie odebrany, podczas gdy powłoka czekała na zakończenie programu, „kod pułapki” jest wykonywany dopiero po wyjściu z procesu dziecka.

Nie tego chcemy: chcemy, aby kod pułapki został przetworzony, gdy tylko proces nadrzędny odbiera sygnał. Aby osiągnąć nasz cel, musimy wykonać proces dziecka w tło: Możemy to zrobić, umieszczając I symbol po poleceniu. W naszym przypadku pisalibyśmy:

#!Otrzymany sygnał echa/bin/bash!„Sigint echo„ Skrypt pid to $ ”snu 30 & 
Kopiuj

Gdybyśmy zostawili skrypt w ten sposób, proces nadrzędny wychodziłby zaraz po wykonaniu Sleep 30 polecenie, pozostawiając nas bez szansy na wykonanie zadań oczyszczających po zakończeniu lub przerwaniu. Możemy rozwiązać ten problem, używając powłoki Czekać Wbudowany. Strona pomocy z Czekać definiuje to w ten sposób:



Czeka na każdy proces zidentyfikowany przez identyfikator, który może być identyfikatorem procesu lub specyfikacją zadania, i zgłasza jego status rozwiązania. Jeśli identyfikator nie zostanie podany, czeka na wszystkie obecnie aktywne procesy dzieci, a status powrotu wynosi zero.

Po ustawieniu procesu do wykonania w tle możemy go odzyskać pid w $! zmienny. Możemy to przekazać jako argument Czekać Aby proces dla rodziców poczekaj na jego dziecko:

#!Otrzymany sygnał echa/bin/bash!„Sigint echo„ Script PID to $ ”snu 30 i czekaj $! 
Kopiuj

Skończyliśmy? Nie, nadal występuje problem: odbiór sygnału obsługiwanego w pułapce wewnątrz skryptu powoduje Czekać Zbudowany do natychmiastowego powrotu, nie czekając na zakończenie polecenia w tle. To zachowanie jest udokumentowane w instrukcji BASH:

Kiedy Bash czeka na asynchroniczne polecenie za pośrednictwem wbudowanego oczekiwania, odbiór sygnału, dla którego ustawiono pułapkę, spowoduje natychmiastowe powrót oczekiwania ze statusem wyjścia większym niż 128, po którym wykonano pułapkę. Jest to dobre, ponieważ sygnał jest obsługiwany od razu, a pułapka jest wykonywana bez konieczności czekania na zakończenie dziecka, ale wywołuje problem, ponieważ w naszej pułapce chcemy wykonać nasze zadania oczyszczające tylko wtedy, gdy będziemy pewni, że dziecko proces wyszedł.

Aby rozwiązać ten problem, musimy użyć Czekać znowu, być może jako część samej pułapki. Oto, jak w końcu może wyglądać nasz skrypt:

#!/bin/bash CleanUp () echo "czyszczenie…" # nasz kod czyszczenia trafi tutaj Otrzymano sygnał echo pułapki!; Kill "$ child_pid"; czekać „$ child_pid”; Oczyszczanie Sigint Sigterm Echo „Skrypt PID to $„ Sleep 30 & Child_pid = ”$!„Poczekaj” $ child_pid ” 
Kopiuj

W skrypcie stworzyliśmy posprzątać funkcja, w której moglibyśmy wstawić nasz kod czyszczenia i stworzyć nasz pułapka Złap także Sigterm sygnał. Oto, co się dzieje, gdy uruchamiamy ten skrypt i wysyłamy do niego jeden z tych dwóch sygnałów:

  1. Skrypt jest uruchomiony i Sleep 30 Polecenie jest wykonywane w tle;
  2. pid procesu dziecięcego jest „przechowywane” w Child_pid zmienny;
  3. Skrypt czeka zakończenie procesu dziecka;
  4. Skrypt otrzymuje Sigint Lub Sigterm sygnał
  5. Czekać Polecenie powraca natychmiast, bez oczekiwania na rozwiązanie dziecka;

W tym momencie pułapka jest wykonywana. W tym:

  1. A Sigterm sygnał ( zabić domyślnie) jest wysyłany do Child_pid;
  2. My Czekać Aby upewnić się, że dziecko zostało zakończone po otrzymaniu tego sygnału.
  3. Po Czekać zwroty, wykonujemy posprzątać funkcjonować.

Propaguj sygnał do wielu dzieci

W powyższym przykładzie pracowaliśmy ze skryptem, który miał tylko jeden proces dziecka. Co jeśli scenariusz ma wiele dzieci i co, jeśli niektórzy z nich mają własne dzieci?

W pierwszym przypadku jeden szybki sposób na zdobycie pids spośród wszystkich dzieci jest używanie Jobs -p Polecenie: To polecenie wyświetla PID wszystkich aktywnych zadań w bieżącej powładzie. Możemy niż użyć zabić zakończyć je. Oto przykład:

#!/bin/bash CleanUp () echo "czyszczenie…" # nasz kod czyszczenia trafi tutaj Otrzymano sygnał echo pułapki!; Zabij $ (Jobs -p); Czekać; Oczyszczanie Sigint Sigterm Echo „Skrypt PID to $” Sleep 30 & Sleep 40 i czekaj 
Kopiuj

Skrypt uruchamia dwa procesy w tle: przy użyciu Czekać Wbudowane bez argumentów, czekamy na wszystkie z nich i utrzymujemy proces rodzica przy życiu. Kiedy Sigint Lub Sigterm Sygnały są odbierane przez skrypt, wysyłamy Sigterm dla obu z nich, powrót przez PIDS Jobs -p Komenda (stanowisko sam jest wbudowanym skorupą, więc kiedy go używamy, nie powstaje nowy proces).

Jeśli dzieci mają własny proces dzieci i chcemy je wszystkie zakończyć, gdy przodek otrzyma sygnał, możemy wysłać sygnał do całej grupy procesowej, jak widzieliśmy wcześniej.

To jednak stanowi problem, ponieważ wysyłając sygnał zakończenia do grupy procesowej, wprowadzilibyśmy pętlę „Sygnał-sent/uwięziony sygnał”. Pomyśl o tym: w pułapka Do Sigterm Wysyłamy Sigterm sygnał dla wszystkich członków grupy procesowej; Obejmuje to sam skrypt nadrzędny!

Aby rozwiązać ten problem i nadal być w stanie wykonać funkcję oczyszczania po zakończeniu procesów dziecięcych, musimy zmienić pułapka Do Sigterm Na przykład tuż przed wysłaniem sygnału do grupy procesowej:

#!/bin/bash czyszczenie () echo "czyszczenie…" # nasz kod czyszczenia trafi tutaj pułapka „pułapka” „sigterm; Zabij 0; Czekać; Oczyszczanie Sigint Sigterm Echo „Skrypt PID to $” Sleep 30 & Sleep 40 i czekaj 
Kopiuj

W pułapce przed wysłaniem Sigterm Do grupy procesowej zmieniliśmy Sigterm pułapka, tak że proces nadrzędny ignoruje sygnał i wpływa tylko na jego potomkowie. Zauważ również, że w pułapce, aby zasygnalizować grupę procesową, użyliśmy zabić z 0 jako pid. To jest rodzaj skrótu: kiedy pid Przeszedł do zabić Jest 0, wszystkie procesy w aktualny Grupa procesowa jest sygnalizowana.

Wnioski

W tym samouczku dowiedzieliśmy się o grupach procesowych i jakiej różnicy między procesami na pierwszym planie i. Dowiedzieliśmy się, że Ctrl-C wysyła Sigint sygnał do całej grupy procesowej na pierwszym planie terminalu kontrolującego i nauczyliśmy się wysłać sygnał do grupy procesowej za pomocą zabić. Nauczyliśmy się również, jak wykonywać program w tle i jak korzystać z Czekać Shell wbudowany, aby czekać, aż wyjdzie bez utraty powłoki nadrzędnej. Wreszcie widzieliśmy, jak skonfigurować skrypt, aby po otrzymaniu sygnału zakończyło swoje dzieci przed wyjściem. Przegapiłem coś? Czy masz swoje osobiste przepisy do wykonania zadania? Nie wahaj się daj mi znać!

Powiązane samouczki Linux:

  • Bash Tła zarządzanie procesami
  • Wielokrotyściowe skrypty i zarządzanie procesami w…
  • Wprowadzenie do automatyzacji, narzędzi i technik Linuksa
  • Jak uruchomić polecenie w tle na Linux
  • Mastering Bash Script Loops
  • Jak zainstalować sygnał na Linux
  • Jak śledzić połączenia systemowe wykonane przez proces z Strace na…
  • Mint 20: Lepsze niż Ubuntu i Microsoft Windows?
  • Porównanie Linux Apache Prefork vs Pracowni
  • Jak pracować z grupami pakietów DNF