Sistemi operativi, A.A. 2011/2012 (M. Cesati) Scaletta esercitazione #12, 31.05.2012 1 Segnali di terminazione di un processo +-------------------------------------------------------------+ |#include | |#include | |#include | |#include | |#include | |void sighandler(int sig) | |{ | | fprintf(stderr, "Got signal %d\n", sig); /* UNSAFE!! */ | |} | |int main(void) | |{ | | printf("My PID: %llu\n", (unsigned long long) getpid()); | | if ( signal(SIGINT, sighandler) == SIG_ERR || | | signal(SIGTERM, sighandler) == SIG_ERR || | | signal(SIGQUIT, sighandler) == SIG_ERR ) { | | fprintf(stderr, "Error in signal()\n"); | | return EXIT_FAILURE; | | } | | for (;;); | | return EXIT_SUCCESS; | |} | +-------------------------------------------------------------+ $ ./intr & My PID: 1003 ^CGot signal 2 ^\Got signal 3 ^Z [1]+ Stopped ./intr $ bg [1]+ ./intr $ kill -TERM 1003 Got signal 15 $ kill -KILL 1003 [1]+ Killed ./intr 2 I gestori dei segnali 2.1 Eseguiti in modo asincrono rispetto al flusso principale del programma 2.2 Limiti dei gestori dei segnali 2.2.1 Devono essere funzioni "re-entranti" (corrette anche se invocate in modo concorrente) 2.2.2 Condizione sufficiente per essere "re-entrante": non accedere ad alcuna variabile che non sia automatica (locale e non statica) e non invocare alcuna funzione non re-entrante 2.2.3 Le funzioni della libreria stdio non sono re-entranti 2.2.4 Tutte le API che restituiscono informazioni in variabili allocate staticamente non sono re-entranti 2.2.5 E' corretto utilizzare exit() in un gestore di segnali? No perche' exit() puo' svuotare i buffer di stdio. E' corretto invece utilizzare _exit() 2.3 Regola empirica: avere gestori dei segnali estremamente semplici che registrano solo l'avvenuta occorrenza del segnale 3 Esercizio: scrivere un programma "seconds" che visualizza il numero di secondi trascorsi dalla sua invocazione 3.1 La chiamata di sistema alarm() +-----------------------------------------+ |unsigned int alarm(unsigned int seconds);| +-----------------------------------------+ 3.1.1 Dopo "seconds" secondi il processo riceve il segnale SIGALRM 3.2 Il programma: +----------------------------------------------------------------+ |#include | |#include | |#include | |#include | |#include | |unsigned long seconds = 0; | |void alarm_hndl(int sig) | |{ | | (void) sig; /* to avoid the 'unused parameter' warning */ | | alarm(1); | | ++seconds; | |} | |int main(void) | |{ | | if ( signal(SIGALRM, alarm_hndl) == SIG_ERR ) { | | fprintf(stderr, "Error in signal()\n"); | | return EXIT_FAILURE; | | } | | alarm(1); | | for (;;) | | fprintf(stderr, "Elapsed %lu seconds\r", seconds); | | return EXIT_SUCCESS; | |} | +----------------------------------------------------------------+ 3.3 Problema: questo programma consuma inutilmente il tempo di CPU 3.4 La chiamata di sistema pause() +---------------+ |int pause(void)| +---------------+ sospende il processo finche' non riceve un segnale non ignorato o bloccato 3.5 Variante del programma: +----------------------------------------------------------+ |[...] | | for (;;) { | | pause(); | | fprintf(stderr, "Elapsed %lu seconds\r", seconds);| | } | |[...] | +----------------------------------------------------------+ 4 La chiamata di sistema execve() per eseguire un nuovo programma +------------------------------------------------------+ |int execve(const char *filename, char *const argv[], | | char *const envp[]); | +------------------------------------------------------+ 4.1 filename: il percorso del file eseguibile 4.2 argv: vettore di stringhe con gli argomenti da passare al nuovo programma; argv[0] e' il percorso del programma; l'ultimo elemento e' NULL 4.3 envp: vettore di stringhe con le variabili d'ambiente da passare al nuovo programma; l'ultimo elemento e' NULL 4.4 In caso di successo execve() non ritorna; altrimenti viene restituito -1 4.5 Esempio: +----------------------------------------------------------+ |#include | |#include | |#include | |int main(void) | |{ | | char * const argv[] = { "/bin/ls", "-l", ".", NULL }; | | execve(argv[0], argv, argv+3); | | fprintf(stderr, "Cannot execute %s file\n", argv[0]); | | return EXIT_FAILURE; | |} | +----------------------------------------------------------+ 5 Variabili d'ambiente 5.1 Sono stringhe con formato convenzionale "NOME=valore" 5.2 Accessibili per mezzo del simbolo esterno +----------------------+ |extern char **environ;| +----------------------+ 5.3 Esempio: +------------------------------------+ |#include | |#include | |extern char **environ; | |int main(int argc, char **argv) | |{ | | char *p; | | while ((p = *environ++) != NULL)| | printf("%s\n", p); | | return EXIT_SUCCESS; | |} | +------------------------------------+ 6 La chiamata di sistema sigaction 6.1 Prototipo: +-----------------------------------------------------+ |int sigaction(int signum, const struct sigaction *sa,| | struct sigaction *oldsa); | +-----------------------------------------------------+ 6.1.1 signum: numero del segnale 6.1.2 sa: nuovo gestore 6.1.3 oldsa: se non nullo, vecchio gestore 6.2 La struttura sigaction: +-------------------------------------------------------+ |struct sigaction { | | void (*sa_handler)(int); | | void (*sa_sigaction)(int, siginfo_t *, void *);| | sigset_t sa_mask; | | int sa_flags; | | void (*sa_restorer)(void); | |}; | +-------------------------------------------------------+ 6.2.1 sa_mask: maschera dei segnali bloccati durante la gestione del segnale 6.2.2 sa_flags: flag vari, tra cui 6.2.2.1 SA_RESTART: le chiamate di sistema interrotte sono automaticamente riavviate, quando possibile 6.2.2.2 SA_INFO: si utilizza sa_sigaction per impostare il gestore, che riceve maggiori informazioni dal SO 6.2.2.3 SA_RESETHAND: alla ricezione del segnale reimposta l'azione di default 6.2.3 sa_handler: gestore del segnale, o SIG_DFL, o SIG_IGN 6.2.4 sa_sigaction: gestore del segnale con flag SA_INFO 6.2.2 sa_restorer: obsoleto 6.3 La maschera dei segnali di tipo sigset_t e' un tipo opaco +-------------------------------------------------+ |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);| +-------------------------------------------------+ 7 Esercizio: scrivere un programma "timeout" che riceve almeno 3 parametri: un timeout in secondi, il percorso di un file eseguibile, e gli argomenti del file eseguibile. Il programma deve eseguire il programma dato in argomento per al massimo il numero di secondi indicato, poi se non e' stato completato deve ucciderlo inviandogli il segnale SIGTERM [timeout.c] ==========