Signaalit

Unix-tyyppisissä järjestelmissä prosesseille lähetetään signaaleja merkkinä erilaisten asioiden tapahtumisesta. KJ lähettää signaaleja erilaisissa virhetilanteissa, esim. jos prosessi viittaa kielletylle muistialueelle. Tälläisessä tilanteessa signaalin vastaanottava prosessi terminoidaan.

Signaaleja lähetetään myös muissa kuin virhetilanteissa. Jos terminaalissa painetaan ctrl+c, lähetetään suorittavalle prosessille signaali SIGINT joka tavallisesti terminoi prosessin. ctrl+z saa aikaan signaalin SIGSTOP joka asettaa prosessin taustalle. Taustalla oleva prosessi saadaan herätettyä esim. komennolla fg joka saa aikaan prosessin herättävän signaalin SIGCONT.

Prosessi voi myös määritellä itse sen, miten signaalin vastaanotettaessa toimitaan. Tämä tapahtuu määrittelemällä tietylle signaalille signaalikäsittelijä (engl. signal handler). Jos prosessi vastaanottaa signaalin jolla on käsittelijä, kutsuu KJ signaalin tullessa prosessin signaalinkäsittelijää. Käsittelijän koodin suorittamisen jälkeen suoritus palaa jälleen prosessin normaaliin koodiin.

Oikeastaan kaikilla prosessin signaaleilla on joku signaalin käsittelijä. Aluksi prosessin kaikilla signaaliella on oletusarvoiset signaalinäsittelijät joista suurin osa (esim. SIGINT:n käsittelijä) terminoivat prosessin. Prosessi voi tarvittaessa ylikirjoittaa oletusarvoisia signaalikäsittelijöitä.

Lista käytössä olevista signaaleista ja niiden oletusarvoisista signallinkäsittelijöistä löytyy komennolla man 7 signal, seuraavassa ote sivulta:

     Signal     Value     Action   Comment
       -------------------------------------------------------------------------
       SIGHUP        1       Term    Hangup detected on controlling terminal
                                     or death of controlling process
       SIGINT        2       Term    Interrupt from keyboard
       SIGQUIT       3       Core    Quit from keyboard
       SIGILL        4       Core    Illegal Instruction
       SIGABRT       6       Core    Abort signal from abort(3)
       SIGFPE        8       Core    Floating point exception
       SIGKILL       9       Term    Kill signal
       SIGSEGV      11       Core    Invalid memory reference
       SIGPIPE      13       Term    Broken pipe: write to pipe with no readers
       SIGALRM      14       Term    Timer signal from alarm(2)
       SIGTERM      15       Term    Termination signal
       SIGUSR1   30,10,16    Term    User-defined signal 1
       SIGUSR2   31,12,17    Term    User-defined signal 2
       SIGCHLD   20,17,18    Ign     Child stopped or terminated
       SIGCONT   19,18,25            Continue if stopped
       SIGSTOP   17,19,23    Stop    Stop process

Kohdassa Action oletusarvoisen signaalinkäsittelijän toiminta: Term ja Core tarkoitavat, että prosessi terminoidaan. Ign tarkoittaa, että signaalin tullessa ei tehdä mitään. Stop asettaa prosessin taustalle.

Signaalin lähettäminen

KJ siis lähettää signaalit prosesseille. Esim. jos painetaan terminaalissa ctrl+c, lähettää KJ terminaalissa suoritettavalle prosessille signaalin SIGINT.

Komentotulki lähettää signaalin (tai pyytää KJ:ltä signaalin lähetystä) mille tahansa prosessille käyttämällä kill-komentoa, joka toimii seuraavasti:

kill 32698		lähetetään prosessille 32698 signaali SIGTERM
kill -INT 32698		lähetetään prosessille 32698 signaali SIGINT
kill -2 32698		lähetetään prosessille 32698 signaali 2 eli SIGINT
kill -9 32698		lähetetään prosessille 32698 signaali 9 eli SIGKILL
			SIGKILL on varma tapa tappaa prosessi
Eli parametrina on signaalin numero tai nimen loppuosa (esim. -INT) sekä signaalin kohteen PID. Jos signaalin nimeä tai numeroa ei anneta, lähetetään signaali SIGTERM.

Signaali voidaan myös lähettää komennolla killall joka saa parametriksi signaalinumeron tai nimen sekä suoritettavan ohjelman nimen, esim. killall -9 a.out tappaa kaikki a.out:ia suorittavat prosessit.

Ohjelmakoodista signaalin lähettäminen tapahtuu komennolla jonka nimi on myös kill, ote man-sivulta:

NAME
       kill - send signal to a process

SYNOPSIS
       #include < sys/types.h>
       #include < signal.h>

       int kill(pid_t pid, int sig);

DESCRIPTION
       The  kill  system  call  can  be used to send any signal to any process.

       If pid is positive, then signal sig is sent to pid.
Esimerkkejä kill-komennon käytöstä pian.

Huom: nimestään huolimatta kill ei siis missään tapauksessa tarkoita prosessin tappamista, vaan signaalin lähettämistä. Usein toki signaalin lähettäminen tappaa signaalin kohteena olevan prosessin.

Signaalin käsittely

Seuraavassa otteita ohjelmasta 7-1.c, joka määrittelee signaalikäsittelijän signaalille SIGINT.
#include < stdio.h>
#include < signal.h>
#include < unistd.h>

void handleri(int sig){
  printf("vastaanotettiin signaali numero %d\n", sig);
}
Määriteltiin signaalikäsittelijä, joka on funktio, jolla on yksi int-parametri ja joka ei palauta mitään. KJ kutsuu signaalinäsittelijää siinä vaiheessa kun prosessi saa signaalin. Signaalin käsittelyn jälkeen palataan suorittamaan sihen kohtaan koodia, mihin prosessi signaalin tulohetkellä jäi.

Pääohjelmassa signaalinkäsittelijä rekisteröidään, jotta KJ osaa kutsua käsittelijää signaalin tullessa:

int main(int argc, char *argv[]){
  struct sigaction act;

  act.sa_handler = handleri;
  act.sa_flags = SA_RESETHAND;
  sigemptyset(&act.sa_mask);

  sigaction(SIGINT, &act, NULL);
Rekisteröinti tapahtuu funktiolla sigaction, jolla on kolme parametria. Ensimmäinen parametri on signaalin nimi, eli mille signaalille käsittelijä asetetaan. Toisena parametrina on osoitin struct sigaction- tietueeseen, joka määrittelee miten signaalin tullessa toimitaan. Kolmas parametri on esimerkissä NULL, jos kolmanneksi parametriksi olisi asetettu osoite struct sigactioniin, olisi funktio asettanut parametriin tiedon siitä miten signaali käsiteltiin ennen sigaction-funktion kutsua. Jos tästä tiedosta ei olla kiinnostuneita, voidaan siis kolmanneksi parametriksi laittaa NULL.

struct sigaction -tietueella on kolme kenttää:

  sa_handler	signaalin käsittelevä funktio
  sa_flags	ohjeita signaalin käsittelyyn
  sa_mask	mitä tehdään muille signaaleille signaalikäsittelyn aikana
Ensimmäinen kenttä siis on osoitin signaalikäsittelijään eli käytännössä signaalikäsittelijäfunktion nimi. Toinen kenttä saa esimerkissämme arvon SA_RESETHAND joka tarkoittaa sitä, että signaalin käsittelyn tapahtuessa palataan oletusarvoiseen signaalin käsittelytapaan. Jos halutaan, että signaalin käsittelijä jää päälle, tulee parametrille asettaa arvo 0. Kentän muista arvoista lisää myöhemmin.

Jos kesken signaalin käsittelyn aikana tulee uusi samantyyppinen signaali (eli esimerkissä SIGINT), uuden signaalin käsittelyä viivästytetään, ja käsittely tapahtuu vasta kun ensimmäinen signaali on käsitelty kokonaan. Kolmas kenttä sa_mask määrittelee sen, miten muihin signaaleihin suhtaudutaan signaalikäsittelyn aikana. Esimerkissä asetettiin kentän arvoksi tyhjä signaalimaski joka tarkoittaa sitä, että mikään muu signaali ei ole estettynä signaalikäsittelyn aikana. Eli jos signaalikäsittelyn aikana tulee joku toinen signaali, käsitellään se kesken SIGINT:n käsittelyn. Signaalimaskien muodostamiseen palaamme pian.

Huom: sigaction-funktion lisäksi signaalikäsittelijöitä voidaan määritellä myös hieman helppokäyttöisemmällä funktiolla signal. Funktion signal-käyttö sisältää useita ongelmia, eikä se ole suositeltavaa. Tällä kurssilla emme käytä funktiota signal ollenkaan.

Signaalinkäsittelijät

Esimerkkimme signaalinkäsittelijä määriteltiin seuraavasti:
void handleri(int sig){
  printf("vastaanotettiin signaali numero %d\n", sig);
}
Signaalinkäsittelijässä ei yleensä ole turvallista kutsua esim. stdio.h:ssa ja stdlib.h:ssa määriteltyjä funktioita. Ongelmia syntyy silloin, jos signaalinkäsittelijän suoritus keskeytyy saman prosessin toisen signaalinkäsittelijän suorituksen takia. Esim. printf on määritelty stdio.h:ssa, eli sitä ei tulisi käyttää signaalinkäsittelijässä. Ruudulle kirjoittamisessa parempi tapa on käyttää write-funktiota ja ohjaamalla kirjoitus tiedostokuvaajaan 0 eli näytölle:
void handleri2(int sig){
  char  viesti[80] = "vastaanotettiin signaali\n";
  write(0, viesti, strlen(viesti) );
}
Numeroarvon muuttaminen printattavaan muotoon ilman stdio.h:ta ja stdlib.h:ta on hiukan hankalaa.

Jos mahdollista, kannattaa signaalinkäsittelijässä tehdä niin vähän asioita kuin mahdollista, esim. asettaa flagi, joka kertoo signaalin tapahtuneen. Varsinaninen signaaliin liittyvä toimenpide voidaan sitten tarvittaessa tehdä muualla ohjelmakoodissa.

Seuraavassa list funktioista, joita on turvallista kutsua signaalinkäsittelijässä: _Exit() _exit() abort() accept() access() aio_error() aio_return() aio_suspend() alarm() bind() cfgetispeed() cfgetospeed() cfsetispeed() cfsetospeed() chdir() chmod() chown() clock_gettime() close() connect() creat() dup() dup2() execle() execve() fchmod() fchown() fcntl() fdatasync() fork() fpathconf() fstat() fsync() ftruncate() getegid() geteuid() getgid() getgroups() getpeername() getpgrp() getpid() getppid() getsockname() getsockopt() getuid() kill() link() listen() lseek() lstat() mkdir() mkfifo() open() pathconf() pause() pipe() poll() posix_trace_event() pselect() raise() read() readlink() recv() recvfrom() recvmsg() rename() rmdir() select() sem_post() send() sendmsg() sendto() setgid() setpgid() setsid() setsockopt() setuid() shutdown() sigaction() sigaddset() sigdelset() sigemptyset() sigfillset() sigismember() signal() sigpause() sigpending() sigprocmask() sigqueue() sigset() sigsuspend() sleep() socket() socketpair() stat() symlink() sysconf() tcdrain() tcflow() tcflush() tcgetattr() tcgetpgrp() tcsendbreak() tcsetattr() tcsetpgrp() time() timer_getoverrun() timer_gettime() timer_settime() times() umask() uname() unlink() utime() wait() waitpid() write().

Jos ohjelma käyttää vain yhtä signaalia tai signaalinkäsittelijät suoritetaan aina siten, että on varmaa, että mikään muu signaalikäsittelijä ei pääse yhtäaikaa suoritukseen, voi signaalikäsittelijöiden koodissa käyttää myös stdio.h:n ja stdlib.h:n funktioita.

Signaalin odottaminen

Komennolla pause prosessi voi odottaa seuraavan signaalin saapumista.

Seuraavassa otteita ohjelmasta 7-2.c. Ohjelma käyttää myös komentoa alarm, joka toimii seuraavasti:

NAME
       alarm - set an alarm clock for delivery of a signal

SYNOPSIS
       #include < unistd.h>

       unsigned int alarm(unsigned int seconds);

DESCRIPTION
       alarm  arranges  for a SIGALRM signal to be delivered to the process in
       seconds seconds.

       If seconds is zero, no new alarm is scheduled.

       In any event any previously set alarm is cancelled.
Pääohjelmassa toimitaan seuraavasti:
int main(int argc, char *argv[]){
  struct sigaction act;

  act.sa_handler = handleri;
  act.sa_flags = SA_RESETHAND;
  sigemptyset(&act.sa_mask);

  sigaction(SIGINT, &act, 0);
  sigaction(SIGALRM, &act, 0);

  alarm(10);
  pause();
Eli asetataan sama käsittelijäfunktio sekä SIGINT:ille että SIGALRM:ille. Tämän jälkeen kutsulla alarm(10); pyydetään signaalia SIGALRM 10 sekunnin kuluttua. Sitten kutsutaan pause(); eli ruvetaan odottamaan signaalin saapumista. Signaalikäsittelijä on määritelty seuraavasti:
void handleri(int sig){
  signaali = sig;
  if ( sig==SIGINT )
    alarm(0);
}
Ohjelmalla on globaali muuttuja int signaali, johon handleri asettaa vastaanotetun signaalin numeron. Jos signaali ei ollut alarmin tekemä, tehdään kutsu alarm(0) eli poistetaan alarm toiminnasta.

Ohjelma siis pysähtyy pause()-kutsuun niin kauaksi aikaa kunnes signaali vastaanotetaan. Tämän jälkeen tarkastetaan globaalin muuttujan (jonka arvon signaalin käsittelijä asetti) avulla kumpi signaali tapahtui:

  alarm(10);
  pause();
  if ( signaali==SIGALRM )
    printf("vastaanotettiin SIGALARM\n");
  else if ( signaali==SIGINT )
    printf("vastaanotettiin SIGINT\n");

  return 0;
}

Prosessien välinen kommunikointi signaalien avulla

Äiti ja lapsiprosessit voivat käyttää signaaleja yksinkertaiseen prosessien väliseen kommunikointiin. Kommunikointia varten kannattaa käyttää signaaleja SIGUSR1 ja SIGUSR2 sillä käyttöjärjestelmä ei käytä näitä signaaleja mihinkään. Seuraavassa otteita ohjelmasta 7-3.c, jossa äitprosessi ohjaa lapsiprosessin etenemistä signaalien avulla.

Idea ohjelmassa seuraava. Vanhempi luo lapsiprosessin. Lapsiprosessi rekisteröi seuraavat signaalikäsittelijät SIGUSR1:lle ja SIGUSR2:lle:

// signaalinkäsittelijä, joka kirjoittaa ruudulle viestin
void handleri1(int sig){
    char  viesti[] = "lapsi vastaanotti SIGUSR1:n ja aloittaa\n";
  // kirjoitetaan merkkijono tiedostokuvaajaan 0, eli näytölle
    write(0, viesti, strlen(viesti) );
}

// signaalinkäsittelijä joka saa suorittavan prosessin lopettamaan itsensä
void handleri2(int sig){
    char  viesti[] = "lapsi vastaanotti SIGUSR2:n ja lopettaa\n";
  // kirjoitetaan merkkijono tiedostokuvaajaan 0, eli näytölle
    write(0, viesti, strlen(viesti) );
    exit(0);
}
handleri1:n suorittaminen siis ei aiheuta muuta kun merkkijonon tulostamisen näytölle. handleri2 tulostaa merkkijonon ja lopettaa koodia suorittavan prosessin.

Heti käsittelijät rekisteröityään lapsi tekee pause():n eli jää odottamaan signaalia. Saatuaan signaalin lapsi jatkaa loopiin missä se tulostaa ruudulle lukuja. Lapsen koodi:

 cid = fork();
  if ( cid==0 ) {

    // lapsi rekisteröi kaksi signaalihandleria
    struct sigaction act1, act2;

    // SIGUSR1:lle handleri1 joka ei tapa lasta
    act1.sa_handler = handleri1;
    act1.sa_flags = SA_RESETHAND;
    sigemptyset(&act1.sa_mask);
    sigaction(SIGUSR1, &act1, NULL);

    // SIGUSR2:lle handleri2 joka saa lapsen lopettamaan
    act2.sa_handler = handleri2;
    act2.sa_flags = SA_RESETHAND;
    sigemptyset(&act2.sa_mask);
    sigaction(SIGUSR2, &act2, NULL);

    // odotetaan ensimmäistä signaalia
    pause();

    // lapsi aloittaa prosessoinnin, ikuinen looppi eli odotetaan, että
    // vanhempi signaloi ja lopettaa lapsen toiminnan
    int i = 0;
    while( 1 ){
      sleep(1);
      printf("%d\n",i++);
    }
  }
Vanhempi lähettää aluksi lapselle signaalin SIGUSR1, joka päästää lapsen pause():sta. Jonkun ajan kuluttua vanhempi sitten lopettaa lapsen lähettämällä SIGUSR2:n. Vanhemman koodi:
  sleep(1)                      // odotetaan hiukan...
  kill( cid, SIGUSR1 );         // annetaan lapsen jatkaa pausesta
  sleep(5);                     // odotetaan ...
  kill( cid, SIGUSR2 );         // lopetetaan lapsi

  wait(NULL);                   // wait lopetettua lasta varten

  return 0;
}
Vanhempi siis antaa lapselle luvan jatkaa suoritusta lähettämällä SIGUSR1:n. Sitten kun lasta ei enää tarvita, lähettää vanhempi signaalin SIGUSR2 joka saa aikaan sen, että lapsi terminoi itsensä.

Kyse on pikemminkin prosessien välisestä synkronoinnista, eli prosessit voivat signaalien avulla tahdistaa toistensa toimintaa. Kunnollista kommunikointia eli esim. lukujen tai merkkijonojen lähettämistä signaalien avulla ei voi tehdä. Tätä varten on olemassa muita mekanismejä, esim. putket ja jaetut muistialueet, joita käsittelemme seuraavilla viikoilla.

Advanced topic: kilpailutilanteet ja signaalimaski

Signaalien käyttö saattaa pintapuolisesti tarkallen näyttää helpolta ja sitä se onkin niin kauan kun ohjelma on yksinkertainen ja yksi prosessi voi vastaanottaa vain yhden signaalin kuten 7-1.c tai jos prosessi ei voi ikinä vastaanottaa kahta signaalia suunilleen samaan aikaan kuten esimerkissä 7-3.c.

7-3.c:ssa on kuitenkin pieni ongelma, joka on korjattu laittamalla aikuisen koodiin ennen kill-komentoja sleep(1)-komento. Mitä ongelmallista voisi tapahtua ilman sleep:iä?

Esimerkissä 7-2.c signaalit SIGINT ja SIGALRM voivat saapua ohjelmalle suunilleen yhtäaikaa, esim. siten että, SIGINT on juuri tapahtunut ja kun ollaan suorittamassa signaalikäsittelijää, tulee SIGALRM. Näin käydessä toinen signaalikäsittelijä suoritetaan ensimmäisen signaalikäsittelijän ollessa kesken. Esimerkissä 7-2.c tästä ei seuraa mitään kovin vakavaa, mutta isommissa ohjelmissa vastaavilla tilanteilla voi olla vakavia seurauksia.

Katsotaan vielä tarkemmin esimerkkiä 7-2.c. Signaalinkäsittelijä, jonka tehtävänä oli huolehtia molemmista signaaleista oli seuraava:

void handleri(int sig){
  signaali = sig;
  if ( sig==SIGINT )
    alarm(0);
}
Ideana tässä on se, että jos käsitellään SIGINT:iä, niin komennolla alarm(0) asetetaan käynnissä oleva herätyskello pois päältä, jotta signaalia SIGALRM ei enää tule. Ongelma tässä on kuitenkin se, että SIGALRM voi tulla juuri sillä hetkellä kun signaalinkäsittelijän suoritus on kesken ja alarm(0)-komentoa ei ole vielä ehditty tekemään. Tällöin SIGALRM:n aiheuttama signaalin käsittely aloitetaan kesken SIGINT:n käsittelyn ja ohjelma "menee sekaisin".

Tilanne on hyvin tyypillinen rinnakkaisuuden aiheuttama ilmiö, jota kutsutaan kilpailutilanteeksi (engl. race condition), jolla tarkoitetaan sitä, että järjestelmän oikein toimiminen riippuu tapahtumien ajoituksesta. Järjestelmä siis toimii yleensä oikein, mutta jos kaksi asiaa tapahtuu suunilleen yhtä aikaa, on vaarana se, että ohjelma toimii väärin.

Signaaleihin liittyvät kilpailutilanteet on kuitenkin mahdollista estää. Ohjelman 7-2.c viat on korjattu ohjelmassa 7-4.c josta otteita seuraavassa:

Signaalinkäsittelijän rekisteröintiin on tehty pieni lisäys:

 struct sigaction act;

  act.sa_handler = handleri;
  act.sa_flags = SA_RESETHAND;
  sigemptyset(&act.sa_mask);

  sigaddset(&act.sa_mask, SIGALRM);
  sigaddset(&act.sa_mask, SIGINT);

  sigaction(SIGINT, &act, 0);
  sigaction(SIGALRM, &act, 0);
Kuten aiemmin mainittiin, niin jos kesken signaalin käsittelyn aikana tulee uusi samantyyppinen signaali kuin mitä juuri käsitellään, uuden signaalin käsittelyä viivästytetään, ja käsittely tapahtuu vasta kun ensimmäinen signaali on käsitelty kokonaan. Ongelmatilanne saattaa syntyä jos SIGINT:n käsittelyn aikana tapahtuu SIGARLM tai päinvastoin.

struct sigaction:in kenttä sa_mask sisältää ns. signaalimaskin, joka määrittelee mitkä signaalit estetään signaalinkäsittelijän suorituksen aikana.

Ensin tehdään sigemptyset(&act.sa_mask); joka nollaa signaalimaskin. Tämän jälkeen lisätään maskiin SIGALRM ja SIGINT:

  sigaddset(&act.sa_mask, SIGALRM);
  sigaddset(&act.sa_mask, SIGINT);
Tämä saa aikaan sen, että jos signaalinkäsittelyn aikana tapahtuu SIGALRM tai SIGINT, ei niitä vastaavaa signaalinäsittelyä suoriteta ennen kun signaalinkäsittely on ohi. Tällä siis estetään yllä kuvattu kilpailutilanne, jossa signaalinkäsittely keskeytyy toisen saapuvan signaalin takia.

Ongelmaksi jää vielä se, että kun signaalinkäsittelystä palataan, suoritetaan käsittelyn aikana blokattujen signaalien käsittelijät. Ongelma on hoidettu 7-4.c:ssa muuttamalla signaalikäsittelijää seuraavasti:

void handleri(int sig){
  signaali = sig;
  if ( sig==SIGINT )
    alarm(0);

  struct sigaction act;  
  act.sa_handler = SIG_IGN;
  act.sa_flags = 0;
  sigemptyset(&act.sa_mask);

  sigaction(SIGINT, &act, 0);
  sigaction(SIGALRM, &act, 0);
}
Jos siis esim. SIGINT:n käsittelyä suorittaessa SIGALRM tapahtuu, on signaali estettynä ja signaali suoritetaan vasta signaalinkäsittelijän suorituksen jälkeen. Emme kuitenkaan halua, että signaalinkäsittelijää suoritetaan enää missään tapauksessa, ja tämän takia yllä asetetaan SIGINT:lle ja SIGALRM:lle oletushandleri SIG_IGN joka jättää signaalin huomiotta. Eli jos SIGINT tai SIGALRM vielä tapahtuu, ei tehdä mitään.

Hiukan monimutkaista, mutta valitettavasti asiat muuttuvat monimutkaisiksi heti kun rinnakkaisuus astuu kuvaan.

Signaalien estäminen signaalimaskin avulla

Edellä asetimme prosessille signaalimaskin siksi aikaa kun suoritettiin signaalinkäsittelijää. Signaalimaskin voi asettaa myös muuten, eli prosessin normaalin suorituksen ajaksi.

Periaatteena on se, että ensin rakennetaan signaalimaski tyyppiä sigmask_t olevaan muuttujaan ja tämän jälkeen asetetaan maski prosessille.

Tarkastellaan ohjelmaa 7-5.c, joka asettaa signaalimaskin:

int main(int argc, char *argv[]){
  sigset_t mask;        // muuttuja, johon rakennetaan signaalimaski

  sigfillset(&mask);
  sigdelset(&mask, SIGINT);
  // nyt mask sisältää kaikki muut signaalit paitsi SIGINT:n

  // asetetaan rakennettu signaalimaski prosessille
  sigprocmask(SIG_SETMASK, &mask, NULL);
Aluksi ohjelma muodostaa maskin, jossa on kaikki muut signaalit paitsi SIGINT. Tämä tapahtuu ensin täyttämällä muuttujassa mask oleva maski funktiolla sigfillset ja tämän jälkeen poistamalla SIGINT maskista funktiolla sigdelset. Tämän jälkeen muuttujaan mask rakennettu maski asetetaan ohjelmalle komennolla sigprocmask(SIG_SETMASK, &mask, NULL);.

Funktion toiminnan esittely man-sivulta:

SYNOPSIS
       #include < signal.h>

       int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
 
       The sigprocmask call is used to change the list  of  currently  blocked
       signals. The behaviour of the call is dependent on the value of how, as
       follows.

              SIG_BLOCK
                     The set of blocked signals is the union  of  the  current
                     set and the set argument.

              SIG_UNBLOCK
                     The  signals  in  set are removed from the current set of
                     blocked signals.  It is legal to  attempt  to  unblock  a
                     signal which is not blocked.

              SIG_SETMASK
                     The set of blocked signals is set to the argument set.

       If  oldset is non-null, the previous value of the signal mask is stored
       in oldset.
Huom: muuttujaan sigset_t mask rakennettu maski asettuu siis voimaan vasta sigprocmask()-komennon antamisen yhteydessä.

Esimerkissä signaalimaski rakennettiin käyttäen funktioita sigfillset ja sigdelset. Myös muita signaalimaskien manipulointiin tarkoitettuja funktioita on olemassa. Ote man-sivulta:

SIGSETOPS(3)               Linux Programmer's Manual              SIGSETOPS(3)

NAME
       sigemptyset, sigfillset, sigaddset, sigdelset, sigismember - POSIX sig-
       nal set operations.

SYNOPSIS
       #include < signal.h>

       int sigemptyset(sigset_t *set);

       int sigfillset(sigset_t *set);

       int sigaddset(sigset_t *set, int signum);

       int sigdelset(sigset_t *set, int signum);

       int sigismember(const sigset_t *set, int signum);

DESCRIPTION
       The sigsetops(3) functions allow the manipulation of POSIX signal sets.

       sigemptyset  initializes the signal set given by set to empty, with all
       signals excluded from the set.

       sigfillset initializes set to full, including all signals.

       sigaddset and sigdelset add and delete respectively signal signum  from
       set.

       sigismember tests whether signum is a member of set.

RETURN VALUE
       sigemptyset,  sigfillset,  sigaddset  and sigdelset return 0 on success
       and -1 on error.

       sigismember returns 1 if signum is a member of set, 0 if signum is  not
       a member, and -1 on error.
Ohjelma 7-5.c siis blokkaa muut signaalit paitsi SIGINT:n, jolle rekisteröidään seuraava käsittelijä:
void handleri(int sig){
  int i;
  sigset_t pend;

  // haetaan muuttujaan pend tieto mitkä signaalit ovat odottamassa
  sigpending(&pend);

  for ( i=1; i<18; i++ ) {
    if ( sigismember(&pend, i) )
      printf("signaali %s odottaa \n", signame[i]);
  }
  printf("\n");
}
Käsittelijässä käytetään funktiota sigpending, joka toimii seuraavasti:
       #include < signal.h>

       int sigpending(sigset_t *set);

DESCRIPTION
       The sigpending call allows the examination  of  pending  signals  (ones
       which have been raised while blocked).  The signal mask of pending sig-
       nals is stored in set.
Eli sigpending hakee sigset_t-tyyppiseen muuttujaan tiedon siitä mitä estettyjä signaaleja prosessilla on odottamassa.

for-loopissa käydään läpi signaalinumerot 1-18 ja katsotaan mitä vastaava signaali on asetettuna odottavia signaaleja kuvaavassa muuttujassa pend. Printausta varten signaalinumero mapataan signaalin nimeksi käyttäen golbaaliksi määriteltyä merkkijonotaulukkoa:

char signame[19][10] = {
  "",
  "SIGHUP",
  "SIGINT",
  "SIGQUIT",
  "SIGILL",
  "",
  "SIGABRT",
  "",
  "SIGFPE",
  "SIGKILL",
  "SIGUSR1",
  "SIGSEGV",
  "SIGUSR2",
  "SIGPIPE",
  "SIGALRM",
  "SIGTERM",
  "",
  "SIGCHLD"
};
Kyseessä siis 19 paikkainen taulukko, joka koostuu korkeintaan 10 merkkiä pitkistä merkkijonoista.

Ohjelma siis blokkaa kaikki muut signaalit paitsi SIGINT:n. Aina SIGINT:n saapuessa tulostetaan mitä blokatuista signaaleista on tapahtunut, eli odottamassa (pending). Jos joku odottava signaali otettaisiin pois prosessin signaalimaskista, siirryttäisiin heti suorittamaan odottavan prosessin signaalinkäsittelijää.