Signál (angl. signal) je způsob, jak poslat „upozornění“ běžícímu procesu. I když ten proces zrovna dělá něco jiného, může na signál zareagovat. Vlastně, jak si ukážeme, na signál zareagovat musí.
Signály jsou docela minimalistické: obsahují jen jedno malé číslo,
druh signálu.
Větší „zprávy“ se musí procesům posílat jiným způsobem (třeba souborem,
rourou nebo přes síť), ale i v těchto případech se dá signálem upozornit
že si proces má nějakou větší zprávu přečíst.
Typickým příkladem je ukončení probíhajícího procesu pomocí Ctrl+C, kdy
pošleme procesu signál žádající o jeho ukončení.
Když procesu pošleš signál, tak ten proces přeruší to co zrovna dělá a signál zpracuje. Co přesně se stane závisí na druhu signálu; možnosti jsou tyto:
Když se proces na signály předem nepřipraví, provede se nějaká výchozí akce. Ta závisí na druhu signálu, ale nejčastější jsou první dvě: proces se ukončí.
Každý druh signálu má své číslo a zkratkovité jméno.
Jejich seznam najdeš v manuálové stránce signal(7), pomocí příkazu:
$ man 7 signal
Pozor, man signal zobrazí stránku signal(2) – nápovědu pro funkci
signal, nikoli signály obecně.
To číslo je číslo „sekce“ manuálu; seznam sekcí najdeš v man man.
Každý signál má i své číslo, které se ale může na různých systémech
a procesorech lišit.
Lepší je proto používat jména – snad kromě čísla 9 pro SIGKILL.
Příkaz na posílání signálů už znáš: je to kill.
Přepínačem pojmenovaným podle signálu můžeš vybrat, který signál pošleš.
Zkus si to!
V jedom terminálu zadej:
$ sleep 1000
A ve druhém třeba:
$ ls -a | grep sleep
12345678 pts/3 00:00:00 sleep
$ kill -SIGABRT 12345678
Signál SIGABRT proces dostane když se snaží provést neplatnou operaci –
instrukci, které procesor nerozumí.
To je většinou hodně závažná chyba.
Kdybys při instalaci systému zadala „automatické hlášení chyb“,
už by k vývojářům Fedory putovalo hlášení o tom, že v /usr/bin/sleep
z balíčku coreutils je chyba.
Pravděpodobně takhle ale virtuálku nastavenou nemáš, ale můžeš se aspoň
pomocí abrt-cli list podívat, že chyba byla zaznamenána.
Podobně si můžeš vyzkoušet jiné signály, třeba:
SIGINT se posílá když zmáčkneš Ctrl+C.SIGTERM posílá kill když mu nezadáš jiný signál.SIGKILL (9) se posílá když zmáčkneš Ctrl+\;
proces se „natvrdo“ ukončí.
U tohoto signálu si procesy nemůžou nastavit jiné chování:
proces se vždy ukončí.SIGSTOP se posílá když zmáčkneš Ctrl+z;
proces se pozastaví.
Podobně jako u SIGKILL si i u tohoto signálu procesy nemůžou nastavit
jiné chování.SIGCONT posílá příkaz fg; pozastavený proces se obnoví.To, že se přijetí signálu přeruší cokoli co proces zrovna dělá,
se dá přirovnat k výjimkám v Pythonu.
Koneckonců, výjimka KeyboardInterrupt je jen reakce Pythonu na signál
SIGINT: vzniká tak, že so Python na SIGINT připravil: řekl operačnímu
systému že pro SIGINT nemá ukončit proces,
ale má se zavolat funkce která způsobí výjimku.
Program v Pythonu můžeš „připravit“ na výjimky pomocí try a except.
Na to, aby se program připravil na přijetí signálu, slouží funkce
signal z modulu signal:
import signal
import os
import time
print('Moje PID:', os.getpid())
def handler(sig_number, frame):
print(f'Dostal jsem signál {sig_number}: {signal.strsignal(sig_number)}')
signal.signal(signal.SIGUSR1, handler)
signal.signal(signal.SIGUSR2, handler)
signal.signal(signal.SIGINT, handler)
while True:
time.sleep(10)
Tento program reaguje na SIGUSR1 a SIGUSR2, tedy např. kill -SIGUSR1 $pid.
Můžeš si ověřit, že pro SIGKILL a SIGSTOP funkce signal.signal selže.
A taky že předefinování SIGINT přenastaví chování
Ctrl+C – právě pro takové situace se hodí SIGKILL.