lesson_02.md 5.1 KB

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.