Knihovna signal.h
Knihovna signal.h
Signály slouží k oznámení nějaké události. Například při dělení nulou je poslán programu signál SIGFPE. Definice signálů najdete v knihovně <signal.h>. Ve chvíli, kdy je vašemu programu poslán signál, provádění programu se pozastaví a signál je zpracován příslušnou funkcí, kterou jste signálu přiřadili. Některé signály lze ignorovat a některé naopak nelze ani zpracovat, ani ignorovat (např. signál SIGKILL).
Pokud signálu žádnou funkci nepřiřadíte, bude provedena standardní
obsluha signálu. Například při dělení nulou to bude mít za následek
ukončení programu. Signály nemusí být vyvolány jen událostmi
programu, ale mohou být posílány programu operačním systémem nebo
jinými programy (v Linuxu můžete programu poslat signál pomocí
příkazu kill
).
Signál | Význam |
---|---|
SIGINT | Přerušení provádění programu |
SIGABRT | Zastavení programu |
SIGILL | Neplatná instrukce (např. instrukce procesoru Pentium na stroji i386 |
SIGFPE | Aritmetická chyba (dělení nulou, výsledek mimo rozsah) |
SIGSEGV | Chybný přístup do paměti.(Program čte z míst v paměti, ze kterých by neměl). |
SIGTERM | Ukončení (linuxový příkaz kill) |
Protože se budu zabývat v dalším tutoriálu programováním v Linuxu, uvedu zde ještě tabulku některých signálů podle normy POSIX.
Signál | Význam |
---|---|
SIGHUP | problém |
SIGQUIT | opuštění terminálu |
SIGKILL | zabití procesu (nelze odchytit) |
SIGUSR1 | signál definovaný uživatelem |
SIGUSR2 | další signál definovaný uživatelem |
SIGPIPE | nefungující roura (např. když z ní nikdo nečte) |
SIGALARM | budík |
SIGCHLD | dceřiný proces se změnil (např. se zastavil nebo skončil) |
SIGCONT | pokračovat (byl-li proces pozastaven) |
SIGSTOP | zastavit provádění (nelze odchytit) |
SIGTSTP | zastavit provádění (signál z klávesnice) |
SIGTTIN | čtení procesu spuštěného na pozadí (ze standardního vstupu) |
SIGTTOU | proces na pozadí se pokouší zapisovat |
Funkce signal()
Pokud některý ze signálů SIGHUP – SIGALARM v programu neodchytíte,
dojde k ukončení běhu programu. K odchytávání signálů, respektive k
přiřazení signálům funkce slouží funkce signal()
.
Podívejte se na její deklaraci:
Funkce signal
má jako první argument
signál signum, kterému je přiřazena funkce handler.
Funkce handler()
nemá žádnou návratovou hodnotu
a musí mít jeden argument typu int, kterým je předávána hodnota signálu, který
způsobil volání funkce. Jedna funkce totiž může být přiřazena
libovolnému množství signálů a podle tohoto argumentu má šanci poznat, který signál
ji vyvolal.
Funkce signal()
vrací ukazatel na funkci, která byla signálu
dosud přiřazena, SIG_ERR v případě chyby, nebo jednu z následujících hodnot:
Makro | Význam |
---|---|
SIG_IGN | ignoruj signál |
SIG_DFL | implicitní chování funkce |
Tyto hodnoty lze použít ve funkci signal jako druhý argument (tedy místo funkce obsluhující signál). Možná to na první pohled vypadá trošku složitě, ale v příkladu uvidíte, že to nemůže být snazší.
V příkladu budu odchytávat signál SIGFPE
po dělení nulou. Nejdříve vytvořím globální proměnou chyceni_signalu
,
ve které budu počítat, kolikrát byl signál vyvolán. Při každém
vyvolání signálu je nastaveno původní přiřazení signálu, proto ve
funkci 1)zpracuj_signal()
funkci znovu
signálu SIGFPE přiřadím.
Po skončení funkce obsluhující signál se program vrátí do místa ve
kterém byl přerušen. V příkladě to znamená, že se pokusí znovu
dělit nulou.
Podrobněji se budu věnovat odchytávání signálů ještě v části věnované programování v systému Linux.
- /*------------------------------------------------*/
- /* signal/signal1.c */
- #include <stdio.h>
- #include <signal.h>
- {
- chyceni_signalu++;
- case SIGFPE:
- a, b, chyceni_signalu);
- }
- }
- else {
- /* toto uz neni potreba */
- }
- }
- {
- ukazatel_na_fci = signal(SIGFPE, zpracuj_signal);
- }
- /* abort(); */
- }
- /*------------------------------------------------*/
Visual Studio SIGFPE
signál pro dělení nulou nevyšle,
takže vám tento příklad ve VS fungovat nebude. Ale můžete si vyzkoušet
zachytávání signálu SIGABRT
, který VS vyvolá i zachytí.
(Vyvolat ho můžete funkcí
abort()
). Zachytávání SIGABRT
si můžete samozřejmě vyzkoušet i s jinými překladači :-).
Výstup z programu v prostředí Linux:
To je poprve, co nastavuji zpracovani signalu pro SIGFPE Zaciname: Delit nulou se nevyplaci! a= 5, b= 0, chyceni_signalu= 1 Delit nulou se nevyplaci! a= 5, b= 0, chyceni_signalu= 2 Delit nulou se nevyplaci! a= 5, b= 0, chyceni_signalu= 3 Uz toho mam dost! Výjimka matematického koprocesoru (SIGFPE)
Převzato z manuálových stránek k funkci signal()
:
Podle normy POSIX není definováno chování procesu poté, co ignoruje signál SIGFPE, SIGILL, nebo SIGSEGV, který nebyl
generován funkcemi kill
nebo raise
.
Celočíselné dělení nulou dává nedefinovaný výsledek a na některých
architekturách generuje signál SIGFPE. Ignorování tohoto signálu může způsobit zacyklení procesu.
Jedna perlička k příkladu na závěr. Pokud byste v programu uvedli výraz ve kterém byste dělili nulou přímo a ne přes proměnné jako v předchozím příkladě, pak by to překladač odhalil a buď by program nepřeložil, nebo by za výraz dosadil nulu. V takovém případě by za běhu programu k dělení nulou nedošlo.
Funkce sigaction()
Funkce signal()
volá funkci sigaction()
.
Funkce sigaction()
je o něco složitější, protože má
více možností jak ovlivnit chování signálů. Já jen ukáži, jak tuto
funkci použít namísto signal()
.
Funkce sigaction()
odpovídá normě POSIX.
To znamená, že máte zaručeno, že ji budete mít definovanou v Unixech,
ale ve Windows ji pravděpodobně mít k dispozici nebudete. Ve windows
si budete muset vystačit s funkcí signal()
(viz víše).
Deklarace funkce vypadá takto:
Návratovou hodnotou je 0 v případě úspěchu a -1 v případě chyby (v příkladu,
ač se to správně nedělá, budu návratovou hodnotu ignorovat). První
argument je signál, který chceme odchytávat, druhý argument je struktura,
která nastavuje odchytávání signálu a do třetího argumentu se uloží
staré nastavení pro daný signál signum
.
Prohlédněte si definici struktury sigaction
(ano, má stejné jméno jako funkce sigaction()
, ale nezapomeňte
na struct
).
První argument struktury sigaction
je
ukazatel na obslužnou funkci (můžete použít SIG_DFL i SIG_IGN, viz výše). Třetí argument,
sa_mask ovlivňuje zachytávání signálů
během volání obslužné funkce (blokuje je do skončení funkce) a pro nás je nezajímavý.
Čtvrtý argument, sa_flags,
je množina příznaků, která ovlivňuje obsluhu signálu. Například
SA_RESTART způsobí, že po zpracování signálu je znovu
nastavena tatáž obsluha signálu a nikoliv implicitní akce. Poslední argument,
sa_restorer, je zastaralý a již se nepoužívá.
Pokud je nastaven v sa_flags příznak SA_SIGINFO
,
použije se místo sa_handler funkce v sa_sigaction.
Některé systémy používají pro tyto dvě proměnné union,
proto nikdy nepřiřazujte do jedné struct sigation
obě obslužné funkce.
Funkce sa_sigaction má, jak z definice struktury vidíte, jiné argumenty,
jinak se používá obdobně jako sa_handler.
Příklad sigaction.c
dělá to samé, co příklad signal1.c
- /*------------------------------------------------*/
- /* signal/sigaction.c */
- #include <stdio.h>
- #include <signal.h>
- {
- chyceni_signalu++;
- struct sigaction sa;
- sigset_t set;
- case SIGFPE:
- a, b, chyceni_signalu);
- }
- sigemptyset(&set);
- sa.sa_handler = SIG_DFL;
- sa.sa_mask = set;
- sa.sa_flags = SA_RESTART;
- sa.sa_restorer = NULL;
- }
- }
- {
- sigset_t set;
- sigemptyset(&set); /* inicializuje - vyprazdni set */
- sa.sa_handler = zpracuj_signal;
- sa.sa_mask = set;
- sa.sa_flags = SA_RESTART;
- sa.sa_restorer = NULL;
- }
- }
- /*------------------------------------------------*/
To je poprve, co nastavuji zpracovani signalu pro SIGFPE Zaciname: Delit nulou se nevyplaci! a= 5, b= 0, chyceni_signalu= 1 Delit nulou se nevyplaci! a= 5, b= 0, chyceni_signalu= 2 Delit nulou se nevyplaci! a= 5, b= 0, chyceni_signalu= 3 Uz toho mam dost! Výjimka matematického koprocesoru (SIGFPE)
1)To platilo dříve. Dnes platí, že
obslužná funkce zůstává stále zaregistrována. Kvůli tomuto
historickému zmatení se doporučuje místo funkce
signal()
používat funkci sigaction()
.
Bezpečné použití funkcí
V předchozích příkladech je velká chyba. Ve funkci pro zpracování signálu používám
funkci printf()
, ale ta je AS-Unsafe.
Některé funkce není bezpečné používat v částech programu obsluhujících signály. Takové funkce se označují jako AS-Unsafe, naopak AS-Safe použít v signálech můžete.
Jestli je funkce AS-Safe nebo AS-Unsafe se bohužel nedočtete v manuálových stránkách, ale najdete ji v dokumentaci na gnu.org, viz třeba Formatted Output Functions.
Kromě AS-Safe / AS-Unsafe mohou být funkce MT-Safe/ MT-Unsafe. To zase značí, zda je možné funkci bezpečně volat ve vícevláknovém (multi-thread) programu, tj. volat jí ve dvou vláknech ve stejný čas (tj. bez synchronizace).
Více se o tomto tématu dočtete v kapitole POSIX Safety Concepts z gnu.org.
Takže místo printf()
používejte v obsluze signálů funkci write()
,
která je AS-Safe.