I sistemi tradizionali adottano l'esecuzione sequenziale, possibile adottarla su ogni core per sistemi multicore. Questa soluzione presenta diversi vantaggi
Ma anche degli svantaggi
Parallelismo simulato se ho un solo processore, reale se ho cpu multiple Introduce alcuni problemi:
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
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
Nei sistemi embedded non serve il paradigma di avvio tradizionale linux init -> login -> shell -> processo Può essere lanciato direttamente il processo principale del sistema.
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
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.vfork()
o una fork()
che implementa copy on write.Terminazione di un processo
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.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);
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
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
h = signal(SIGINT,SIG_IGN );
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.