Sei sulla pagina 1di 4

Object 5

1
2
3
4

Processi nei Sistemi Operativi


Tutto il software in esecuzione su un calcolatore, a volte compreso il sistema operativo stesso (o una sua parte), è
organizzato in un certo numero di processi sequenziali, chiamati semplicemente processi. Un processo è l’istanza
di un programma in esecuzione, esso contiene tutti i “valori” di quest’ultimo, compresi i valori del Program
Counter (PC) e di tutti i registri.
Ogni processo è caratterizzato da:
• Un quantitativo di CPU utilizzata;
• Un quantitativo di memoria utilizzata;
• Un certo numero di file aperti.

Sistemi mono-programmati e sistemi multi-programmati


L’introduzione dei processi permette di operare una divisione netta dei sistemi che li implementano, esistono
infatti:
• Sistemi mono-programmati;
• Sistemi multi-programmati.
Nei sistemi mono-programmati il processore è in grado di eseguire un solo processo per volta. Un processo B non
può essere avviato finché il processo A non è stato completamente eseguito e terminato. Nei sistemi multi-
programmati il processore può comunque eseguire un solo processo per volta, tuttavia esso implementa un
procedimento noto come pseudo-parallelismo. Con lo pseudo-parallelismo il processore riesce a portare avanti
contemporaneamente più processi, eseguendo per un breve periodo un frammento di ognuno (questo ovviamente
considerando un sistema mono-processore).
Un processo si dice sospeso quando viene interrotto per consentire l’esecuzione di un secondo processo. La
sospensione di un processo a favore di un altro processo avviene attraverso l’operazione di context switch. Le
operazioni di context switch sono gestite dall’algoritmo di scheduler. Analizzeremo nel dettaglio lo scheduler in
un capitolo a parte.

Evoluzione dei processi


I sistemi operativi necessitano di una procedura che gli permetta, in modo facile e veloce, la gestione dei processi,
partendo dalla loro creazione fino loro terminazione. Di seguito vediamo gli eventi che possono portare alla
creazione di un processo:
• Inizializzazione del sistema;
• Esecuzione di una chiamata di sistema per la creazione di un processo;
• Inizio di una jobs in modalità kernel;
• Intervento dell’utente.
Nel corso di questo capitolo vedremo anche quali sono le istruzioni (o syscall) responsabile della creazione di un
nuovo processo. Infine, vediamo di seguito quali sono gli eventi che possono portare alla terminazione di un
processo:
• Terminazione normale;
• Scadenza del tempo di permanenza;
• Errore durante l’esecuzione;
• Errore da memoria non disponibile;
• Errore per violazione delle protezioni.
Creazione di un processo – Syscall fork()
Nei sistemi operativi UNIX o UNIX-LIKE la chiamata della syscall fork() permette la creazione di un processo
figlio come duplicato del processo genitore.
Il processo generato dalla fork() è un processo che:
• Condivide l’area del codice del processo genitore;
• Utilizza una copia dell’area dati del genitore;
• Possiede un PID (Process ID) unico.
La fork() restituisce:
• Un valore negativo in caso di fallimento;
• Uno zero in caso di successo al processo figlio;
• Un numero positivo (che corrisponde al pid del processo figlio) al padre in caso di successo.

Caricamento di un programma – Syscall exec()


Al contrario della system call fork() la chiamata di exec() consente la creazione di un nuovo processo, con codice
e dati differenti da quelli originali. L’operazione di exec() accetta due serie di parametri a seconda della sua
implementazione:
• execl(“/folder/programma”, arg0, arg1, arg2, NULL);
• execv(“/folder/programma”, argv);
La prima variante accetta in ingresso il programma da eseguire e un insieme di parametri terminali dal simbolo
NULL. La seconda variante accetta sempre come primo ingresso il programma da eseguire e come secondo un
array di puntatori a stringhe terminate da NULL.

In conclusione
Per avviare un nuovo programma in ambiente LINUX è quindi necessario eseguire una fork() del processo
chiamante, ad esempio la shell, e in seguito caricare il programma con la chiamata di execv(). In ambiente
Windows il procedimento è un po’ diverso, esiste una sola funzione in Win32 chiamata CreateProcess() che si
occupa della creazione del processo e del caricamento del programma. La funzione CreateProcess() supporta 10
parametri diversi, che se correttamente sfruttati portano alla creazione del nuovo processo.

Stato di un processo
Un processo, durante la sua esecuzione, passa attraverso tre stati principali:
1. Esecuzione, il processo è effettivamente in esecuzione nel processore;
2. Pronto, il processo è in attesa del suo turno per adoperare il processore;
3. Attesa, il processo è in attesa di un input o di un altro evento.
Fra i vari stati sono possibili quattro transizioni diverse. Il passaggio tra lo stato di “esecuzione” e di “attesa”
avviene quando il sistema operativo scopre che il programma non può più continuare, ad esempio perché questo è
bloccato su una richiesta di input, oppure perché sta aspettando uno specifico evento. Il passaggio tra lo stato di
“attesa” e lo stato “pronto” avviene invece quando il processo riceve i dati che stava aspettando e può quindi
nuovamente essere eseguito. La transizione da “pronto” a “esecuzione” avviene quando lo scheduler (la parte del
sistema incaricata di scegliere un processo da eseguire) sceglie il processo sulla base di un certo algoritmo. Infine
il passaggio da “esecuzione” a “pronto” avviene quando lo scheduler mette in pausa un processo che poteva
continuare a essere eseguito, vale a dire un processo che non era in stato di attesa.

Gerarchie tra processi


Alcuni sistemi operativi creano una gerarchia padre-figlio quando un processo ne istanzia un altro. Un processo
UNIX può creare un figlio tramite la chiamata dell’istruzione fork(). In UNIX un processo può avere un solo
padre e infiniti figli. Quando un processo padre genera un processo figlio, viene creato un gruppo di processi.
All’interno del gruppo i processi sono collegati e possono interagire tra di loro.
Anche Windows possiede un comando simile al fork(), tuttavia in ambiente Windows non esiste la distinzione tra
processo padre e processo figlio, tutti i processi sono considerati uguali, senza una distinzione. Tuttavia quando un
processo ne genera un altro, viene creato un handle che il “padre” può usare per comunicare con il “figlio”.

Processi come spazio degli indirizzi


I computer possiedono una memoria principale predisposta al mantenimento dei programmi in esecuzione (la
RAM). Tuttavia nei sistemi che implementano la multi-programmazione questo spazio di memoria deve essere
suddiviso e condiviso tra i vari programmi in esecuzione. Per fare sì che i programmi non interferiscano tra loro e
per motivi di sicurezza, si rende necessario l’introduzione di un meccanismo di protezione. Tale sistema di
protezione è gestito dall’hardware ma è controllato dal sistema operativo. La tecnica utilizzata è chiamata spazio
degli indirizzi, un’altra astrazione fornita dal sistema operativo. Come vedremo nell’articolo dedicato alla
memoria (Gestione della memoria nei sistemi operativi) e a quello dedicato alla memoria virtuale (Memoria
virtuale nei sistemi operativi), lo spazio degli indirizzi può avere una dimensione inferiore o superiore rispetto alla
memoria fisica.
Per ora ci limitiamo a dire che a ogni processo, in fase di creazione, è assegnato uno spazio degli indirizzi
indipendente, in cui sarà poi caricato il programma da eseguire. Ovviamente il processo non è rappresentabile
unicamente con il suo spazio degli indirizzi, esso infatti è composto anche dai suoi file aperti, dai registri salvati e
da molti altri valori. In ogni caso lo spazio degli indirizzi è una componente importante di ogni processo.

Processi CPU-Bound e I/O-Bound


I processi si possono suddividere in due categorie principali:
• CPU-Bound, sono processi che alternano a lunghe computazioni delle attività di I/O poco frequenti;
• I/O-Bound, sono processi che alternano a brevi computazioni delle attività di I/O molto frequenti.
Per risparmiare tempo durante l’I/O i processi sono messi in stato di Wait o Blocked e schedulati.
Contemporaneamente viene scelto un nuovo processo da eseguire dalla lista dei Ready.

Context Switch
Il context switch è un operazione che consente al processore di mettere in attesa (sospendere o terminare) il
processo in esecuzione e selezionare un nuovo processo “pronto” da eseguire. Per svolgere il context switch, il
kernel deve poter salvare nella memoria fisica tutte le informazioni utili a ripristinare in un secondo momento il
processo. Successivamente deve caricare il nuovo processo e tutte le relative informazioni, questo permette ad
esempio di riprendere l’esecuzione del processo precedente dal punto esatto in cui era stato sospeso.
Per velocizzare questo procedimento viene creata una Tabella dei Processi contenente tutte le informazioni sui
vari processi in esecuzione.
Il context switch è una operazione di interrupt drive che viene svolta dal kernel in seguito ad alcuni eventi:
• Clock Interrupt;
• I/O Interrupt;
• Memory Fault;
• Chiamata di eccezione.
Il tempo di context switch è cruciale per le prestazioni del sistema, per questo motivo la sua implementazione è
decisiva per le prestazioni generali (parleremo di questo nell’articolo dedicato allo scheduling di processi).
Tabella dei processi
Nei sistemi UNIX e UNIX-LIKE il sistema operativo mantiene una struttura chiamata Tabella dei Processi
(Process Table) le cui voci rappresentano le istanze dei diversi processi. La tabella dei processi si trova in una
porzione della memoria inaccessibile ai programmi utente. La tabella dei processi contiene tutte le informazioni
sullo stato dei processi, in questo modo quando un processo passerà dallo stato “esecuzione”, o “attesa” allo stato
“pronto” sarà possibile ripristinare tutte le informazioni in breve tempo.

Stato di un processo (PCB)


Lo “stato del processo” è mantenuto all’interno del Process Control Block (PCB), che a sua volta è contenuto
(assieme a tutti i PCB) nella tabella dei processi. Il PCB permette di dedurre tutte le informazioni utili di un
processo, come:
• L’identificatore del processo;
• L’identificatore del processo padre;
• L’identificatore dell’utente che ha creato il processo.
Lo stato del processo è rappresentato dall’insieme di alcuni valori:
• Program Counter;
• Valore dei registri;
• Execution mode;
• Variabili di stato e flag di interrupt;
• Condition code;
• Stack Pointer
• Ecc.
All’avvio il processo provvede all’inserimento del proprio PCB all’interno della tabella dei processi. Il PCB sarà
poi rimosso dal processo stesso al momento della sua terminazione.