I segnali
1
I segnali
Prima un po’ di teoria…...
2
Stati dei processi in UNIX
Fork
terminata
Idle
Fork
iniziata
Runnable
L’evento accade
scheduling
Sleeping
Running
Attesa di
un evento
exit
waitpid
Zombified
3
Segnali
• Sono ‘interruzioni’ software
– comunicano al processo il verificarsi di un evento
– ad ogni evento corrisponde un segnale numerato
– un processo all’arrivo di un segnale di un certo tipo
può decidere di
• ignorarlo
• lasciarlo gestire al kernel con l’azione di default definita per
quel segnale
• specificare una funzione (signal handler) che viene
mandata in esecuzione appena il segnale viene rilevato
4
Segnali (2)
• Da chi sono inviati i segnali?
– da processo all’altro
• usando la SC kill()
• solo processi del gruppo (discendenti o antenati)
– dall’utente con particolari combinazioni di tasti (al
processo in foregroud)
• Control-C corrisponde a SIGINT (ANSI)
• Control-Z corresponde a SIGTSTP
– dall’utente con l’utility kill della shell
– dal SO per a comunicare al processo il verificarsi di
particolari eventi (es. SIGFPE, errore floating-point,
SIGSEGV, segmentation fault)
5
Segnali (3)
• Lo standard POSIX stabilisce un insieme di
segnali riconosciuti in tutti i sistemi conformi
– sono interi definiti come macro in
/usr/include/bits/signum.h
– esempi:
• SIGKILL (9) : il processo viene terminato (non può essere
intercettata)
• SIGALRM (14): è passato il tempo richiesto
6
Segnali (4)
– Sono di uso comune anche segnali non POSIX:
• SIGINT (2) Control-C (ANSI)
– richiesta di terminazione da tastiera (quit)
• SIGTSTP (POSIX) Control-Z
– richiesta di sospensione da tastiera (suspend fino all’arrivo
SIGCONT)
• SIGFPE (8) (ANSI)
– si è verificato un errore Floating Point (dump)
• SIGCHLD(17) (POSIX)
– si è verificato un cambiamento di stato in un processo figlio
(ignore)
• SIGPIPE(13) (POSIX)
– la pipe è stata chiusa in lettura (quit)
• …….
7
Segnali (5)
• Strutture dati del kernel relative ai segnali
– signal handler array : descrive cosa fare quando arriva un
segnale di un certo tipo
• ignorare, trattare + puntatore al codice della funzione da eseguire
(handler)
– pending signal bitmap (signal mask): che contiene un bit per
ogni tipo di segnale
• il bit X è a 1 se c’è un segnale pendente di tipo X
– ogni processo ha un signal handler array (nella user area) ed
una pending signal bitmap (nella process table)
8
Segnali (6)
• Cosa accade quando arriva un segnale?
– il processo che lo riceve viene interrotto
– il kernel stabilisce quale comportamento adottare
controllando il contenuto del signal handler array
– se deve essere eseguito un signal handler:
• lo stato del processo interrotto viene salvato
• si esegue la funzione che tratta il segnale
• il processo riprende l’esecuzione dallo stato in cui e’ stato
interrotto
9
Stati dei processi in UNIX (2)
Fork
terminata
Idle
Runnable
Fork
iniziata
L’evento accade
scheduling
Sleeping
Running
Segnale
SIGSTOP
(CTRL Z)
Segnale
SIGCONT
Attesa di
un evento
exit
Stopped
waitpid
Zombified
10
SC per i segnali
alarm(), sigaction(),pause(),kill(),…...
11
Segnali: system call
• Come si definisce un signal handler
personalizzato?
– usando la SC sigaction()
• ci sono SC che permettono di inviare segnali
– alarm(), kill()
• ci sono SC che permettono di mettersi in attesa
dell’arrivo di un segnale
– pause()
12
Segnali di sveglia: alarm()
int alarm(unsigned int count);
– serve a implementare un timeout
– invia un segnale SIGALRM al processo che l’ha invocata
dopo count secondi
– Per default questo segnale termina il processo
–
–
se count è 0 non viene settato nessun allarme
in ogni caso tutte le richieste di allarme già settate sono
cancellate
–
restituisce (0) se non c’erano allarmi settati oppure (x>0)
se macavano x secondi allo scadere dell’ultimo allarme
settato
13
Invio di una SIGALRM
int main (void) {
alarm(3);
/* SIGALRM fra 3 secondi */
WRITELN(”Ciclo infinito ….") ;
while (1) ;
/* ciclo infinito */
WRITELN(”Pippo") ; /* mai eseguita */
return 0 ;
}
14
Esempio : eseguiamo...
cosa accade se eseguiamo il codice dell’esempio :
$ a.out
Ciclo infinito ...
-- per (circa) 3 secondi non accade niente
15
Esempio : eseguiamo…(2)
cosa accade se eseguiamo il codice dell’esempio :
$ a.out
Ciclo infinito ...
Alarm clock
-- arriva il segnale
-- processo terminato
Nota: terminazione comportamento di default
16
Personalizzare la gestione
int sigaction(int signum,
const struct sigaction* act,
struct sigaction* oldact);
– serve a definire un nuovo handler
– signum : segnale da trattare
– &act : struttura che definisce il nuovo trattamento
del segnale signum;
– &oldact : (OUTPUT) ritorna il contenuto
precedente del signal handler array (può servire per
ristabilire il comportamento precedente)
– ritorna (-1) se c’è stato errore
17
Personalizzare la gestione (2)
struct sigaction {
...
void sa_handler (int);
...
}
– sa_handler: indica come gestire il segnale può
essere:
• SIG_IGN ignora il segnale
• SIG_DFL usare la funzione di gestione di default
• puntatore alla funzione da invocare all’arrivo del segnale
– gli altri campi di solito si lasciano invariati
18
Personalizzare la gestione (3)
– SIGKILL e SIGSTP: non possono essere gestiti se
non con la procedura di default
– il figlio eredita la gestione dei segnali dal padre
– dopo la exec() le gestioni ritornano quelle di
default (ma i segnali ignorati continuano ad essere
ignorati)
– i segnali SIGCHLD sono gli unici ad essere
accumulati (stacked) negli altri casi se arriva un
segnale dello stesso tipo di uno già settato nella signal
mask viene perso
19
Personalizzare SIGALRM
void gestore (int sig) {/* numero segnale */
WRITELN (”SIGALRM catturato\n") ;
}
int main (void) {
struct sigaction s ;
……
}
20
Personalizzare SIGALRM (2)
int main (void) { …
/* inizializzo s con i valori correnti */
IFERROR(sigaction(SIGALRM,NULL,&s), \
”nella sigaction ") ;
s.sa_handler=gestore; /* nuovo gestore */
/* installo nuovo gestore */
IFERROR(sigaction(SIGALRM,&s,NULL), \
”nella sigaction ") ;
alarm(3);
/* SIGALRM fra 3 secondi */
WRITELN(”Ciclo infinito ….") ;
while (1) ;
/* ciclo infinito */
WRITELN(”Pippo") ; /* mai eseguita */
return 0 ;}
21
Esempio : eseguiamo...
cosa accade se eseguiamo il codice dell’esempio :
$ a.out
Ciclo infinito ...
-- per (circa) 3 secondi non accade niente
22
Esempio : eseguiamo…(2)
cosa accade se eseguiamo il codice dell’esempio :
$ a.out
Ciclo infinito ...
SIGALRM catturato
-- arriva il segnale
-- il processo cicla indefinitamente …
23
Attesa di segnali: pause()
int pause(void);
– sospende il processo fino all’arrivo di un segnale
– serve a implementare l’attesa passiva di un segnale
– ritorna dopo che il segnale è stato catturato ed il
gestore è stato eseguito, restituisce sempre (-1)
24
Attendere SIGALRM
/* indica se è arrivato SIGALARM (=1) o no (=0)
*/
int sigalarm_flag = 0;
void gestore (int sig) {/* numero segnale */
sigalarm_flag = 1;
}
int main (void) {
struct sigaction s ;
……
}
25
Attendere SIGALRM (2)
int main (void) {
IFERROR(sigaction(SIGALRM,NULL,&s), ”In spec");
s.sa_handler=gestore; /* nuovo gestore */
IFERROR(sigaction(SIGALRM,&s,NULL), ”In spec");
alarm(3);
/* SIGALRM fra 3 secondi */
WRITELN(”Ciclo fino a SIGALRM ….") ;
while (sigalarm_flag!= 1)
pause();
/* ciclo fino a SIGALRM */
/* serve a non sbloccarsi se arriva un altro
segnale */
WRITELN(”SIGALRM arrivato ...") ;
return 0 ;}
26
Esempio : eseguiamo...
cosa accade se eseguiamo il codice dell’esempio :
$ a.out
Ciclo fino a SIGALRM….
-- per (circa) 3 secondi non accade niente
27
Esempio : eseguiamo…(2)
cosa accade se eseguiamo il codice dell’esempio :
$ a.out
Ciclo infinito ...
SIGALRM arrivato
-- arriva il segnale
-- processo terminato
$
28
Invio di segnali: kill()
int kill(pid_t pid, int sig);
– invia un segnale di tipo sig a uno o più processi
(dipende da pid)
– il segnale è inviato solo se
• il processo che invia il segnale e chi lo riceve hanno lo
stesso owner
• il processo che invia il segnale è posseduto dal
superutente (root)
– restituisce (0) se OK (-1) se si verifica un errore
29
Invio di segnali: kill()(2)
int kill(pid_t pid, int sig);
– pid può essere
• >0 , in questo caso è il pid del processo cui si deve inviare il
segnale
• =0, in questo caso il segnale è inviato a tutti i processi del
gruppo di chi esegue la kill
• -1, in questo caso il segnale è inviato a tutti i processi
(tranne init) se il processo è di root, altrimenti come (pid=0)
• <-1, in questo caso il segnale è inviato a tutti i processi del
gruppo (-pid)
30
Esempio : una shell con timeout
/*istallazione gestore SIGCHLD*/
while (TRUE) {
/*ciclo infinito*/
flag_sigchld = 0;
type_prompt();
/* stampa prompt*/
argv = read_comm();
/*legge command line*/
IFERROR3(pid = fork(),”Nella fork”,continue);
if (pid) {/* codice padre */
sleep(1);
IFERROR3(kill(pid,SIGKILL),”In kill”, nop()),;
while(!flag_sigchld)
pause();
} else {/*codice figlio*/
IFERROR(execvp(argv[0],argv),”In execvp”); }
31
Esempio : una shell con timeout (2)
void gestore_chld (int sig) {
int pid, stato;
pid = wait(&stato);
if (WIFEXITED(stato)) /* term con exit*/
printf(”%d: Terminato con exit %d”, pid,
WEXITSTATUS(stato));
else
printf(”%d: Terminato con kill %d”, pid,
WTERMSIG(stato));
flag_sigchld = 1;
}
32
Conoscere e settare il gruppo
pid_t getpgrp(void);
int setpgid(pid_t pid, pid_t pgid);
– i figli ereditano il gruppo dal padre che non viene
modificato dalla exec()
– getpgrp: restituisce l’identificativo di gruppo del
processo che l’ha chiamata
– setpgid: assegna un gruppo (pgid) ad un
processo:
• i valori di pid possono essere vari (guardare man)
• ritorna -1 se fallisce
33
Segnali e system call
–
Nello standard POSIX specifica che se arriva un segnale
mentre una SC (es. open(), read()) è in esecuzione la SC deve
fallire con errore EINTR
•
•
–
si dovrebbe quindi testare EINTR dopo ogni SC e gestirlo
esplicitamente
è possibile settare opportuni flags durante la sigaction() in modo
da non essere interrotti
Linux per default NON interrompe le SC all’arrivo di un
segnale
•
Se vogliamo essere interrotti possiamo richiederlo esplicitamente con
una chiamata alla system call siginterrupt()
34
Mappare file in memoria
mmap(), mmunmap()
35
Allocazione dei processi nella RAM
Process A
Process B
• Spazio logico dei processi A e B e memoria fisica
• Condivisione dell’area testo
36
Mapping e condivisione di File
Two processes can share a mapped file.
Un file mappato simultaneamente in due processi
37
Mappaggio file in mamoria: mmap
void* mmap(void* start,
size_t length,
int prot, int flags,
int fd, off_t offset)
– fd: descrittore file da mappare
– offset: inizio area fd da mappare in memoria
– length: lunghezza area da mappare in memoria
– start : indirizzo logico dal quale effettuare il mapping
(tipicamente ignorato meglio passare NULL)
– prot/flags : maschere di bit che specificano condivisione e
protezione dell’area mappata
38
Mappaggio file: mmap (2)
void* mmap(void* start,
size_t length,
int prot,int flags,
int fd, off_t offset)
– il valore restituito è l’indirizzo logico (iniziale) in cui il file è
stato effettivamente mappato
– il valore restituito è MAP_FAILED se non si riesce a mappare
il file (settando errno opportunamente)
39
Mappaggio file: mmap (3)
– prot : descrive la protezione dell’area mappata, si ottiene
mettendo in OR un insieme di maschere predefinite. Es:
• PROT_WRITE : permesso di scrittura
• PROT_READ: permesso di lettura
– flags : se e con quali modalità l’area di memoria può essere
condivisa da più processi, si ottiene mettendo in OR un insieme
di maschere predefinite. Es:
• MAP_SHARED : si può condividere con tutti gli altri processi
• MAP_PRIVATE: crea una copia privata del processo, le scritture non
modificano il file
40
Mappaggio file: mmap (4)
– ATTENZIONE: length deve essere un multiplo
dell’ampiezza di pagina
– l’ampiezza della pagina (in byte) si ottiene con la fne standard
#include <unistd.h>
int getpagesize(void);
41
Mappaggio file: mmap (5)
int fd, psize, esito;
char* file; /* puntatore area mappata */
psize = getpagesize(); /* ampiezza pagina */
IFERROR(fd=open(“s.c”,O_RDWR), “aprendo s.c”);
/* esito è -1 se la mmap() e’ fallita */
esito =(file = mmap(NULL, psize, \
PROT_READ|PROT_WRITE, MAP_SHARED, \
fd, 0) == MAP_FAILED )?-1:0;
IFERROR(esito, “mappando s.c”);
/* da qua accedo al file come un array */
putchar(file[10]);
42
S-mappaggio file: munmap()
int munmap(void* start,
size_t length);
–
–
–
–
length: lunghezza area da s-mappare dalla memoria
start : indirizzo logico dal quale effettuare lo smapping
ritorna -1 se si è verificato un errore
NB: la chiusura di un file NON elimina i mapping relativi al
file che devono essere eliminati chiamando esplicitamente la
munmap()
43
Mappaggio file: esempio
...
IFERROR(fd=open(“s.c”,O_RDWR), “aprendo s.c”);
esito =(file = mmap(NULL, psize, \
PROT_READ|PROT_WRITE, MAP_SHARED, \
fd, 0) == MAP_FAILED )?-1:0;
IFERROR(esito, “mappando s.c”);
IFERROR(close(fd), “chiudendo s.c”);
/* da qua accedo al file come un array */
putchar(file[10]);
…
IFERROR(munmap(file,psize), “smappando s.c”);
44
Scarica

PPT - DISI