home - inizio - precedente - successivo | autore: Simone Sollena |
Un processo può essere definito semplicemente come un programma in
esecuzione. I primi sistemi operativi consentivano l'esecuzione di un solo
programma per volta, questo aveva il completo controllo del sistema e di tutte
le sue risorse. Un maggiore controllo sui programmi in esecuzione e sulle
risorse da parte del sistema operativo rende possibile l'esecuzione di più
programma concorrentemente, che prendono il nome di processi.
Un processo è l'unità di lavoro in un sistema, i processi si
suddividono in processi del sistema operativo, che eseguono il codice di sistema,
e processi utente, che eseguono il codice utente.
L'esecuzione di un processo avviene in modo sequenziale, ciò vuol dire
che in qualsiasi momento può essere eseguita al massimo una sola istruzione
di un processo.
Un programma non è un processo, infatti, mentre un programma, che è
un file memorizzato su disco, è un'entità passiva, un processo
è un'entità attiva, ed è costituito, oltre che dal codice
di un programma in esecuzione, anche dall'attuale valore del program counter,
dal contenuto dei registri della CPU e dalle variabili globali e temporanee
del programma in esecuzione.
Ogni processo subisce continuamente un cambiamento di stato, i possibili stati di un processo sono:
Su ciascun processore un solo processo per volta può trovarsi in stato
di running.
Quando un processo viene creato è nello stato New, passa allo
stato Ready e quando ottiene la disponibilità della CPU passa
in Running, in seguito ad un interrupt torna in Ready mentre
durante l'attesa di un'operazione di I/O o di un evento passa in Waiting
e poi in Ready. Quando l'esecuzione è terminata, da Running
passa in Terminated.
Tutte le informazioni relative ai vari processi sono memorizzate nel PCB, Process Control Block, esso contiene:
Ogni nuovo processo creato viene messo in una coda di processi, detta ready
queue; come detto precedentemente, ogni PCB contiene un puntatore al successivo
processo in coda, l'intestazione della coda contiene i puntatori al primo
ed all'ultimo PCB.
Avendo più processi in esecuzione contemporaneamente le risorse e i
dispositivi di I/O vengono condivisi, un processo potrebbe richiedere l'utilizzo
di un dispositivo di I/O mentre questo è occupato a servire un altro
processo, per far fronte a questo problema vengono create diverse code, ogni
dispositivo ha la propria coda.
La coda dei processi viene detta anche coda di scheduling, lo scheduler ha
il compito di selezionare i processi per l'esecuzione.
Nei sistemi batch abbiamo uno scheduler a lungo termine, detto anche scheduler
di job, ed uno scheduler a breve termine, detto scheduler di CPU, che ha i
compito di selezionare i processi dalla ready queue e passarli alla CPU per
l'esecuzione e viene eseguito almeno una volta ogni 100 millisecondi. Lo scheduler
a lungo termine controlla invece il grado di multiprogrammazione, ossia il
numero di processi in memoria e può essere richiamato per aggiungere
un nuovo processo nella ready queue solo quando un altro processo ha terminato
la sua esecuzione.
I processi possono essere I/O bound o CPU bound, un processo
è detto I/O bound quando dedica la maggior parte del tempo ai
dispositivi di I/O e non all'esecuzione di calcoli, è detto invece
CPU bound quando impiega la maggior parte del tempo nell'utilizzo della
CPU e poco per operazioni di I/O.
Se lo scheduler a lungo termine seleziona troppi processi CPU bound
la CPU viene continuamente utilizzata mentre le code dei dispositivi di I/O
restano vuote ed in attesa, se al contrario vengono selezionati troppi processi
I/O bound la CPU resta a lungo senza far nulla. Per ottimizzare le
prestazioni ed avere una velocizzazione del calcolo è necessario che
lo scheduler a lungo termine selezioni una combinazione equilibrata di processi
I/O bound e CPU bound.
I sistemi time sharing generalmente non hanno uno scheduler a lungo termine,
ma può essere introdotto uno scheduler a medio termine, detto anche
swapping, che permette di ridurre temporaneamente il livello di multiprogrammazione:
tramite l'operazione di swap out viene tolto un processo dalla ready queue
e salvato in una memoria di swap che generalmente è la memoria secondaria,
e può essere reintrodotto in un secondo tempo nella ready queue, in
memoria centrale, senza che la sua esecuzione debba essere ripresa da capo.
Ciò può essere fatto quando il sistema richiede più memoria
e questa è occupata dalla ready queue, o per ottimizzare il mix dei
processi.
Il salvataggio dello stato di un processo vecchio ed il caricamento dello
stato di un nuovo processo o di un processo precedentemente sospeso è
chiamato context switch, quest'operazione richiede un certo tempo,
detto overhead, perché durante questo tempo il sistema non svolge
alcun lavoro utile.
Un processo, tramite la system_call fork può creare nuovi processi,
il processo creante è detto padre mentre quelli creati vengono detti
figli.
Ogni processo ha bisogno di accedere a delle risorse, fisiche e logiche, CPU,
dispositivi di I/O, ecc. Le risorse allocate al processo figlio possono essere
condivise con il processo padre o indipendenti ed assegnate dal sistema operativo.
Quando un processo figlio viene avviato, i parametri di inizializzazione gli
possono essere passati dal padre. Il padre infine può continuare la
sua esecuzione concorrentemente con i figli o può attendere che i figli
abbiano terminato l'esecuzione.
Generalmente un processo termina quando ha eseguito l'ultima istruzione, tramite
la system_call exit a quetso punto richiede la sua cancellazione ed
i valori di uscita vengono ritornati al processo padre. In alternativa, un
processo figlio può terminare perché il padre ha richiamato
la system_call abort, ciò può avvenire per uno dei tre
motivi:
I processi possono essere indipendenti o cooperanti tra loro; si dicono indipendenti
quando l'esecuzione non influenza altri processi e non può essere influenzata
da altri processi, si dicono cooperanti quando la loro esecuzione può
influenzare altri processi e può essere influenzata da altri processi.
Nei processi cooperanti il risultato dell'esecuzione non è deterministico
in quanto, poiché dipende anche da altri processi, uno stesso input
non produce sempre uno stesso risultato.
La cooperazione tra processi permette la condivisione dei dati e accelerazione
di calcolo, infatti, in certi casi, un task può essere eseguito più
velocemente dividendolo in subtask da eseguire in parallelo su più
processori.
Un thread è definito come l'unità base di utilizzo della CPU,
è parte di un processo, i processi possono essere di tipo tradizionale
(heavyweight), che possono essere considerati come task composti da
un solo thread, o possono contenere più thread eseguiti concorrentemente
tra loro. Un task è sempre composto da almeno un thread e un singolo
thread può trovarsi in un solo task.
Più thread facenti parte di un processo condividono lo stesso codice,
lo stesso spazio indirizzabile e le risorse del sistema. Grazie alla condivisione
di questi elementi, per svolgere compiti simili l'utilizzo di più thread
in un task piuttosto che di più processi permette risparmi sull'utilizzo
delle risorse e sui tempi di esecuzione poiché la commutazione tra
i diversi thread non richiede né il salvataggio di molti dati né
il ricorso a sistema call, ed è quindi più veloce rispetto al
context switching.