Sistemi operativi, A.A. 2011/2012 (M. Cesati) Scaletta esercitazione #11, 24.05.2012 1 Mappatura in memoria dei file 1.1 Concetti generali: area di memoria del processo che viene associata con il contenuto di un file su disco, sia in lettura che in scrittura 1.2 La chiamata di sistema mmap() +------------------------------------------------------------+ |void *mmap(void *addr, size_t length, int prot, int flags, | | int fd, off_t offset); | +------------------------------------------------------------+ 1.2.1 addr: tipicamente NULL, suggerimento sull'indirizzo iniziale dell'area di memoria 1.2.2 length: lunghezza del memory mapping (e dell'area di memoria) ATTENZIONE: non si puo' estendere un file tramite mmap! 1.2.3 prot: OR logico di PROT_EXEC, PROT_READ e/o PROT_WRITE 1.2.4 flags: in alternativa: 1.2.4.1 MAP_SHARED: i cambiamenti sono visibili ad altri processi che mappano il file e sono copiati nel file su disco 1.2.4.2 MAP_PRIVATE: i cambiamenti non sono visibili e non copiati sul file su disco flags: in OR logico 1.2.4.3 MAP_ANONYMOUS: mapping senza file associato 1.2.4.4 MAP_FIXED: "addr" non e' un suggerimento, e' l'indirizzo da utilizzare per l'area di memoria 1.2.5 fd: il descrittore del file (deve essere aperto sia in lettura che scrittura (O_RDWR) in caso di MAP_SHARED e PROT_WRITE) 1.2.6 offset: la posizione del file in cui il mapping inizia (multiplo di una pagina) 1.3 Per la lunghezza di una pagina si puo' utilizza la funzione di libreria +-----------------------------------+ |page_size = sysconf(_SC_PAGE_SIZE);| +-----------------------------------+ 1.4 Per allineare una quantita' x ad un multiplo di una lunghezza s: 4.6.1 verso il basso: x & ~(s-1) equivalente a x & (-s) 4.6.2 verso l'alto: (x+s-1) & (-s) 1.5 La chiamata di sistema munmap() +---------------------------------------+ |int munmap(void *addr, size_t length); | +---------------------------------------+ 1.6 I memory map sono ereditati dai figli dopo fork(). Un memory map sopravvive anche dopo aver chiuso il file con close(). 2 Esercizio: modificare il programma 'mandelbrot4' in modo che il file di output sia scritto in parallelo da tutti i core tramite memory mapping [mandelbrot5.c.] 3 Segnali (interruzioni software) 3.1 I segnali "tradizionali" 3.1.1 $ man 7 signal oppure $ kill -l elenca i ~31 tipi di segnali "tradizionali" 3.1.2 Un segnale e' caratterizzato da 3.1.2.1 Numero (macro): esempio #2 (SIGINT) 3.1.2.2 Azione di default: terminazione, ignorato, terminazione con generazione di file "core", stop, continue 3.1.3 Alcuni segnali tradizionali comunemente usati 3.1.3.1 SIGABRT: generato da abort(), termina con core dump 3.1.3.2 SIGALRM: un timer real-time e' scaduto 3.1.3.3 SIGBUS: particolare tipo di errore d'accesso alla memoria 3.1.3.4 SIGCONT: continua un processo stoppato 3.1.3.5 SIGINT: interruzione da tastiera (Ctrl-C) 3.1.3.6 SIGKILL: uccisione certa del processo (non puo' essere ignorato, gestito o bloccato) 3.1.3.7 SIGQUIT: quit da tastiera, termina con core dump 3.1.3.8 SIGSEGV: errore d'accesso alla memoria 3.1.3.9 SIGSTOP: stop certo del processo (non puo' essere ignorato, gestito o bloccato) 3.1.3.10 SIGTERM: segnale standard per uccidere un processo 3.1.3.11 SIGTSTP: stop da tastiera 3.1.3.12 SIGUSR1: a disposizione dell'utente 3.1.3.13 SIGUSR2: a disposizione dell'utente 4 La chiamata di sistema signal() +------------------------------------------------------+ |typedef void (*sighandler_t)(int); | |sighandler_t signal(int signum, sighandler_t handler);| +------------------------------------------------------+ 4.1 signum: il numero del segnale 4.2 handler: l'indirizzo del gestore del segnale ovvero SIG_IGN (ignora) o SIG_DFL (azione di default) 4.3 valore restituito: l'indirizzo del precedente gestore del segnale, o SIG_ERR in caso di errore 5 La chiamata di sistema kill() +-----------------------------+ |int kill(pid_t pid, int sig);| +-----------------------------+ 5.1 pid: identificatore del processo destinatario, se > 0 ogni processo del gruppo del processo invocante, se == 0 ogni processo del sistema, se == -1 tutti i processi del gruppo -pid, se < -1 5.2 sig: il segnale da inviare; se == 0, nessun segnale viene inviato ma la chiamata di sistema verifica comunque la possibilita' di inviare un segnale al/ai processo/i pid 5.3 valore restituito: 0 o -1 in caso di errore 5.4 In generale e' possibile inviare segnali solo a processi lanciati dallo stesso utente del processo che esegue kill() 6 Il comando (interno od esterno) kill 6.1 Invia un segnale ad un processo da linea comando 7 Gestione del "segmentation fault" +------------------------+ |#include <stdio.h> | |#include <stdlib.h> | |int main(void) | |{ | | char *p = NULL; | | *p = 1; | | return EXIT_SUCCESS;| |} | +------------------------+ $ ./segfault Segmentation fault 7.1 Variante per ignorare il segnale SIGSEGV +------------------------------------------------+ |#include <stdio.h> | |#include <stdlib.h> | |#include <signal.h> | |int main(void) | |{ | | char *p = NULL; | | if (signal(SIGSEGV, SIG_IGN) == SIG_ERR) { | | fprintf(stderr, "Error in signal()\n"); | | return EXIT_FAILURE; | | } | | *p = 1; | | return EXIT_SUCCESS; | |} | +------------------------------------------------+ $ ./segfault Segmentation fault 7.1.1 Non funziona perche' non si puo' ignorare direttamente un segnale generato dall'hardware 7.2 Variante per gestire il segnale SIGSEGV +-------------------------------------------------------------+ |[...] | |void sighandler(int sig) | |{ | | fprintf(stderr, "Got signal %d\n", sig); /* UNSAFE!! */ | |} | |int main(void) | |{ | | char *p = NULL; | | if (signal(SIGSEGV, sighandler) == SIG_ERR) { | | fprintf(stderr, "Error in signal()\n"); | | return EXIT_FAILURE; | | } | | *p = 1; | |[...] | +-------------------------------------------------------------+ $ ./segfault Got signal 11 Got signal 11 ... (si ripete finche' non si interrompe con Ctrl-C) 7.2.1 La gestione del segnale non ha risolto il problema dell'accesso invalido alla memoria: poiche' il page fault e' una "trap", dopo aver gestito il segnale l'istruzione stessa viene ripetuta ==========