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).

Tabulka signálů ANSI
SignálVý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.

Tabulka signálů POSIX
SignálVý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:

void (*signal(int signum, void (*handler)(int)))(int);

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:

Návratové hodnoty funkce signal
MakroVý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 zpracuj_signal() funkci znovu signálu SIGFPE přiřadím.1)
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.

  1. /*------------------------------------------------*/
  2. /* signal/signal1.c                               */
  3.  
  4. #include <stdio.h>
  5. #include <signal.h>
  6.  
  7. static int chyceni_signalu;
  8.  
  9. int a = 5, b = 0;
  10.  
  11. void zpracuj_signal(int cislo_signalu)
  12. {
  13.     chyceni_signalu++;
  14.     switch (cislo_signalu) {
  15.     case SIGFPE:
  16.         printf("Delit nulou se nevyplaci! a= %i, b= %i, chyceni_signalu= %i\n",
  17.                 a, b, chyceni_signalu);
  18.         break;
  19.     default:
  20.         printf("Neocekavany signal!\n");
  21.         break;
  22.     }
  23.     if (chyceni_signalu >= 3) {
  24.         printf("Uz toho mam dost!\n");
  25.         (void) signal(SIGFPE, SIG_DFL);
  26.     }
  27.     else {
  28.         /* toto uz neni potreba */
  29.         (void) signal(SIGFPE, zpracuj_signal);
  30.     }
  31. }
  32.  
  33. int main(void)
  34. {
  35.  
  36.     void (*ukazatel_na_fci) (int);
  37.  
  38.     ukazatel_na_fci = signal(SIGFPE, zpracuj_signal);
  39.     if(ukazatel_na_fci == SIG_DFL) {
  40.         printf("To je poprve, co nastavuji zpracovani signalu pro SIGFPE\n");
  41.     }
  42.  
  43.     /* abort(); */
  44.  
  45.     printf("Zaciname: \n");
  46.     printf("Deleni: %i/%i=%i\n", a, b, a / b);
  47.     printf("Tento text se jiz nevytiskne.");
  48.  
  49.     return 0;
  50. }
  51. /*------------------------------------------------*/
Visual Studio

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().

Visual Studio

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:

int sigaction(int signum, const struct sigaction *act,
              struct sigaction *oldact)

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).

struct sigaction {
         void     (*sa_handler)(int);
         void     (*sa_sigaction)(int, siginfo_t *, void *);
         sigset_t sa_mask;
         int      sa_flags;
         void     (*sa_restorer)(void);
};

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

  1. /*------------------------------------------------*/
  2. /* signal/sigaction.c                             */
  3.  
  4. #include <stdio.h>
  5. #include <signal.h>
  6.  
  7. static int chyceni_signalu;
  8.  
  9. int a = 5, b = 0;
  10.  
  11. void zpracuj_signal(int cislo_signalu)
  12. {
  13.     chyceni_signalu++;
  14.     struct sigaction sa;
  15.     sigset_t set;
  16.  
  17.  
  18.     switch (cislo_signalu) {
  19.     case SIGFPE:
  20.         printf("Delit nulou se nevyplaci! a= %i, b= %i, chyceni_signalu= %i\n",
  21.                 a, b, chyceni_signalu);
  22.         break;
  23.     default:
  24.         printf("Neocekavany signal!\n");
  25.         break;
  26.     }
  27.     if (chyceni_signalu >= 3) {
  28.         printf("Uz toho mam dost!\n");
  29.         sigemptyset(&set);
  30.         sa.sa_handler = SIG_DFL;
  31.         sa.sa_mask = set;
  32.         sa.sa_flags = SA_RESTART;
  33.         sa.sa_restorer = NULL;
  34.         (void) sigaction(SIGFPE, &sa, NULL);
  35.     }
  36. }
  37.  
  38. int main(void)
  39. {
  40.  
  41.     struct sigaction sb, sa;
  42.     sigset_t set;
  43.  
  44.     sigemptyset(&set); /* inicializuje - vyprazdni set */
  45.     sa.sa_handler = zpracuj_signal;
  46.     sa.sa_mask = set;
  47.     sa.sa_flags = SA_RESTART;
  48.     sa.sa_restorer = NULL;
  49.  
  50.     (void) sigaction(SIGFPE, &sa, &sb);
  51.     if(sb.sa_handler == SIG_DFL) {
  52.         printf("To je poprve, co nastavuji zpracovani signalu pro SIGFPE\n");
  53.     }
  54.  
  55.     printf("Zaciname: \n");
  56.     printf("Deleni: %i/%i=%i\n", a, b, a / b);
  57.     printf("Tento text se jiz nevytiskne.");
  58.  
  59.     return 0;
  60. }
  61. /*------------------------------------------------*/
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.

Komentář Hlášení chyby
Created: 28.8.2014
Last updated: 28.8.2014
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..