Introduzione ai Processi

I primi computer permettevano solo a un programma alla volta di essere eseguito. Questo programma aveva il controllo completo del sistema e aveva accesso a tutte le risorse del sistema.

Al contrario, i sistemi informatici contemporanei permettono a programmi multipli di essere caricati in memoria ed eseguiti concorrentemente. Questa evoluzione richiese un controllo più saldo e più compartimentalizzazione dei vari programmi; e questi bisogni risultarono nella nozione di processo, che è un programma in esecuzione. Un processo è l'unità di lavoro in un moderno sistema time-sharing (a condivisione del tempo).

Più complesso è il sistema operativo, più ci si aspetta che faccia molte cose per conto dei suoi utenti. Anche se la sua preoccupazione principale è l'esecuzione di programmi utente, deve anche prendersi cura di vari compiti di sistema che è meglio lasciare fuori dal kernel stesso.

Un sistema quindi consiste di una collezione di processi: processi del sistema operativo che eseguono codice di sistema e processi utente che eseguono codice utente. Potenzialmente, tutti questi processi possono eseguire concorrentemente, con la CPU (o le CPU) multiplexate tra di loro. Cambiando la CPU tra processi, il sistema operativo può rendere il computer più produttivo.

In questa lezione introduciamo il concetto di processo in un sistema operativo.

Concetti Chiave
  • Processo: Un processo è un programma in esecuzione, che include il codice del programma, il contatore di programma, i registri della CPU, lo stack del processo e la sezione dati.
  • Stati di un Processo: Un processo può trovarsi in vari stati, come pronto, in esecuzione o bloccato.
  • Blocco di Controllo del Processo (PCB): Ogni processo è rappresentato da un PCB, che contiene informazioni come lo stato del processo, il contatore di programma, i registri della CPU e le informazioni di gestione della memoria.

Il Concetto di Processo in un Sistema Operativo

Una domanda che sorge nella discussione dei sistemi operativi riguarda come chiamare tutte le attività della CPU.

Un sistema batch esegue vari job, mentre un sistema time-shared ha programmi utente, o task. Anche su un sistema single-user, un utente può essere in grado di eseguire diversi programmi contemporaneamente: un word processor, un browser Web e un pacchetto e-mail. E anche se un utente può eseguire solo un programma alla volta, come su un dispositivo embedded che non supporta il multitasking, il sistema operativo può aver bisogno di supportare le proprie attività programmate interne, come la gestione della memoria.

Sotto molti aspetti, tutte queste attività sono simili, quindi le chiamiamo tutte processi.

I termini job e processo sono usati quasi intercambiabilmente in questa guida. Sebbene preferiamo personalmente il termine processo, gran parte della teoria e terminologia dei sistemi operativi fu sviluppata durante un periodo in cui l'attività principale dei sistemi operativi era l'elaborazione di job. Sarebbe fuorviante evitare l'uso di termini comunemente accettati che includono la parola job (come job scheduling) semplicemente perché processo ha sostituito job.

Definizione di Processo

Informalmente, come menzionato in precedenza, un processo è un programma in esecuzione. Un processo è più del codice del programma, che è talvolta noto come sezione di testo (Text Section). Include anche l'attività corrente, rappresentata dal valore del contatore di programma o program counter e dal contenuto dei registri del processore. Un processo generalmente include anche lo stack del processo, che contiene dati temporanei (come parametri di funzione, indirizzi di ritorno e variabili locali), e una sezione dati (Data Section), che contiene variabili globali. Un processo può anche includere un heap, che è memoria allocata dinamicamente durante il tempo di esecuzione del processo.

La struttura di un processo in memoria è mostrata nella figura che segue:

Organizzazione di un Processo in Memoria
Figura 1: Organizzazione di un Processo in Memoria

Sottolineiamo che un programma di per sé non è un processo. Un programma è un'entità passiva, come un file contenente una lista di istruzioni memorizzate su disco (spesso chiamato file eseguibile). Al contrario, un processo è un'entità attiva, con un contatore di programma che specifica la prossima istruzione da eseguire e un insieme di risorse associate. Un programma diventa un processo quando un file eseguibile viene caricato in memoria.

Due tecniche comuni per caricare file eseguibili sono fare doppio clic su un'icona che rappresenta il file eseguibile (come ad esempio è possibile fare su Windows) e digitare il nome del file eseguibile sulla riga di comando come mostrato di seguito:

./eseguibile

Sebbene due processi possano essere associati allo stesso programma, sono comunque considerati due sequenze di esecuzione separate. Ad esempio, diversi utenti possono eseguire copie diverse del programma di posta, o lo stesso utente può invocare molte copie del programma del browser web. Ognuno di questi è un processo separato; e sebbene le sezioni di testo siano equivalenti, le sezioni dati, heap e stack variano. È anche comune avere un processo che genera molti processi mentre è in esecuzione. Su quest'ultimo punto torneremo nelle prossime lezioni.

Bisogna notare che un processo stesso può essere un ambiente di esecuzione per altro codice. L'ambiente di programmazione Java fornisce un buon esempio. Nella maggior parte delle circostanze, un programma Java eseguibile viene eseguito all'interno della macchina virtuale Java (JVM). La JVM viene eseguita come un processo che interpreta il codice Java caricato e intraprende azioni (tramite istruzioni macchina native) per conto di quel codice. Ad esempio, per eseguire il programma Java compilato Programma.class, inseriremmo

java Programma

Il comando java esegue la JVM come un processo ordinario, che a sua volta esegue il programma Java Programma nella macchina virtuale. Il concetto è lo stesso della simulazione, eccetto che il codice, invece di essere scritto per un set di istruzioni diverso, è scritto nel linguaggio Java.

Ricapitolando:

Definizione

Processo

Un Processo è un programma in esecuzione, che include il codice del programma, il contatore di programma, i registri del processore, lo stack del processo e la sezione dati. Un processo è un'entità attiva, mentre un programma è un'entità passiva.

Stati di un Processo

Mentre un processo viene eseguito, esso cambia stato. Lo stato di un processo è definito in parte dall'attività corrente di quel processo. Un processo può trovarsi in uno dei seguenti stati:

  • Nuovo. Il processo è in fase di creazione.
  • In esecuzione. Le istruzioni sono in fase di esecuzione.
  • In attesa. Il processo è in attesa che si verifichi qualche evento (come il completamento di un'operazione di I/O o la ricezione di un segnale).
  • Pronto. Il processo è in attesa di essere assegnato a un processore.
  • Terminato. Il processo ha terminato l'esecuzione.

Questi nomi sono arbitrari e variano tra i sistemi operativi. Gli stati che rappresentano si trovano su tutti i sistemi, tuttavia. Alcuni sistemi operativi delineano anche più finemente gli stati dei processi. È importante rendersi conto che solo un processo può essere in esecuzione su qualsiasi processore in qualsiasi istante. Molti processi possono essere pronti e in attesa, tuttavia.

Il diagramma di stato corrispondente a questi stati è presentato nella Figura che segue:

Stati di un Processo
Figura 2: Stati di un Processo

Process Control Block

Ogni processo è rappresentato nel sistema operativo da un blocco di controllo del processo (PCB), o Process Control Block chiamato anche blocco di controllo dell'attività. Un esempio di PCB è mostrato nella figura che segue:

Esempio di Process Control Block
Figura 3: Esempio di Process Control Block

Un PCB Contiene molte informazioni associate ad un processo specifico, incluse le seguenti:

  • Stato del processo. Lo stato può essere nuovo, pronto, in esecuzione, in attesa, fermato, e così via.
  • Contatore di programma. Il contatore indica l'indirizzo della prossima istruzione da essere eseguita per questo processo.
  • Registri della CPU. I registri variano in numero e tipo, dipendendo dall'architettura del computer. Includono accumulatori, registri indice, puntatori dello stack, e registri di uso generale, più qualsiasi informazione di codice di condizione. Insieme al contatore di programma, queste informazioni di stato devono essere salvate quando si verifica un interrupt, per permettere al processo di essere continuato correttamente dopo.
  • Informazioni di schedulazione della CPU. Queste informazioni includono una priorità del processo, puntatori alle code di schedulazione, e qualsiasi altro parametro di schedulazione. Studieremo la schedulazione della CPU in dettaglio nelle prossime lezioni.
  • Informazioni di gestione della memoria. Queste informazioni possono includere elementi come il valore dei registri base e limite e le tabelle delle pagine, o le tabelle dei segmenti, dipendendo dal sistema di memoria usato dal sistema operativo.
  • Informazioni di contabilità. Queste informazioni includono la quantità di CPU e tempo reale usato, limiti di tempo, numeri di account, numeri di lavoro o processo, e così via.
  • Informazioni di stato I/O. Queste informazioni includono la lista dei dispositivi I/O allocati al processo, una lista dei file aperti, e così via.

In breve, il PCB serve semplicemente come deposito per qualsiasi informazione che può variare da processo a processo.

Nel Diagramma seguente viene mostrato (in maniera semplificata) il cambio della CPU da processo a processo:

Esempio di Passaggio da un Processo ad un Altro
Figura 4: Esempio di Passaggio da un Processo ad un Altro

I Thread

Il modello di processo discusso finora implicitamente assume che un processo è un programma che esegue un singolo thread di esecuzione.

Ad esempio, quando un processo sta eseguendo un programma di elaborazione testi, viene eseguito un singolo thread di istruzioni. Questo singolo thread di controllo consente al processo di eseguire solo un'attività alla volta. L'utente non può simultaneamente digitare caratteri ed eseguire il controllo ortografico all'interno dello stesso processo, ad esempio.

La maggior parte dei sistemi operativi moderni ha esteso il concetto di processo per consentire a un processo di avere thread multipli di esecuzione e quindi di eseguire più di un'attività alla volta. Questa caratteristica è particolarmente vantaggiosa sui sistemi multi-core, dove thread multipli possono essere eseguiti in parallelo. Su un sistema che supporta i thread, il PCB viene espanso per includere informazioni per ogni thread. Sono necessari anche altri cambiamenti in tutto il sistema per supportare i thread. Studieremo i thread in dettaglio nelle prossime lezioni.

Esempio: Rappresentazione di un Processo in Linux

Il blocco di controllo del processo nel sistema operativo Linux è rappresentato dalla struttura C task struct, che si trova nel file di inclusione <linux/sched.h> nella directory del codice sorgente del kernel.

Questa struttura contiene tutte le informazioni necessarie per rappresentare un processo, incluso lo stato del processo, le informazioni di scheduling e gestione della memoria, la lista dei file aperti, e i puntatori al genitore del processo e una lista dei suoi figli e fratelli. Il genitore di un processo è il processo che lo ha creato; i suoi figli sono qualsiasi processo che esso crea. I suoi fratelli sono figli con lo stesso processo genitore.

Alcuni di questi campi includono:

struct task_struct {
    /* identificatore del processo */
    long pid;

    /* identificatore del processo genitore */
    long ppid;

    /* ... */

    /* stato del processo */
    long state;

    /* Informazioni di scheduling */
    struct sched entity se;

    /* Puntatore al PCB del processo genitore */
    struct task struct *parent;

    /* Lista dei PCB dei processi figli */
    struct list head children;

    /* List dei file aperti */
    struct files struct *files;

    /* Spazio degli indirizzi di questo processo */
    struct mm struct *mm;

    /* ... */
};

Ad esempio, lo stato di un processo è rappresentato dal campo long state in questa struttura. All'interno del kernel Linux, tutti i processi attivi sono rappresentati utilizzando una lista doppiamente collegata di task_struct. Il kernel mantiene un puntatore, current_task, al processo attualmente in esecuzione sul sistema, come mostrato di seguito:

Lista di task_struct usata dal kernel di Linux per memorizzare i vari PCB incluso il puntatore al processo corrente
Figura 5: Lista di task_struct usata dal kernel di Linux per memorizzare i vari PCB incluso il puntatore al processo corrente

Come illustrazione di come il kernel potrebbe manipolare uno dei campi nella struttura task_struct per un processo specificato, assumiamo che il sistema voglia cambiare lo stato del processo attualmente in esecuzione al valore new_state. Se current_task è un puntatore al processo attualmente in esecuzione, il suo stato viene cambiato con il codice seguente:

current_task->state = new_state;