Ciele
- Oboznámiť sa s pojmom signál
- Naučiť sa pracovať so signálmi
Postup
Krok 1: Signál
Príprava
Pre prácu na cvičení si stiahnite súbor k cvičeniu.
Obsah súboru:
.
└── 07
├──u1_1.c
├──u2_1.c
├──Makefile
└──tests
└──u1_1_test.sh
Poznámka
Programy je možné kompilovať pomocou make nazov_suboru.c
Napríklad:
make u1_1.c
Úvod
Signál je komunikačný prostriedok. Je generovaný UNIXom/Linuxom ako odpoveď na výskyt konkrétnej udalosti. Keď je signál prijatý, tak naň môže proces reagovať.
Signály sú generované: operačným systémom, používateľským procesom.
Môžu byť: generované, odchytávané, * ignorované.
Názvy signálov sú definované v hlavičkovom súbore signal.h
.
Štandardné signály v UNIX/Linuxe:
Názov | č. | Akcia | Popis |
---|---|---|---|
SIGINT | 2 | Term | prerušenie terminálu (ekvivalent k Ctrl + C z klávesnice) |
SIGQUIT | 3 | Core | ukončenie z klávesnice |
SIGKILL | 9 | Term | ukončenie procesu |
SIGALRM | 14 | Term | signál časovača pre službu alarm() |
SIGTERM | 15 | Term | terminačný signál |
SIGCHLD | 17 | Ign | proces potomka bol ukončený alebo prerušený |
SIGSTOP | 19 | Stop | zastaví proces |
SIGWINCH | 28 | Ign | signál zmeny veľkosti okna |
Úloha 1.1
Signálov je omnoho viac. To aké ešte existujú si naštudujte v manuáli: man 7 signal
Krok 2: Práca so signálmi
Príkaz shellu kill
Pomocou príkazu kill je signál okamžite zaslaný buď jednému procesu alebo skupine procesov. Ako parameter prijíma voliteľné číslo signálu a PID procesu. Ak nie je zadané žiadne číslo signálu, tak sa odošle signál SIGTERM.
kill [-signal|-s signal|-p] [-q value] [-a] [--] pid|name...
Poznámka
Podrobnejšie informácie k príkazu kill sa nachádzajú v manuáli: man kill
Príklady použitia kill:
Odoslanie signálu SIGTERM procesu s PID = 15
kill 15
Odoslanie signálu SIGTERM procesom patriacím do skupiny s PGID = 15
kill -15
Odoslanie signálu SIGTERM všetkým procesom okrem procesu s PID = 1 Proces s PID = 1 je init proces. Jedná sa o proces, ktorý riadi spúšťanie iných procesov.
kill -1
Odoslanie signálu SIGKILL viacerým procesom (32, 55, 78)
kill -9 32, 55, 78
Služba jadra signal()
Slúži na odchytenie príchodu špecifického signálu.
#include <signal.h>
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
Parametere | |
---|---|
int signum | typ signálu (napr. SIGINT) |
sighandler_thandler | obslužná funkcia - reakcia, čo sa stane, keď bude procesu poslaný signál |
Návratová hodnota |
---|
Pri úspešnom vykonaní vráti prechadzajúcu hodnotu signálu. |
Pri chybe SIG_ERR. |
Príklad použitia:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void obsluzna_funkcia(int sig){
printf("\nSignal %d bol prave odchyteny.\n", sig);
printf("Nastavujem povodnu reakciu na signal SIGINT.\n");
signal(SIGINT, SIG_DFL);
}
int main(){
printf("Program bude v sekundovych intervaloch vypisovat retazec 'Hello World!'.\n");
signal(SIGINT,obsluzna_funkcia);
while(1){
printf("Hello world!\n");
sleep(1);
}
return(0);
}
Opis kódu:
Program pozostáva z hlavnej funkcie main a obslúžnej funkcie s príslušným názvom. V hlavnej funkcií je zmenená reakcia na prijatie signálu SIGINT z klasickej na funkciu obsluzna_funkcia. Po zachytení signálu SIGINT, ktorý reprezentuje stlačenie kombinácie kláves CTRL + C sa táto obslúžna funkcia zavolá. V nej sa zmení reakcia na signál SIGINT na tú prednastavenú (SIG_DFL). Program je možné ukončiť opätovným stlačením kláves CTRL + C.
Upozornenie
V prípade, že by sa v obslúžnej funkcií nenastavila pôvodná reakcia na signál SIGINT, by nebolo možné ukončiť program prostredníctvom kombinácie kláves CTRL + C.
Výstup z programu:
Program bude v sekundovych intervaloch vypisovat retazec 'Hello World!'.
Hello world!
Hello world!
^C
Signal 2 bol prave odchyteny.
Nastavujem povodnu reakciu na signal SIGINT.
Hello world!
Hello world!
Hello world!
^C
Služba jadra kill()
Slúži na zaslanie signálu procesu alebo skupine procesov.
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
Parametere | |
---|---|
pid_t pid | ak je hodnota pid kladná, tak je signál odoslaný procesu s ID špecifikovaným pomocou pid |
ak je hodnota pid 0, tak je signál poslaný každému procesu v skupine procesov, v ktorej sa nachádza volajúci proces | |
ak je hodnota pid -1, tak je signál poslaný každému procesu, pre ktorý má volajúci proces právo posielať signály, okrem procesu 1 (init) | |
ak je hodnota pid menšia ako -1, tak je signál poslaný každému procesu v skupine procesov, ktorej ID je -pid | |
int sig | ak je hodnota sig 0, tak nie je poslaný žiaden signál ale overenie existencie a práv prebehne |
Návratová hodnota |
---|
Pri úspešnom vykonaní vráti 0. |
Pri chybe -1. |
Príklad použitia:
#define _POSIX_SOURCE //Musí byť definované na začiatku pred include kvôli kompilácií.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
//Obslužná funkcia
void obsluzna_funkcia(int sig){
printf("Signal %d bol odchyteny.\n",sig);
}
int main(){
printf("Spustenie hlavneho programu.\n");
pid_t pid = fork();
switch(pid){
case 0:
sleep(3);
kill(getppid(), SIGABRT);
exit(0);
case -1:
perror("Nastala chyba.");
exit(1);
default:
printf("Cakanie na prijatie signalu SIGABRT.\n");
signal(SIGABRT, obsluzna_funkcia);//Zaregistruje obslužnú funkciu
pause();//Tu čaká na signál
printf("Koniec hlavneho programu.\n");
exit(0);
}
}
Opis kódu:
Najprv je spustený hlavný program. Následne sa vytvorí proces potomka prostredníctvom služby fork(). Proces rodiča (vetva default) využíva službu signal() na zachytenie signálu SIGABRT na ktorý bude reagovať prostredníctvom obslúžnej funkcie. Následne prostredníctvom služby pause() pozastaví svoje vykonávanie až do príchodu nejakého signálu. Proces potomka pošle po 3 sekundách signál SIGABRT
procesu rodiča a ukončí sa. Následne sa ukončí aj proces rodiča.
Výstup programu:
Spustenie hlavneho programu.
Cakanie na prijatie signalu SIGABRT.
Signal 6 bol odchyteny.
Koniec hlavneho programu.
Úloha 2.1
Vytvorte program, ktorý v hlavnom programe (funkcia main()) odošle aktuálnemu procesu signál SIGQUIT. Na tento signál nastavte obslužnú funkciu obsluzna_funkcia(), ktorá dokáže tento signál odchytiť a nastaví mu pôvodnu reakciu.
Poznámka
Zdrojové súbory k úlohe sa nachádzajú v priečinku 07 pod názvom u1_1.c.
Pred testovaním je potrebné skompilovať program u1_1.c.
Úlohu je možné testovať zadaním následovného príkazu do terminálu:
./tests/u1-1-test.sh
Služba jadra alarm()
Zaistí, že volajúcemu procesu sa po počte sekúnd, určenom v parametre služby, odošle signál SIGALRM.
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
Parametere | |
---|---|
unsigned int seconds | počet sekúnd, po ktorom bude zaslaný signál SIGALRM |
Návratová hodnota |
---|
Počet sekúnd zostavajúcich do uplynutia predchádzajúceho alarmu (ak nejaký naplánovaný bol). |
0 ak pred tým nebol naplánovaný žiaden alarm(). |
Poznámka
Podrobnejšie informácie k službe alarm() sa nachádzajú v manuáli: man alarm
Príklad použitia:
#define _POSIX_SOURCE //Musí byť definované na začiatku pred include
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
//Obslužná funkcia
void obsluzna_funkcia(int sig){
printf("Signal %d bol odchyteny.\n",sig);
}
int main(){
signal(SIGALRM,obsluzna_funkcia);
printf("Spustenie programu.\n");
alarm(3);
pause();
printf("Koniec hlavneho programu.\n");
return(0);
}
Opis kódu:
Program najprv nastaví prostredníctvom služby signal() odchytenie signalu SIGALRM, ktorý pochádza zo služby alarm(). Následne zavolá službu alarm() pomocou ktorej odošle signál SIGALRM volajúcemu procesu po 3 sekundách.
Služba jadra sigsuspend()
Služba jadra sigsuspend() pozastaví činnosť programu dovtedy, pokiaľ nie je programu poslaný nejaký signál.
#include <signal.h>
int sigsuspend(const sigset_t *mask);
Parametere | |
---|---|
const sigset_t *mask | dočasná maska, ktorá nahrádza tú pôvodnu. Ak sa ponechá prázdna (v príklade nižšie) tak proces čaká na akýkoľvek signál |
Návratová hodnota |
---|
Stále -1. |
Poznámka
Podrobnejšie informácie k službe sigsuspend() sa nachádzajú v manuáli: man sigsuspend
Príklad použitia:
#define _POSIX_SOURCE //Musí byť definované na začiatku pred include
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
//Obslužná funkcia
void obsluzna_funkcia(int sig){
printf("Signal %d bol odchyteny.\n",sig);
}
int main(){
printf("Spustenie hlavneho programu.\n");
sigset_t mask;
alarm(3);
printf("Program caka na signal.\n");
signal(SIGALRM, obsluzna_funkcia);
sigsuspend(&mask);
printf("Koniec hlavneho programu.\n");
return(0);
}
Opis kódu:
V hlavnom programe sa vytvorí prázdna maska. Následne sa zavolá služba alarm() s parametrom 3 čo znamená, že sa po 3 sekundách odošle signál SIGALRM. Tak je prostredníctvom služby signal() nastavená reakcia na signál SIGALRM. Pomocou služby sigsuspend() sa pozastaví činnosť programu a pri použití prázdnej masky proces čaká na akýkoľvek signál.
Služba jadra sigaction()
Prostredníctvom služby sigaction() je možné zmeniť akciu vykonanú procesu po prijatí špecifického signálu.
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
Parametere | |
---|---|
int signum | špecifikuje signál |
const struct sigaction *act | ak nie je NULL, tak špecifikuje novú akciu pre signál signum |
const stuct sigaction *oldact | ak nie je NULL, tak sa sem vloží predchadzajúca akcia signálu signum |
Opis štruktúry sigaction:
struct sigaction{
void (*sa_handler) (int);
void (*sa_sigaction) (int, siginfo_t *,void*);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer) (void);
}
- void (*sa_handler) (int) - obslužná funkcia - má rovnaký význam ako pri službe signal(),
- void (sa_sigaction) (int, siginfo_t ,void*) - môžu byť použité príznaky SIG_IGN a SIG_DFL,
- sigset_t sa_mask - špecifikuje masku signálov, ktoré by mali byť blokované počas vykonávania obslužnej funkcie. Blokované - t. j. pridané do signálovej masky vlákna, v ktorom je obslužná funkcia vyvolaná. Okrem toho bude signál, ktorý spustil obslužnú funkciu, zablokovaný, pokiaľ nie je použitý príznak SA_NODEFER.
- int sa_flags - modifikuje správanie sa obslužnej funkcie signálu. Môže nadobúdať hodnoty:
- SA_NOCLDSTOP - negeneruje sa signál SIGCHILD, keď sa potomkovia ukončia,
- SA_RESETHAND - nastavuje pôvodnu akciu na daný signál (význam ako SIG_DFL),
- SA_RESTART - reštartuje služby, ktoré môžu byť prerušené príchodom nejakého signálu,
- SA_NODEFER - keď beží obslúžna funkcia a proces zachytí nejaký signál, tento signál sa nepridá do sady signálov.
Návratová hodnota |
---|
V prípade úspešného vykonávania je to 0. |
V prípade chyby je to -1. |
Poznámka
Podrobnejšie informácie k službe sigaction() sa nachádzajú v manuáli: man sigaction
Upozornenie
Príklad použitia bude uvedený po vysvetlení služieb sigemptyset(), sigaddset(), sigdellset() a sigprocmask().
Služby jadra sigemptyset(), sigfillset(), sigaddset(), sigdelset(), sigprocmask()
Služby sigemptyset() a sigfillset() majú byť volané vždy pred ostatnými službami. Argumentom týchto služieb je sada signálov, ktorej dátový typ je sigset_t.
sigemptyset()
Inicializuje sadu signálov.
sigfillset()
Naplní sadu signálov všetkými možnými signálmi.
sigaddset()
Pridá do sady konkrétny signál.
sigdelset()
Odstráni jeden alebo niekoľko signálov zo sady. Obvykle sa využíva po službe sigfillset().
Služba jadra sigprocmask()
Slúži na zablokovanie prijatia niektorého signálu pokiaľ nie je vykonávanie obslúžnej funkcie ukončené. Vďaka tomu je možné ochraniť časti programu, ktoré nesmú byť prerušené (napríklad príchodom nejakého signálu). Táto služba modifikuje aktuálnu signálovú masku.
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
Parametere | ||
---|---|---|
int how | určuje, ako má byť modifikovaná aktuálna signálová maska | |
const sigset_t *set | ak je NULL, tak zostane signálová maska nezmenená ale aktuálna hodnota masky je napriek tomu vrátená v oldset (ak nie je NULL) | |
sigset_t *oldset | ak nie je NULL, tak predchádzajúca hodnota signálovej masky je uložená v oldset |
Parameter int how môže nadobúdať hodnoty: SIG_BLOCK - signály v dodanej sade sú pridané k blokovaným signálom aktuálnej sady SIG_UNBLOCK - signály v dodanej sade sú odstránené zo sady blokovaných signálov * SIT_SETMASK - kompletne nahrádza signálovú masku. Špecifikovaná sada nahradí aktuálnu sadu, v ktorej sa nachádzajú blokované signály
Návratová hodnota |
---|
V prípade úspešného vykonávania je to 0. |
V prípade chyby je to -1. |
Poznámka
Podrobnejšie informácie k službe sigprocmask() sa nachádzajú v manuáli: man sigprocmask
Príklad použitia k službám sigaction() a sigemptyset():
#define _POSIX_SOURCE
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void obsluzna_funkcia(int sig){
printf("Signal %d bol odchyteny.\n", sig);
}
int main(){
struct sigaction act;
act.sa_handler = obsluzna_funkcia;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGINT, &act, 0);
int i=0;
while(i<5){
printf("Hello World!\n");
sleep(1);
i++;
}
printf("Koniec programu.\n");
}
Opis kódu:
Na začiatku je definovaná obslužná funkcia obsluzna_funkcia()
.
Následne je deklarovaná štruktúra sigaction.
Tejto štruktúre je nastavená obslužná funkcia pomocou act.sa_handler = obsluzna_funkcia;
.
sigemptyset(&act.sa_mask);
vyprázdni sadu signálov.
Príznak štruktúry je inicializovaný na hodnotu 0 pomocou act.sa_flags = 0;
, pretože nie je potrebné nastaviť špeciálne správanie sa obslužnej funkcie.
Pomocou služby sigaction()
, kde je na mieste prvého argumentu predaný SIGINT, sa nastaví odchytávanie signálu prerušenia.
V závere sa v slučke vypisuje reťazec "Hello world!"
v sekundových intervaloch.
Po 5 sekundách sa ukončí činnosť programu.
Vysvetlenie funkcionality programu:
Program aj napriek stláčaniu klávesovej skratky CTRL + C
bude vypisovať reťazec "Hello World!", pretože je v programe nastavené odchytávanie tohto signálu (SIGINT). Ukončiť program je možné pomocou klávesovej skratky CTRL + \
, prípadne sa procesu tohto programu pošle signál kill -9
.
Výstup z programu môže vyzerať nasledovne:
Hello World!
^CSignal 2 bol odchyteny.
Hello World!
Hello World!
Hello World!
Hello World!
Koniec programu.
Príklady použitia ďalších služieb:
Blokovanie všetkých signálov okrem SIGUSR1 a SIGUSR2
Nasledujúci príklad zablokuje všetky signály okrem SIGUSR1 a SIGUSR2: * Deklarácia sady signálov s názvom sig_set:
sigset_t sig_set;
- Naplnenie sady všetkými možnými signálmi:
sigfillset(&sig_set);
- Odstránenie signálov SIGUSR1 a SIGUSR2 zo sady:
- Zaevidovanie upravenej masky:
sigprocmask(SIG_SETMASK, &sig_set,NULL);
Odblokovanie/odstránenie SIGINT, SIGQUIT:
- Deklarácia:
sigset_t onesig, old_mask;
- Inicializácia sady
sigemptyset(&onesig);
- Pridanie signálov do sady:
sigaddset(&onesig, SIGINT);
sigaddset(&onesig, SIGQUIT);
Úloha 2.2
Doplňte do nasledujúci kód tak, aby signály v dodanej sade boli odstránené zo sady blokovaných signálov a aby sa následne uchovala stará sada v old_mask:
sigprocmask(
[1. ............] ,
[2. ............] ,
[3. ............] );
Príklad práce so signálmi:
Nasledujúci program najprv povolí signál SIGUSR1 a nastaví obslužnú funkciu. Následne tento signál zablokuje (nedôjde k zavolaniu obslúžnej funkcie).
#define _POSIX_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void obsluzna_funkcia(int sig){
printf("Signal %d bol odchyteny.\n", sig);
}
int main(){
printf("Spustenie hlavneho programu.\n");
struct sigaction sigact;
sigset_t sigset;
//Prva cast
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = 0;
sigact.sa_handler = obsluzna_funkcia;
sigaction(SIGUSR1, &sigact, NULL);
printf("Prve poslanie signalu.\n");
kill(getpid(), SIGUSR1);
//Druha cast
sigemptyset(&sigset);
sigaddset( &sigset, SIGUSR1);
sigprocmask(SIG_SETMASK, &sigset, NULL);
printf("Druhe poslanie signalu.\n");
kill(getpid(), SIGUSR1);
printf("Ukoncenie programu.\n");
return (0);
}
Opis kódu:
Najprv je zadeklarovaná štruktúra sigaction
a sada signálov sigset
. V prvej časti je nastavená štruktúra sigaction
na obsluhu signálu SIGUSR1. Štruktúre sigaction
je nastavená obslúžna funkcia obsluzna_funkcia()
. Pri zaslaní signálu SIGUSR1 je na štandardný výstup vypísaný text: "
Signal 10 bol odchyteny.".
V druhej časti je najskôr vyčistená štruktúra sigaction a je do nej pridaný len signál SIGUSR1. Na danú sadu je nastavený signál a následne vykoná opätovné poslanie signálu.
Výstup z programu:
pred prvym poslanim signalu
som v obsluznej funkcii
pred druhym poslanim signalu
po druhom poslani signalu
Z výstupu je jasné, že program sa v prvej časti dostal aj do obslúžnej funkcie catcher. V druhej časti je už zablokované prijatie signálu SIGUSR1 a tak sa do obslúžnej funkcie program nedostane.
Aplikovanie poznatkov
Úloha 2.3
V nasledujúcom programe je vynechaných niekoľko časti. Dopĺňte čo tam patrí:
#define _POSIX_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void obsluzna_funkcia(int sig){
printf("Signal %d bol odchyteny.\n", sig);
}
int main() {
struct sigaction sigact;
sigset_t sigset;
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = 0;
[1.____________] = obsluzna_funkcia;
sigaction(SIGUSR1, &sigact, NULL);
sigfillset(&sigset);
sigaddset(&sigset, SIGUSR1);
sigprocmask([2.____________], &sigset, NULL);
printf("Prve poslanie signalu.\n");
[3.____________](getpid(), SIGUSR1);
printf("Pred odblokovanim signalu SIGUSR1.\n");
[4.____________](&sigset, SIGUSR1);
sigprocmask(SIG_SETMASK, &sigset, NULL);
printf("Po odblokovani signalu SIGUSR1.\n");
return (0);
}
-
formulár:
-
formulár:
-
formulár:
-
formulár
Úloha 2.4
Predchadzajúci kód, do ktorého ste doplňali správne odpovede sa nachádza v priečinku 07 pod názvom u2_1.c. Pred testovaním je potrebné skompilovať program u2_1.c.
Poznámka
V archíve, ktorý ste si stiahli k cvičeniu máte predchadzajúci kód v súbore u2_1.c. Správne odpovede len doplňte namiesto [______].
V prípade úspešného doplnenia bude výstup z programu vyzerať nasledovne:
Prve poslanie signalu.
Pred odblokovanim signalu SIGUSR1.
Signal 10 bol odchyteny.
Po odblokovani signalu SIGUSR1.