UNIX verzije 7 ima 15, BSD-UNIX pa 31 (oštevilčenih) signalov. Zaradi jasnosti je vsakemu signalu prirejeno simbolično ime, ki začenja s SIG (na primer SIGKILL, SIGQUIT,..). Ta imena so definirana v datoteki <signal.h>. Posebno poznan je signal številka 9, ki mu pravimo SIGKILL. Z njim lahko ukinemo "zablodeli" proces, če le poznamo številko tega procesa. Omenimo še, da sta dva signala (SIGUSR1 in SIGUSR2) direktno namenjena uporabniku in nimata kakšnega "sistemskega" pomena.
Signale generirajo dogodki na terminalu (na primer pritisk na tipko DELETE generira signal SIGINT), lahko jih povzroče aparaturne ali programske izjeme (na primer delitev z 0). Lahko jih tudi sprožimo iz komandne vrstice ali pa programsko z nekaterimi funkcijami (na primer kill( ) ).
Nekaj primerov tvorbe signala s komandne vrstice:
kill -9 125 # ukinjamo proces 125 kill -KILL 112 # ukinjamo proces 112, ukaz je ekvivalenten prejsnjemu kill -USR2 116 # procesu 116 posiljamo signal SIGUSR2Jedru lahko damo dispozicijo, kako naj odgovori na signal. Možni so 3 tipi reakcij:
![]() |
Ignoriranje signala. To lahko velja za vse signale razen za SIGKILL in SIGSTOP. Ni pa priporočljivo ignoriranje signalov, ki jih generira aparaturna oprema (delitev z 0, nelegalna inštrukcija). |
![]() |
Signal ujamemo (catch). Jedru moramo povedati, katero funkcijo naj pokliče. |
![]() |
Izvede naj se privzeta (default) akcija. Večinoma pomeni to ukinitev danega procesa. Včasih povzroči to tudi izpis pomnilnika (core dump). |
Funkcija signal( ) Je v bistvu vmesnik med mehanizmom signalov in našim programom. Njeno (precej splošno) definicijo zasledimo tudi v ANSI C. Na sistemih UNIX so lahko njeni ekvivalenti različni. Funkcija signal ( ) ima dva argumenta.. Prvi (signo) je številka signala. Drugi argument je kazalec na funkcijo, ki ji pravimo rokovalnik signalov (signal handler). To funkcijo sami napišemo.
Spodnji primer kaže definicijo rokovalnika signalov userFunc( ),
ki se bo sprožil ob sprejemu (ujetju) enega od obeh "uporabniških signalov".
Navezavo rokovalnika na oba signala smo vzpostavili s funkcijo signal(
) v funkciji main( ).
/* Program sigZgledA : primer definicije rokovalnika signalov */ /* in vzpostavitve zveze z njim s funkcijo signal */ #include <signal.h> static void userFunc (int signo) { /* isti rokovalnik za oba signala: argument je stevilka ujetega signala */ if(signo == SIGUSR1) { printf("Prejel sem signal SIGUSR1\n"); signal(SIGUSR1,userFunc); } else if (signo == SIGUSR2) { printf("Prejel sem signal SIGUSR2\n"); signal(SIGUSR2,userFunc); } } int main(void) { if (signal(SIGUSR1, userFunc) == SIG_ERR) printf("Ne morem ujeti signala SIGUSR1\n"); if (signal(SIGUSR2, userFunc) == SIG_ERR) printf("Ne morem ujeti signala SIGUSR2\n"); for (; ;) pause( ); /* smo blokirani v zanki */ } |
Tak program poženimo "v ozadju" in mu posredujmo signale z ukazom kill. Pogovor z računalnikom bi imel naslednjo obliko (pri tem podajamo na desni strani komentar posameznih vrstic):
$sigZledA &
# Prozili smo program sigZgledA v "ozadju"
[ 156]
# Racunalnik javi PID prozenega procesa
$kill -USR1 156
# Procesu s PID= 156 posljemo signal USR1
Prejel sem signal SIGUSR1
# Odgovor procesa 156, ki ostaja v ozadju ...
V nekaterih verzijah UNIX klic "našega" rokovalnika žal preklopi dispozicijo za dani signal na privzeto (default) alternativo. To pa pomeni, da bi naslednji signal naą proces verjetno ukinil. Problem rešimo tako, da v rokovalniku s ponovno uporabo funkcije signal( ) dispozicijo spet vzpostavimo tako, kot želimo.
Poudarimo še nekaj pojmov v zvezi s signali: Signal je generiran (oziroma poslan procesu), ko nastopi utrezen dogodek. Signal je sprejet (delivered), ko se sproži primerna akcija. Vmes signal visi (pending). Proces lahko blokira posredovanje signala (in ta ostane viseč). Pri nekaterih sistemih (POSIX.1) lahko taki procesi čakajo v vrsti.
Včasih si zaželimo, da bi sistemu rekli: "V danem hipu nas signali ne
zanimajo (in naj nas morda ne motijo), vendar si zapomni, da so nastopili".
To lahko naredimo z uvedbo primerne zastavice,
ki jo sprejem signala nastavi, proces bo pa na to reagiral malo kasneje.
To ponazoruje naslednji primer:
#include <signal.h> int flag=0; int sigNum; static void userFunc( ) { signal(sigNum,userFunc); /* ponovna vzpostavitev */ flag = 1; /* zastavico bo testiral glavni program */ } main( ) { sigNum = SIGINT; signal(sigNum, userFunc); for (;;) { flag = 0; /* brisanje zastavice */ printf (" cakam na signal \n"); while (flag == 0) pause( ); /* cakamo na signal */ printf("Prejel signal\n"); } } |
Tudi v tem primeru nastopa problem "časovnega okna"in s tem morebitne izgube signala med testiranjem zastavice in "spanjem" procesa. Proces je morda že testiral zastavico in ugotovil njeno vrednost 0. Če je nato prišlo do tvorbe signala pred vstopom v funkcijo pause() (tako nam lahko zagode razvrščanje procesov), bo proces zaspal za vedno, signal pa bo obvisel.
Problem prekinjenih sistemskih klicev.
Ta problem zasledimo predvsem pri "počasnih" sistemskih klicih. Imejmo proces, ki je trenutno blokiran zaradi sistemskega klica. Signal mora tak klic prekiniti (ker morda želimo prekiniti "obešen" program. To pa pomeni, da bi v našem programu morali po vsakem sistemskem klicu testirati vzrok povratka iz sistemskega klica (spremenljivko errno). Program postane zaradi dodatnih testov nepregleden. Pri sistemu 4.2BSD so v ta namen uvedli mehanizem avtomatskega restarta prekinjenih sistemskih klicev. UNIX sistem V tega mehanizma ne podpira.
Funkcija kill( ) pošilja signal procesu ali skupini procesov. Podobna je funkcija raise( ), ki omogoča procesu, da si pošlje signal sam sebi.
int kill(pid_t pid, int signo);
int raise(int signo);V prvem primeru odloča argument pid, kateri procesi dobe signal. Signal lahko pošljemo določenemu procesu (pid>0) ali skupini procesov (pid=0), ki pripadajo isti skupini kot proces, ki signal tvori. Upoštevati pa moramo, da PID pri sistemu UNIX čez nekaj časa reciklirajo.
Funkcija alarm( ) nastavi timer, ki po izteku generira signal SIGALARM. Njena splošna oblika je naslednja:
unsigned int alarm(insigned int seconds);Posredovanje signala SIGALARM bo (malo) po izteku časa, saj moramo upoštevati še delovanje razvrščevalnika. (Torej nikakor ne v realnem času).
Funkcija pause( ) suspendira proces, dokler ne pride do ulovljenja primernega signala.
S kombinacijo alarm(0) in pause( ) lahko realiziramo ekvivalent klica sleep. Pri tem lahko pride do več problemov: Kaj, če je pred prvim alarmom bil klican še kakąen? Kaj če je sistem zelo zaseden in pride do prevelikega zamika med klicem alarm( ) in klicem pause( ) (med obema velja tekmovanje (race condition)).
sigemptyset( )
Brisanje maske v celoti
sigfillset( )
Setiranje maske v celoti
sigaddset( )
Setiranje posameznih bitov v maski
sigdelset( )
Brisanje posameznih bitov v maski
sigprocmask( ) Blokiranje,
deblokiranje signalov, nastavljanje maske
sigpending( )
Ugotavljanje, kateri signali visijo
sigaction( )
Preverjanje in spreminjanje akcij, ki so prirejene danemu signalu
V nadaljevanju vidimo še primer uporabe teh funkcij:
#include <signal.h> static void sigQuit(int signo) { printf("Ujet signal SIGQUIT\n"); if (signal(SIGQUIT,SIG_DFL)==SIG_ERR) printf("Ne morem resetirati SIGQUIT\n"); return; } int main(void) { sigset_t newmask, oldmask, pendmask; /* deklaracija treh mask */ if (signal(SIGQUIT,sigQuit)==SIG_ERR) printf("Napaka:Ne morem ujeti signala SIGQUIT \n"); sigemptyset(&newmask); /* resetiranje maske v celoti */ sigaddset( &newmask, SIGQUIT); /* nastavimo bit, ustrezen signalu SIGQUIT*/ if(sigprocmask(SIG_BLOCK, &newmask, &oldmask)<0) printf("Napaka: SIG_BLOCK \n"); sleep(5); /* signal SIGQUIT bo obvisel, ce pride v casu teh 5 sekund */ if (sigpending(&pendmask) <0) printf(" Napaka: sigpending \n"); if (sigismember( &pendmask, SIGQUIT)) printf("Visi signal SIGQUIT \n"); /* setirajmo signalno masko za deblokiranje SIGQUIT */ if (sigprocmask(SIG_SETMASK, &oldmask, NULL) <0) printf(" Napaka: SIG_SETMASK\n"); printf("SIGQUIT deblokiran \n"); sleep(5); /* SIGQUIT bo koncal z datoteko core */ exit(0); } |
LINUX: signali
Primer s signali1Primer
s signali 2