# Piattaforme e Software per la rete - lezione 2 #### William Fornaciari ###### 10 March 2016 ## Processi - Concetti di base ### Esecuzione sequenziale (senza s.o.) I sistemi tradizionali adottano l'__esecuzione sequenziale__, possibile adottarla su ogni core per sistemi multicore. Questa soluzione presenta diversi vantaggi - Semplicità e facilità di debug - Determinismo della sequenza e dei tempi di esecuzione Ma anche degli svantaggi - Accesso di un solo utente alla volta - Esecuzione di un solo programma alla volta - Scarso sfruttamento delle risorse hardware ### Esecuzione Parallela (con s.o.) Parallelismo *simulato* se ho un solo processore, *reale* se ho cpu multiple Introduce alcuni problemi: - Il determinismo non è garantito automaticamente - L'ordine di esecuzione non è fissato - Conflitti di accesso alle risorse Questi problemi sono risolti dal Sistema Operativo - Scheduling, memoria virtuale, periferiche virtuali... - Gestione di processi, sincronizzazione... ### Processo Di solito i processi vengono eseguiti su un sistema operativo. Ma è possibile eseguire degli scheduler direttamente sul ferro e far eseguire agli scheduler il proprio processo (es: rtx) vedi esecuzione sequenziale #### Nota I processori per sistemi embedded (es: STM32) costano da qualche centesimo a qualche €, e dispongono di una memoria di qualche KB, quindi è utile risparmiare memoria, ad es. evitando di usare un sistema operativo. Ogni processo è identificato da un PID (Process IDentifier) univoco e viene creato da un processo *padre* Unica eccezione è il processo __init__ che è il primo ad essere eseguito ### Gerarchia di processi Nei sistemi embedded non serve il paradigma di avvio tradizionale linux init -> login -> shell -> processo Può essere lanciato direttamente il processo principale del sistema. ### Modello di memoria __Memoria virtuale__ viene mappata sulla memoria disica dal sistema operativo mediante il PD (Process Descriptor) Questo da più flessibilità perchè evito frammentazione in memoria, permette di far andare uno stesso programma su hardware diversi, di questo si occupa la MMU. Nei sistemi embedded non c'è MMU e non viene usata memoria virtuale. Memoria organizzata in __segmenti__ ### Operazioni sui processi - Creazione di un processo: - `fork()` crea una copia del processo padre con la sola eccezione del PID, il figlio condivide il codice del padre, ha la parde dati copiata ma ha un suo Process Descriptor. - Lo spreco di memoria legato alla copia della sezione dati può essere evitato con una `vfork()` o una `fork()` che implementa copy on write. - Terminazione di un processo - con la `exit(int status)` viene salvato il valore di stato nel Process Descriptor, e viene segnalata la terminazione del figlio inviando una `SIGCHLD` al padre. - Attendere la terminazione di un processo - `wait()` Se viene eseguita prima della terminazione del ciclo viene aspettata la terminazione del figlio, se viene eseguida dopo la terminazione, il figlio diventa un processo zombie. - Stato di terminazione viene passato nella parte significativa per stato normale `es: 0x2000`, nella parte meno significativa per codici di errore `es: 0x0009` è una cosa sporca ma è stata pensata tanti anni fa. - Sostituire il codice di un processo - `exec()` Segmenti TEXT e DATA sostituiti ma stesso PID e file/socket aperti rimangono. Ci sono vari tipi di exec che sono frontend di `execve()` Esempio ``` if((pid2 = fork()) == 0) execv("editor", argv); ``` - Segnalazione di eventi Un processo *orphaned* è quando il padre termina prima che termini il figlio, quando il figlio termina, viene adottato dal processo init (parent PID settato a 1 nel PD). ### Inizializzazione e terminazione di un programma La funziona `_exit()` rappresenta l'ultima operazione eseguita dalla `exit()` che invece effettua tutte le operazioni di housekeeping, che possono anche essere personalizzate utilizzando le primitive `atexit(void(*f) (void));` e se ne possono registrare fino a 32. La terminazione di un programma avviene sempre mediante un segnale - Ricevuto dal processo - Generato dal processo tramite `abort()`, che invia un segnale a se stesso per funzionare in modo analogo alla prima opzione. I segnali possono essere inviati tramite la funzione `kill()` (nome fuorviante) Il più delle volte la signal implica la chiusura del processo, a volte viene effettuato un core dump cioè un salvataggio dello stato del sistema utile per debuggare. Si può registrare un handler per una signal, altrimenti utilizza il comportamento di default, ad esempio CTRL+C agisce da signal non registrata e quindi come effetto chiude 'applicazione. La funzione `raise()` invia un segnale a processo stesso. Nel caso si riceva un segnale è possibile - Ignorare il segnale, tranne SIGKILL e SIGSTOP es:`h = signal(SIGINT,SIG_IGN );` - Effettuare una operazione specificata tramite un signal handler `sighandler_t signal(int signum, sighandler_t,...)` `alarm()` imposta un timer che alla scadenza invierà un SIGALRM al processo, può essere combinata con un handler e pause per fare un timeout o altri barbatrucchi.