Linux - Signály (Signals)

Signály je mechanizmus pro komunikaci a manipulaci s procesy. Signál je speciální zpráva zaslaná procesu. Signály jsou asynchronní, tj. když jej proces obdrží, ihned ho obslouží, bez dokončení aktuální funkce (ta se dokončí až pak). Každý signál je specifikován svým číslem, v programech se však používají jména definovaná v <bits/signum.h>. Všechny signály jsou pak popsány v signal(7).

Zaslání signálu

raise(3)
#include <signal.h>
int raise (int sig);
  • pošle signál sig vlastnímu procesu
  • vrací 0, při chybě nenulové číslo

Př. Vytvořte program, který si pošle SIGTERM a tím se tedy ukončí. Program nemažte, budete ho rozšiřovat o další probírané funkce!

Proces může poslat signál jinému procesu. Např. ho ukončit zasláním SIGTERM nebo SIGKILL.

OTÁZKA: Jaký je rozdíl mezi SIGTERM a SIGKILL?

kill(1)
kill [signal] pid ...
  • pošle každému procesu (pid) SIGTERM (pokud není signal zadán), nebo signál signal
  • přepínač -l vypíše všechny signály

Př. Spusťte si program top a zabijte ho pomocí SIGKILL.

kill(2)
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
  • pošle signál sig procesu s ID pid
  • vrací 0, při chybě -1

Př. Nahraďte funkci raise funkcí kill.

Pro zaslání příkazu procesu jsou rezervovány dva uživatelské signály, SIGUSR1 a SIGUSR2. Někdy se pro tento účel používá i SIGHUP, na "probuzení" nebo "restart" programu.

strsignal(3)
#define _GNU_SOURCE
#include <string.h>
char *strsignal(int sig);
  • vrátí řetězec popisující singál s číslem sig

psignal(3)
#include <signal.h>
void psignal(int sig, const char *s);
  • napíše na stderr zprávu tvaru s: sig_d, kde sig_d je popis signálu sig

pause(2)
#include <unistd.h>
int pause(void);
  • "uspí" proces, dokud nepřijde signál
  • při přerušení (signálem) vrací -1

Př. Uspěte program a z jiného terminálu mu pošlete SIGCONT.

Obdržení signálu

Co proces udělá po obdržení signálu záleží na určení signálu. Každý signál má defaultní určení, která určuje, co se stane, když program nestanovuje jinak. Pro většinu signálů jej může program ignorovat nebo zavolat speciální funkci (signal-handler). Při volání handleru se program zastaví, vykoná se handler a pak program pokračuje.
Např. SIGBUS (bus error), SIGSEGV (segmentation violation) a SIGFPE (floating point exception) jsou zaslány procesu, když se pokouší provést nepřípustnou akci. Defaultní určení signálu je ukončení procesu a vytvoření core souboru.

signal(2)
#define _GNU_SOURCE
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t action);
  • instaluje nový signal-handler pro signál s číslem signum
  • action může být:
    • signal-handler funkce
    • SIG_IGN pro ignorování signálu
    • SIG_DFL pro defaultní určení signálu
  • parametr signal-handler funkce je číslo signálu
  • defaultní určení signálů jsou popsány v signal(7)
  • nelze instalovat handler na SIGKILL a SIGSTOP
  • sighandler_t je GNU rozšíření
  • vrací předchozí signal-handler, nebo při chybě SIG_ERR

Př. Napište signal-handler, který bude bude počítat, kolik signálů SIGTERM program dostane. Při SIGQUIT se ukončí a vypíše počet obdržených signálů SIGTERM. Pošlete programu pár signálů SIGTERM a pak SIGQUIT.

Protože signály jsou asynchronní, program by mohl být při zpracovávání signálu v nekonzistentním stavu a proto by se v signal-handleru neměly volat knihovní a systémové funkce a vykonávat I/O operace. signal-handler by měl vykonat jen práci nezbytně nutnou k ošetření signálu.
Je možné, že signal-handler bude přerušen obdržením jiného signálu. Pokud se to stane, je velmi těžké to debugovat, pokud je tam chyba, proto by se měl dávat velký pozor na to, co se v signal-handleru provádí.
Nebezpečné je dokonce i přiřazení do globální proměnné, protože může probíhat ve více instrukcích, a druhý signál se může vyskytnout mezi nimi. Tato globální proměnná by měla být typu sig_atomic_t, do kterého se přiřazuje v jedné instrukci (protože je to int).

#include <signal.h>
typedef int __sig_atomic_t;
typedef __sig_atomic_t sig_atomic_t;

Př. Proměnnou, ve které počítáte počet signálů SIGTERM, změňte na typ sig_atomic_t.

alarm(2)
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
  • pošle procesu SIGALRM za seconds sekund
  • pokud seconds je 0, nic se nenačasuje
  • vrací zbývající počet sekund do zaslání signálu, nebo 0, pokud není načasován žádný alarm

Př. Zařiďte, aby se program sám ukončil po 10 sekundách běhu.

Procesy a signály

Proces může skončit abnormálně, jako odpověď na signál. Např. SIGBUS, SIGSEGV a SIGFPE způsobí ukončení procesu. Další signály slouží k explicitnímu ukončení. SIGINT je procesu zaslán, když se jej uživatel snaží ukončit pomocí C-C. SIGTERM posílá kill. Při volání funkce abort si proces sám pošle SIGABRT, což jej také ukončí s core souborem. Nejmocnější je SIGKILL, který jej okamžitě ukončí a program tomu nemůže nijak zabránit (blokovat, ingnorovat, ošetřit).

Čekání na potomka pomocí wait proces blokuje, dokud se potomek neukončí. Většinou ale chceme, aby i rodič pokračovat dál. Jak se ale postarat o potomka ihned po jeho ukončení, tak, aby nezůstávali zombie? Jedna možnost je periodicky volat wait3 nebo wait4, které lze volat i v neblokujícím módu.
Mnohem lepší je ale rodiče informovat o ukončení potomka, pomocí signálu. Když se ukončí potomek, systém pošle rodiči signál SIGCHLD, který defaultně nemá žádné určení. Funkci wait tedy zavoláme v signal-handleru signálu SIGCHLD.

PŘÍKLAD

Další funkce týkající se signálů jsou:



Jan Outrata
outrata@phoenix.inf.upol.cz