Processi
Un processo è un'entità attiva in esecuzione dotata di risorse proprie, gestita dal sistema operativo tramite un Process Control Block (PCB) che ne traccia lo stato e il contesto. Mentre nei sistemi single-core la concorrenza è simulata tramite time slicing, le architetture multicore abilitano il parallelismo reale distribuendo i thread del processo su unità di calcolo fisicamente indipendenti.
Introduzione
In questo articolo approfondiamo l'architettura dei processi all'interno dei sistemi operativi, distinguendo tra il concetto di programma statico e quello di entità attiva in esecuzione.
Che cos'è un processo?
Un processo è definito come un programma in esecuzione: un’entità attiva dotata di un contatore di programma e di un insieme di risorse associate. Si distingue dal programma, che è invece un’entità passiva, ad esempio un file memorizzato su disco.
In memoria, un processo è suddiviso in sezioni specifiche:
- Testo (Text Segment): contiene il codice eseguibile.
- Dati (Data Segment): contiene le variabili globali e statiche.
- Heap: area di memoria allocata dinamicamente durante l’esecuzione.
- Stack: memoria temporanea utilizzata per le chiamate di funzione e le variabili locali.
Nei sistemi moderni il concetto si estende ai thread., che consentono a un singolo processo di avere più percorsi di esecuzione concorrenti, facilitando il parallelismo nei sistemi multicore.
Il core e il multicore
Un core è un’unità di calcolo fisica indipendente all’interno della CPU.
Una CPU contiene tipicamente:
- Registri
- ALU (Arithmetic Logic Unit)
- Logica di controllo
- Capacità di eseguire un flusso di istruzioni indipendente
Fino ai primi anni 2000, le CPU disponevano generalmente di un solo core. Ciò significava che poteva essere eseguita una sola istruzione alla volta per ciascun ciclo di clock.
Il sistema operativo utilizzava il time slicing, una tecnica che assegna a ciascun processo un intervallo di tempo limitato di utilizzo della CPU (time slice). Ad esempio:
- Processo A per 5 ms
- Processo B per 5 ms
- Processo C per 5 ms
- Ritorno al processo A
Il cambio tra processi (context switch) avviene così rapidamente da risultare impercettibile all’utente. Tuttavia, il core esegue comunque una sola istruzione per volta: il parallelismo è simulato.
In un sistema multicore, invece, esistono più core fisici. Di conseguenza:
- Il core 1 può eseguire il thread A
- Il core 2 può eseguire il thread B
- Il core 3 può eseguire il thread C
Tutto nello stesso istante fisico. In questo caso il parallelismo è reale, non simulato.
Un processo può avere più thread. Ogni thread:
- È un percorso di esecuzione indipendente
- Possiede un proprio program counter
- Ha un proprio stack
- Condivide l’heap e le risorse del processo con gli altri thread
Ad esempio, con un processo composto da 4 thread e una CPU a 4 core, il sistema operativo può assegnare un thread a ciascun core, eseguendo il programma in parallelo su più unità fisiche.
Stato e controllo del processo
Ogni processo attraversa diversi stati durante il suo ciclo di vita: nuovo, pronto, esecuzione, attesa e terminato.
Il sistema operativo rappresenta ogni processo tramite un PCB (Process Control Block), che memorizza informazioni cruciali quali:
- Stato corrente
- PID (Process Identifier)
- Contatore di programma
- Registri della CPU
- Informazioni di memoria
Il ciclo di vita di un processo
Un processo può trovarsi nei seguenti stati:
- Nuovo (New): è stato creato ma non è ancora pronto per l’esecuzione.
- Pronto (Ready): è in memoria e attende l’assegnazione della CPU.
- Esecuzione (Running): sta utilizzando la CPU.
- Attesa (Waiting/Blocked): è in attesa di un evento esterno, tipicamente un’operazione di I/O.
- Terminato (Terminated): ha completato la propria esecuzione.
Questi stati determinano in quale struttura dati del kernel il processo viene inserito e quali operazioni possono essere effettuate su di esso.
Code di scheduling
Il sistema operativo mantiene diverse code:
- Ready Queue: contiene i processi pronti all’esecuzione.
- Waiting Queue: contiene i processi in attesa di eventi.
- Device Queue: una coda per ciascun dispositivo di I/O.
Quando un processo richiede un’operazione di I/O:
- Viene spostato nella coda del dispositivo.
- Il kernel assegna la CPU a un altro processo pronto.
- Al termine dell’I/O (tramite interrupt), il processo torna nella ready queue.
Il Process Control Block (PCB)
Il PCB è la struttura dati con cui il kernel rappresenta un processo.
Contiene:
- Stato del processo
- PID
- Program counter
- Registri della CPU
- Informazioni di scheduling (priorità, puntatori alle code)
- Informazioni di memoria (tabelle delle pagine, limiti)
- File aperti e risorse allocate
- Informazioni di accounting (tempo di CPU utilizzato)
Il PCB risiede nello spazio kernel ed è accessibile esclusivamente al sistema operativo.
Context switch
Il context switch è il meccanismo con cui il sistema operativo interrompe un processo e ne attiva un altro.
Le operazioni principali sono:
- Salvare registri e program counter nel PCB del processo corrente.
- Selezionare un processo dalla ready queue.
- Ripristinare lo stato del nuovo processo dal suo PCB.
Il context switch rappresenta un overhead inevitabile del multitasking.
Scheduling e politiche decisionali
La scelta del processo successivo dipende dall’algoritmo di scheduling:
- FCFS (First Come, First Served)
- SJF (Shortest Job First)
- Round Robin
- Priority Scheduling
Ogni algoritmo ottimizza metriche diverse:
- Tempo medio di attesa
- Tempo di risposta
- Throughput
- Equità
Non esiste una soluzione universalmente ottimale: la scelta dipende dal contesto applicativo.
Stati estesi
Nei sistemi operativi moderni esistono stati aggiuntivi.
Suspended
Indica che il processo è temporaneamente escluso dalla competizione per la CPU.
Può accadere per:
- Swapping su disco
- Sospensione amministrativa
- Politiche di gestione delle risorse
Differenza fondamentale:
- Waiting: attesa di un evento.
- Suspended: esclusione per decisione del sistema o gestione della memoria.
Zombie
Un processo zombie ha terminato l’esecuzione ma il suo PCB non è ancora stato rimosso.
Il processo:
- Rilascia le risorse.
- Conserva nel PCB il codice di uscita.
- Attende che il padre invochi
wait().
Non consuma CPU, ma occupa una voce nella tabella dei processi.
Orphan
Un processo orfano è un processo il cui padre termina prima di lui.
Nei sistemi Unix-like viene adottato dal processo init (PID 1) o da un suo equivalente, che si occuperà di raccoglierne lo stato finale.
Coordinamento nei sistemi multicore
Nei sistemi multicore:
- Più CPU eseguono in parallelo.
- Le strutture dati del kernel sono condivise.
- Le decisioni di scheduling possono avvenire simultaneamente.
Strutture condivise
- Ready queue
- Tabelle dei processi (PCB)
- Tabelle delle pagine
Accessi concorrenti possono causare condizioni di race.
Meccanismi di sincronizzazione
Il kernel utilizza:
- Spinlock
- Mutex
- Sezioni critiche
- Disabilitazione temporanea degli interrupt
per garantire coerenza.
Affinità della CPU
La CPU affinity consente di associare preferenzialmente un processo a un core specifico per:
- Migliorare la località della cache
- Ridurre il costo di migrazione tra core
La migrazione tra core comporta invalidazioni di cache e costi aggiuntivi.
Comunicazione tra processi (IPC)
I processi cooperanti utilizzano meccanismi di IPC (InterProcess Communication).
Due modelli principali:
- Memoria condivisa: comunicazione tramite una regione di memoria comune; è veloce ma richiede sincronizzazione.
- Scambio di messaggi: basato su primitive
send()ereceive(), può essere sincrono (bloccante) o asincrono (non bloccante).
Esempi di sistemi IPC e comunicazione client-server
- Pipe: canali di comunicazione, tipicamente tra processi correlati.
- Socket: identificati da indirizzo IP e porta; fondamentali per la comunicazione in rete (TCP e UDP).
- RPC (Remote Procedure Call): permettono di invocare procedure remote come se fossero locali.
- Sistemi specifici: Mach utilizza porte e messaggi; Windows impiega le ALPC (Advanced Local Procedure Call) per la comunicazione locale.