Signály v Linuxu
28.11.2022
Jedním ze způsobů komunikace mezi procesy jsou v Linuxu signály. I když primárně jsou posílány z jiné aplikace nebo z jádra, můžeme je také využít interně v rámci jedné aplikace. Signál má jeden parametr a tím je jeho číslo.
Signály můžeme v aplikaci zachycovat a nějak na ně reagovat nebo je ignorovat. Některé speciální "předdefinované" signály však aplikace ingorovat nemůže. Je to především signál s číslem 9 SIGKILL který je určený k vynucenému "tvrdému ukončení" procesu. Druhým takovým signálem je SIGSTOP s číslem 19, sloužící k zastavení běhu procesu s možností jeho pozdějšího obnovení signálem SIGCONT (číslo 18).
Pro nás budou dále zajímavější signály, které můžeme v aplikaci zachytit a ignorovat jejich výchozí efekt. Takovým je například signál SIGINT s číslem 2, který občas posíláme procesu běžícímu v terminálu stisknutím kombinace CTRL+c. Tento signál za normálních okolností přeruší proces. Podobně můžeme zachytit a ignorovat signál SIGTERM (číslo 15). Obojí si dále ukážeme.
Dále existuje několik signálů, které pokud nejsou zachyceny, nemají na běh procesu žádný efekt. Jednak jsou to signály SIGUSR1 (číslo 10) a SIGUSR2 (číslo 12) a dále tzv. "real-time signály" v rozsahu hodnot od SIGRTMIN (34) DO SIGRTMAX (64). Tyto signály můžeme libovolně využívat pro vlastní komunikaci mezi procesy nebo třeba vlákny v rámci naší aplikace.
Přehled všech signálů si můžeme vypsat do terminálu příkazem kill -l. Dostaneme následující výpis:
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
Jako uživatel můžeme požadovanému procesu poslat signál z terminálu příkazem pkill napřklad takto:
$ pkill -SIGRTMIN+1 nazev_procesu $ pkill -SIGTERM nazev_procesu
Nebo pokud běží více instancí nějakého procesu, můžeme jedním příkazem poslat požadovaný signál všem běžícím instancím příkazem kilall
$ killall -SIGRTMIN+1 nazev_procesu
Jako příklad konkrétního využití se můžeme podívat na následující řádky ve výchozím konfiguračním souboru správce oken i3.
set $refresh_i3status killall -SIGUSR1 i3status bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ +10% && $refresh_i3status bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -10% && $refresh_i3status bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle && $refresh_i3status bindsym XF86AudioMicMute exec --no-startup-id pactl set-source-mute @DEFAULT_SOURCE@ toggle && $refresh_i3status
Nejprve je (pro opakované použití) nadefinován vlastní příkaz refresh_i3status, který pošle signál -SIGUSR1 procesu i3status, což je výchozí stavový panel v i3, který na tento signál reaguje aktualizací svého výstupu, tj. např. zobrazením aktuální nastavené hodnoty hlasitosti. Tento příkaz je pak použit při stisku jedné ze speciálních kláves ovládajících hlasitost které bývají na multimediálních klávesnicích a notebooku.
Práce se signály v programu v jazyce C
Jak tedy v aplikaci napsané v jazyce C zachycovat signály? Nejprve si musíme napsat vlastní funkci, kterou nastavíme jako handler příchozího signálu, jednoduše řečeno tato funkce bude vyvolána při zachycení jednoho nebo více signálů, kterým ji nastavíme. Můžeme tedy mít společnou funkci pro více různých signálů. O který signál se v tom kterém případě jedná rozlišíme jejím parametrem. Tím že takto zachytíme signál způsobíme současně ignorování jeho výchozího účinku, samozřejmě s výjimkou výše zmíněných signálů které nelze ignorovat a dokonce ani detkovat. Tato funkce má následující prototyp:
static void signal_handler(int sig) { }
Dále si napíšeme vlastní pomocnou funkci, jejímž parametrem bude číslo signálu, který chceme zachytit výše uvedenou funkcí:
static void set_signal_handler(int sig_cislo) { struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sa.sa_handler = signal_handler; sigaction(sig_cislo, &sa, NULL); }
Nyní si již můžeme napsat jednoduchý ukázkový program, který bude zachycovat a ignorovat výše uvedené signály SIGTERM a SIGINT a vyzkoušet efekt.
/* gcc signaly.c -Wall -O2 -otest-signaly */ #include <stdlib.h> #include <stdio.h> #include <string.h> #include <signal.h> #include <unistd.h> static void signal_handler(int sig_cislo) { if (SIGINT == sig_cislo) { printf("\nsignal SIGINT handled: %d\n", sig_cislo); } else if (SIGTERM == sig_cislo) { printf("\nsignal SIGTERM handled: %d\n", sig_cislo); } } static void set_signal_handler(int sig_cislo) { struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sa.sa_handler = signal_handler; sigaction(sig_cislo, &sa, NULL); } int main(int argc, char** argv) { set_signal_handler(SIGINT); set_signal_handler(SIGTERM); puts("q pro ukonceni"); char ch; while ('q' != (ch = getchar())) {} return EXIT_SUCCESS; }
Zdrojový kód přeložíme pomocí gcc příkazem (uvedeným v komentáři kódu):
$ gcc signaly.c -Wall -O2 -otest-signaly
Když nyní program spustíme, můžeme hned vyzkoušet efekt ignorace signálu SIGINT stisknutím kombinace CTRL+c v terminálu za běhu programu. Výsledkem bude následující výstup:
q pro ukonceni ^C signal SIGINT handled: 2 q
Dále můžeme zkusit poslat naší spuštěné aplikaci signály z jiného terminálu takto:
$ pkill -SIGINT test-signaly $ pkill -SIGTERM test-signaly
výsledkem bude
q pro ukonceni signal SIGINT handled: 2 signal SIGTERM handled: 15 q
Posílání signálů z programu v jazyce C
Signály samozřejmě můžeme posílat z vlastní aplikace pomocí funkce int kill(pid_t pid, int sig);, která má dva parametry: identifikátor procesu a číslo signálu. Pokud chceme poslat signál v rámci procesu, zjistíme jeho identifikátor pomocí funkce pid_t getpid(void);
Rozšíříme si tedy funkci main která bude nyní vypadat takto:
int main(int argc, char** argv) { pid_t mypid = getpid(); set_signal_handler(SIGINT); set_signal_handler(SIGTERM); puts("q pro ukonceni"); char ch; while ('q' != (ch = getchar())) { if ('i' == ch) { kill(mypid, SIGINT); } else if ('t' == ch) { kill(mypid, SIGTERM); } } return EXIT_SUCCESS; }
A po sestavení a spuštění vyzkoušíme a dostaneme následující výstup:
q pro ukonceni i signal SIGINT handled: 2 t signal SIGTERM handled: 15 i signal SIGINT handled: 2 q
Nové články
Souborové operace na Linuxu v jazyce C
Využívejte Midnight Commander naplno
Procesor a jádra, běh na určeném jádru v C.
picom - kompozitor pro odlehčená Linuxová prostředí
Kontakt
739 219 991
live:radekchalupa_1