další předchozí obsah
Další: Meziprocesní komunikace Předchozí: Řízení procesů

  Signály





Úvod

Signály jsou vlastně softwarová přerušení. Každý netriviální program má co do činění se signály. Signály se objevily už ve Verzi 7, ale až s příchodem SVR3 se staly efektivní.



Koncepce signálů

Každý signál má své jméno. Jméno každého signálu začíná na SIG. Verze 7 měla 15 různých signálů. SVR4 a BSD mají 31 různých signálů. Jména těchto signálů jsou definována v souboru <signal.h>. Žádný z těchto signálů nemá hodnotu 0, POSIX.1 totiž definuje signál null.

Signály mohou být generovány různými událostmi:

Signály jsou klasickou asynchronní záležitostí. Reakce na ně mohou být následující:

Tabulka 13 uvádí přehled signálů dle různých norem.

 

Jméno Popis ANSI CPOSIX.1 Pozn.Norm. akce
SIGABRT Abnormální ukončení (abort) o o Ukon.+core
SIGALRM Budík o Ukončení
SIGBUS Chyba sběrnice Ukončení
SIGCHLD Potomek zastaven či ukončen o Ignorace
SIGCONT Pokračování po SIGSTOP o Pokračování
SIGEMP Instrukce EMP Ukon.+core
SIGFPE Aritmetická vyjímka o o Ukon.+core
SIGHUP Zavěšení o Ukončení
SIGILL Ilegální instrukce o o Ukončení
SIGINFO Požadavek o info z terminálu BSD Ignorace
SIGINT Přerušení z terminálu o o Ukončení
SIGIO Asynchronní I/O Ukon./Ign.
SIGIOT Hardwarová chyba (IOT) Ukon.+core
SIGKILL Ukončení (nejde chytit) o Ukončení
SIGPIPE Zápis do roury bez čtenářů o Ukončení
SIGPOLL Sdílená událost Sys.V Ukončení
SIGPROF Budík profile (settimer) Ukončení
SIGPWR Chyba napájení/restart Sys.V Ignorace
SIGQUIT Ukončení z terminálu o Ukon.+core
SIGSEGV Vadný odkaz do paměti o o Ukon.+core
SIGSTOP Zastavení (nejde chytit) o Zastavení
SIGSYS Chybné volání systému Ukon.+core
SIGTERM Ukončení o o Ukončení
SIGTRAP Trasování (hw vyjímka) Ukon.+core
SIGTSTP Zastavení z terminálu o Zastavení
SIGTTIN Proces v pozadí chce číst tty o Zastavení
SIGTTOU Pr. v pozadí chce psát do tty o Zastavení
SIGURG Urgentní podmínka na soket Ignorace
SIGUSR1 Uživatelský signál 1 o Ukončení
SIGUSR2 Uživatelský signál 2 o Ukončení
SIGVTALRM Virtuální hodiny (settimer) Ukončení
SIGWINCH Změna velikosti okna terminálu Ignorace
SIGXCPU Překročen časový limit CPU Ukon.+core
SIGXFSZ Překr. limit velikost souboru Ukon.+core
Tabulka 13: Unixové signály






Funkce signal

Nejjednodušším prostředníkem k signálům je funkce signal.

#include <signal.h>
void (*signal (int signo, void (*func)(int)))(int);
Vrací: předchozí obsluhu signálu

Za argument signo lze dosadit název z tabulky 13. Argument func může nabývat následujících hodnot:

Příklad:

#include <signal.h>

static void sig_usr(int);  /* one handler for both signals */

int
main(void)
{
    if (signal(SIGUSR1, sig_usr) == SIG_ERR)
        err_sys("can't catch SIGUSR1");
    if (signal(SIGUSR2, sig_usr) == SIG_ERR)
        err_sys("can't catch SIGUSR2");

    for ( ; ; )
        pause();
}

static void
sig_usr(int signo)        /* argument is signal number */
{
    if (signo == SIGUSR1)
        printf("received SIGUSR1\n");
    else if (signo == SIGUSR2)
        printf("received SIGUSR2\n");
    else
        err_dump("received signal %d\n", signo);
    return;
}


Funkce kill a raise

Jestliže jsme schopni signály zpracovávat, musíme je umět i zasílat. To nám umožňuje funkce kill nebo raise. Funkce kill umožňuje zaslání signálu procesu nebo skupině procesů. raise umožňuje poslat signál procesu samotnému.

#include <sys/types.h>
#include <signal.h>
int kill (pid_t pid, int signo);
int raise (int signo);
Vrací: 0 když OK, -1 při chybě

Je několik rozdílných podmínek pro argument pid funkce kill.

pid > 0 Signál je posílán procesu s daným PID
pid == 0 Signál pro všechny procesy ze skupiny vysílajícího
pid < 0 Signál pro všechny procesy ze skupiny abs(pid)
pid == -1 Nespecifikováno v POSIX.1

POSIX.1 rezervuje signál 0 jako speciální null signál. Tento signál se často používá pro zjištění, zda daný proces existuje. Jestliže pošleme signál null neexistujícímu procesu, errno bude ESRCH. Pozor, nezapomínejte, že unix po určité době recykluje identifikační čísla procesů.



Funkce alarm a pause

Funkce alarm nám umožňuje nastavit budík na předem stanovený čas. Zazvonění budíku je v unixu reprezentováno generováním signálu SIGALRM. Jestli bude signál ignorován nebo zpracován, je již věcí daného procesu.

#include <unistd.h>
unsigned int alarm (unsigned int seconds);
Vrací: 0 nebo počet sekund předchozího alarmu

Argument sice specifikuje čas, ale musíte počítat s dodatečnou režií systému. Jeden proces může mít nastaven maximálně jeden budík.

Doplněk k této funkci tvoří funkce pause. Po vyvolání této funkce přejde proces do stavu pozdržení (suspend) až do příchodu signálu.

#include <unistd.h>
int pause (void);
Vrací: -1 s errno nastavenou na EINTR

Použití funkce ilustruje nejlépe příklad.

Příklad:

#include <signal.h>
#include <unistd.h>

static void
sig_alrm(int signo)
{
   return; /* nothing to do, just return to wake up the pause */
}

unsigned int
sleep1(unsigned int nsecs)
{
    if (signal(SIGALRM, sig_alrm) == SIG_ERR)
        return(nsecs);
    alarm(nsecs);       /* start the timer */
    pause();            /* next caught signal wakes us up */
    return( alarm(0) ); /* turn off timer, return unslept time */
}

Tato funkce vypadá jako funkce sleep (viz funkci sleep). Implementace však přináší problémy:

Standardním použitím alarm je implementace tzv. hlídacího psa (watch dog). Je to vlastně nastavení maximální doby trvání nějaké operace. Není-li operace hotova do určité doby, došlo patrně k chybě.

Příklad:

#include    <signal.h>

static void    sig_alrm(int);

int
main(void)
{
    int     n;
    char    line[MAXLINE];

    if (signal(SIGALRM, sig_alrm) == SIG_ERR)
        err_sys("signal(SIGALRM) error");
    alarm(10);
    if ( (n = read(STDIN_FILENO, line, MAXLINE)) < 0)
        err_sys("read error");
    alarm(0);

    write(STDOUT_FILENO, line, n);

    exit(0);
}

static void
sig_alrm(int signo)
{
   return; /* nothing to do, just return to interrupt the read */
}



  Funkce pro příklad v kapitole "Podmínky závodu"

Pomocí signálů můžeme implementovat také funkce TELL_CHILD, ... z kap. "Podmínky závodu". Popis funkcí, které jsou použity v tomto příkladu a nejsou popsány v této práci, lze nalézt např. v manuálových stránkách.

Příklad:

#include <signal.h>

static volatile sig_atomic_t    sigflag;
                              /* set nonzero by signal handler */
static sigset_t            newmask, oldmask, zeromask;

static void
sig_usr(int signo)
              /* one signal handler for SIGUSR1 and SIGUSR2 */
{
    sigflag = 1;
    return;
}

void
TELL_WAIT()
{
    if (signal(SIGUSR1, sig_usr) == SIG_ERR)
        err_sys("signal(SIGINT) error");
    if (signal(SIGUSR2, sig_usr) == SIG_ERR)
        err_sys("signal(SIGQUIT) error");

    sigemptyset(&zeromask);

    sigemptyset(&newmask);
    sigaddset(&newmask, SIGUSR1);
    sigaddset(&newmask, SIGUSR2);
    /* block SIGUSR1 and SIGUSR2, and save current signal mask */
    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        err_sys("SIG_BLOCK error");
}

void
TELL_PARENT(pid_t pid)
{
    kill(pid, SIGUSR2);        /* tell parent we're done */
}

void
WAIT_PARENT(void)
{
    while (sigflag == 0)
        sigsuspend(&zeromask);    /* and wait for parent */

    sigflag = 0;
            /* reset signal mask to original value */
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        err_sys("SIG_SETMASK error");
}
void
TELL_CHILD(pid_t pid)
{
    kill(pid, SIGUSR1);            /* tell child we're done */
}

void
WAIT_CHILD(void)
{
    while (sigflag == 0)
        sigsuspend(&zeromask);    /* and wait for child */

    sigflag = 0;
            /* reset signal mask to original value */
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        err_sys("SIG_SETMASK error");
}



Funkce abort

Tato funkce slouží k předčasnému ukončení programu. Pošle signál SIGABRT procesu.

#include <stdlib.h>
void abort (void);

Procesy by neměly signál SIGABRT ignorovat.



  Funkce sleep

Funkce sleep je v textu mnohokrát použita. Jde vlastně jen o uspání procesu na příslušnou dobu.

#include <stdlib.h>
unsigned int sleep (unsigned int seconds);
Vrací: 0 nebo počet nedospalých sekund

Funkce převede proces do stavu pozastavení (suspend) do té doby, než:



Cvičení

  1. Implementujte funkci raise.
  2. Napište následují program, který otestuje synchronizaci rodič-potomek v programu v kapitole ??. Proces vytvoří soubor a zapíše do něj číslo 0. Potom zavolá fork a rodič a potomek střídavě inkrementují počítadlo v souboru. Pokaždé, když je počítadlo zvětšeno, tiskněte, který proces to provedl (rodič nebo potomek).


další předchozí obsah
Další: Meziprocesní komunikace Předchozí: Řízení procesů

Ladislav Dobias
Sat Nov 1 15:38:32 MET 1997