
De Linux-kernel stuurt signalen naar processen over gebeurtenissen waarop ze moeten reageren. Goed opgevoede scripts verwerken signalen elegant en robuust en kunnen achter zichzelf opruimen, zelfs als je op Ctrl+C drukt. Hier is hoe.
Signalen en processen
Signalen zijn korte, snelle eenrichtingsberichten die worden verzonden naar processen zoals scripts, programma’s en daemons. Ze laten het proces weten over iets dat is gebeurd. De gebruiker heeft mogelijk op Ctrl+C gedrukt of de toepassing heeft geprobeerd naar het geheugen te schrijven waartoe het geen toegang heeft.
Als de auteur van het proces heeft geanticipeerd dat er een bepaald signaal naartoe zou kunnen worden gestuurd, kunnen ze een routine in het programma of script schrijven om dat signaal af te handelen. Zo’n routine heet a signaal handler. Het vangt of vangt het signaal op en voert een actie uit als reactie daarop.
Linux gebruikt veel signalen, zoals we zullen zien, maar vanuit het oogpunt van scripting is er slechts een kleine subset van signalen waarin je waarschijnlijk geïnteresseerd bent. Vooral in niet-triviale scripts, signalen die de script dat moet worden afgesloten, moet worden opgesloten (waar mogelijk) en een gracieus afsluiten moet worden uitgevoerd.
Scripts die tijdelijke bestanden maken of firewallpoorten openen, kunnen bijvoorbeeld de kans krijgen om de tijdelijke bestanden te verwijderen of de poorten te sluiten voordat ze worden afgesloten. Als het script gewoon sterft op het moment dat het het signaal ontvangt, kan uw computer in een onvoorspelbare staat worden achtergelaten.
Hier leest u hoe u signalen in uw eigen scripts kunt verwerken.
Maak kennis met de signalen
Sommige Linux-commando’s hebben cryptische namen. Niet zo het commando dat signalen opvangt. Het heet trap
. We kunnen ook gebruik maken van trap
met de -l
(lijst) optie om ons de volledige lijst met signalen te tonen die Linux gebruikt.
trap -l
Hoewel onze genummerde lijst eindigt op 64, zijn er eigenlijk 62 signalen. Seinen 32 en 33 ontbreken. Ze zijn niet geïmplementeerd in Linux. Ze zijn vervangen door functionaliteit in de gcc
compiler voor het afhandelen van realtime threads. Alles van sein 34, SIGRTMIN
om 64 te signaleren, SIGRTMAX
zijn realtime signalen.
U ziet verschillende lijsten op verschillende Unix-achtige besturingssystemen. Op OpenIndiana zijn bijvoorbeeld de signalen 32 en 33 aanwezig, samen met een heleboel extra signalen die het totaal op 73 brengen.
Er kan naar signalen worden verwezen met naam, nummer of verkorte naam. Hun verkorte naam is gewoon hun naam met de leidende “SIG” verwijderd.
Signalen worden om veel verschillende redenen opgewekt. Als je ze kunt ontcijferen, staat hun doel in hun naam. De impact van een signaal valt in een van de volgende categorieën:
- Beëindigen: Het proces wordt beëindigd.
- Negeren: Het signaal heeft geen invloed op het proces. Dit is een informatief signaal.
- Kern: Er wordt een dump-core-bestand gemaakt. Dit wordt meestal gedaan omdat het proces op de een of andere manier is overtreden, zoals een geheugenschending.
- Hou op: Het proces wordt gestopt. Dat wil zeggen, het is gepauzeerdniet beëindigd.
- Doorgaan: Vertelt een gestopt proces om door te gaan met de uitvoering.
Dit zijn de signalen die u het vaakst zult tegenkomen.
- SIGHUP: Signaal 1. De verbinding met een externe host, zoals een SSH-server, is onverwachts verbroken of de gebruiker is uitgelogd. Een script dat dit signaal ontvangt, kan netjes worden beëindigd of ervoor kiezen om opnieuw verbinding te maken met de externe host.
-
SIGINT: Signaal 2. De gebruiker heeft de Ctrl+C-combinatie ingedrukt om een proces te forceren om te sluiten, of de
kill
commando is gebruikt met signaal 2. Technisch gezien is dit een onderbrekingssignaal, geen beëindigingssignaal, maar een onderbroken script zonder signaalhandler zal meestal eindigen. -
SIGQUIT: Signaal 3. De gebruiker heeft op de combinatie Ctrl+D gedrukt om een proces te forceren te stoppen, of de
kill
commando is gebruikt met signaal 3. - SIGFPE: Signaal 8. Het proces probeerde een illegale (onmogelijke) wiskundige bewerking uit te voeren, zoals delen door nul.
- SIGKILL: Signaal 9. Dit is het signaalequivalent van een guillotine. Je kunt het niet vangen of negeren, en het gebeurt onmiddellijk. Het proces wordt onmiddellijk beëindigd.
-
SIGTERM: Signaal 15. Dit is de meer attente versie van
SIGKILL
.SIGTERM
vertelt een proces ook om te beëindigen, maar het kan vastlopen en het proces kan zijn opruimprocessen uitvoeren voordat het wordt afgesloten. Dit maakt een gracieus afsluiten mogelijk. Dit is het standaardsignaal dat wordt opgewekt door dekill
opdracht.
Signalen op de opdrachtregel
Een manier om een signaal op te vangen is door gebruik te maken van trap
met het nummer of de naam van het signaal en een reactie die u wilt laten gebeuren als het signaal wordt ontvangen. We kunnen dit demonstreren in een terminalvenster.
Dit commando vangt de SIGINT
signaal. Het antwoord is om een regel tekst af te drukken naar het terminalvenster. We gebruiken de -e
(enable escapes) optie met echo
zodat we de “n
” formaatspecificatie.
trap 'echo -e "nCtrl+c Detected."' SIGINT
Elke keer dat we op de Ctrl+C-combinatie drukken, wordt onze tekstregel afgedrukt.
Om te zien of er een val op een signaal is gezet, gebruikt u de -p
(afdrukval) optie.
trap -p SIGINT
Gebruik makend van trap
zonder opties doet hetzelfde.
Gebruik een koppelteken om het signaal terug te zetten naar zijn niet-gevangen, normale toestand-
” en de naam van het ingesloten signaal.
trap - SIGINT
trap -p SIGINT
Geen uitvoer van de trap -p
commando geeft aan dat er geen trap is ingesteld op dat signaal.
Signalen in scripts vangen
We kunnen hetzelfde algemene formaat gebruiken trap
commando in een script. Dit script vangt drie verschillende signalen op, SIGINT
, SIGQUIT
en SIGTERM
.
#!/bin/bash trap "echo I was SIGINT terminated; exit" SIGINT trap "echo I was SIGQUIT terminated; exit" SIGQUIT trap "echo I was SIGTERM terminated; exit" SIGTERM echo $$ counter=0 while true do echo "Loop number:" $((++counter)) sleep 1 done
De boom trap
uitspraken staan bovenaan het script. Merk op dat we de exit
commando binnen het antwoord op elk van de signalen. Dit betekent dat het script op het signaal reageert en vervolgens afsluit.
Kopieer de tekst naar je editor en sla het op in een bestand met de naam “simple-loop.sh”, en maak het uitvoerbaar met behulp van de chmod
opdracht. U moet dat met alle scripts in dit artikel doen als u het op uw eigen computer wilt volgen. Gebruik in elk geval gewoon de naam van het juiste script.
chmod +x simple-loop.sh
De rest van het script is heel eenvoudig. We moeten de proces-ID van het script weten, dus we hebben het script voor ons. De $$
variabele bevat de proces-ID van het script.
We maken een variabele genaamd counter
en zet deze op nul.
De while
loop zal voor altijd worden uitgevoerd, tenzij het met geweld wordt gestopt. Het verhoogt de counter
variabele, echoot het naar het scherm en slaapt even.
Laten we het script uitvoeren en er verschillende signalen naar sturen.
./simple-loop.sh
Wanneer we op “Ctrl + C” drukken, wordt ons bericht afgedrukt naar het terminalvenster en wordt het script beëindigd.
Laten we het opnieuw uitvoeren en de . verzenden SIGQUIT
signaal met behulp van de kill
opdracht. We moeten dat doen vanuit een ander terminalvenster. U moet de proces-ID gebruiken die door uw eigen script is gerapporteerd.
./simple-loop.sh
kill -SIGQUIT 4575
Zoals verwacht meldt het script dat het signaal arriveert en eindigt. En tot slot, om het punt te bewijzen, doen we het nog een keer met de SIGTERM
signaal.
./simple-loop.sh
kill -SIGTERM 4584
We hebben geverifieerd dat we meerdere signalen in een script kunnen vangen en op elk afzonderlijk kunnen reageren. De stap die dit alles van interessant naar nuttig bevordert, is het toevoegen van signaalbehandelaars.
Omgaan met signalen in scripts
We kunnen de antwoordreeks vervangen door de naam van een functie in uw script. De trap
commando roept vervolgens die functie aan wanneer het signaal wordt gedetecteerd.
Kopieer deze tekst naar een editor en sla het op als een bestand met de naam “grace.sh”, en maak het uitvoerbaar met chmod
.
#!/bin/bash trap graceful_shutdown SIGINT SIGQUIT SIGTERM graceful_shutdown() { echo -e "nRemoving temporary file:" $temp_file rm -rf "$temp_file" exit } temp_file=$(mktemp -p /tmp tmp.XXXXXXXXXX) echo "Created temp file:" $temp_file counter=0 while true do echo "Loop number:" $((++counter)) sleep 1 done
Het script zet een val voor drie verschillende signalen: SIGHUP
, SIGINT
en SIGTERM
—met behulp van een enkele trap
uitspraak. Het antwoord is de naam van de graceful_shutdown()
functie. De functie wordt aangeroepen wanneer een van de drie ingesloten signalen wordt ontvangen.
Het script maakt een tijdelijk bestand aan in de map “/tmp”, met behulp van mktemp
. De bestandsnaamsjabloon is “tmp.XXXXXXXXXX”, dus de naam van het bestand zal “tmp” zijn. gevolgd door tien willekeurige alfanumerieke tekens. De naam van het bestand wordt op het scherm weergegeven.
De rest van het script is hetzelfde als het vorige, met a counter
variabel en een oneindig while
lus.
./grace.sh
Wanneer het bestand een signaal krijgt waardoor het wordt gesloten, wordt de graceful_shutdown()
functie wordt aangeroepen. Hiermee wordt ons enkele tijdelijke bestand verwijderd. In een echte situatie kan het elke opschoning uitvoeren die uw script vereist.
Ook hebben we al onze gevangen signalen gebundeld en met één enkele functie behandeld. U kunt signalen afzonderlijk opvangen en naar hun eigen speciale handlerfuncties sturen.
Kopieer deze tekst en sla het op in een bestand met de naam “triple.sh”, en maak het uitvoerbaar met de chmod
opdracht.
#!/bin/bash trap sigint_handler SIGINT trap sigusr1_handler SIGUSR1 trap exit_handler EXIT function sigint_handler() { ((++sigint_count)) echo -e "nSIGINT received $sigint_count time(s)." if [[ "$sigint_count" -eq 3 ]]; then echo "Starting close-down." loop_flag=1 fi } function sigusr1_handler() { echo "SIGUSR1 sent and received $((++sigusr1_count)) time(s)." } function exit_handler() { echo "Exit handler: Script is closing down..." } echo $$ sigusr1_count=0 sigint_count=0 loop_flag=0 while [[ $loop_flag -eq 0 ]]; do kill -SIGUSR1 $$ sleep 1 done
We definiëren drie vallen bovenaan het script.
- een vallen
SIGINT
en heeft een handler genaamdsigint_handler()
. - De tweede vangt een signaal genaamd
SIGUSR1
en gebruikt een handler genaamdsigusr1_handler()
. - Val nummer drie vangt de
EXIT
signaal. Dit signaal wordt door het script zelf afgegeven wanneer het wordt gesloten. Een signaalhandler instellen voor:EXIT
betekent dat je een functie kunt instellen die altijd wordt aangeroepen wanneer het script eindigt (tenzij het wordt gedood met een signaal)SIGKILL
). Onze handler heetexit_handler()
.
SIGUSR1
en SIGUSR2
zijn signalen die worden verstrekt zodat u aangepaste signalen naar uw scripts kunt sturen. Hoe u ze interpreteert en erop reageert, is geheel aan u.
Als we de signaalbehandelaars voor nu terzijde laten, zou de hoofdtekst van het script u bekend moeten zijn. Het echoot de proces-ID naar het terminalvenster en maakt enkele variabelen aan. Variabele sigusr1_count
registreert het aantal keren SIGUSR1
werd behandeld, en sigint_count
registreert het aantal keren SIGINT
werd behandeld. De loop_flag
variabele wordt op nul gezet.
De while
lus is geen oneindige lus. Het stopt met lussen als de loop_flag
variabele is ingesteld op een waarde die niet nul is. Elke draai van de while
lus gebruikt kill
om de te sturen SIGUSR1
signaal naar dit script, door het naar de proces-ID van het script te sturen. Scripts kunnen signalen naar zichzelf sturen!
De sigusr1_handler()
functie verhoogt de sigusr1_count
variabele en stuurt een bericht naar het terminalvenster.
Elke keer dat de SIGINT
signaal wordt ontvangen, de siguint_handler()
functie verhoogt de sigint_count
variabele en echoot de waarde ervan naar het terminalvenster.
Als de sigint_count
variabele is gelijk aan drie, de loop_flag
variabele is ingesteld op één en er wordt een bericht verzonden naar het terminalvenster om de gebruiker te laten weten dat het afsluitproces is gestart.
Omdat loop_flag
is niet langer gelijk aan nul, de while
lus wordt beëindigd en het script is voltooid. Maar die actie verhoogt automatisch de EXIT
signaal en de exit_handler()
functie wordt genoemd.
./triple.sh
Na drie keer drukken op Ctrl+C wordt het script beëindigd en wordt automatisch de exit_handler()
functie.
Lees de signalen
Door signalen op te vangen en ermee om te gaan in eenvoudige handlerfuncties, kunt u uw Bash-scripts achter zichzelf laten opruimen, zelfs als ze onverwacht worden beëindigd. Dat geeft je een schoner bestandssysteem. Het voorkomt ook instabiliteit de volgende keer dat u het script uitvoert, en – afhankelijk van het doel van uw script – kan het zelfs beveiligingslekken voorkomen.