Un processo è un programma in esecuzione: l'unità fondamentale di esecuzione all'interno di un sistema operativo.
Concetto di processo
Definizione: Il processo è un programma in esecuzione. È l'unità di esecuzione all'interno del SO. Solitamente ha esecuzione sequenziale (le istruzioni vengono eseguite nell'ordine specificato). Un SO multiprogrammato consente l'esecuzione concorrente di più processi.
📄
Programma
Entità passiva
File eseguibile su disco. Non fa nulla da solo: è solo una sequenza di istruzioni memorizzate. Può dar origine a più processi in esecuzione simultanea.
⚡
Processo
Entità attiva
Programma in esecuzione. Comprende lo stato corrente dell'esecuzione: codice, dati, registri, stack. È dinamico e vive nel tempo.
Componenti di un processo
Un processo è rappresentato da cinque componenti fondamentali:
💻
Codice (text)
Istruzioni del programma eseguito
🗃️
Dati
Variabili globali allocate staticamente
🎯
Program Counter
Prossima istruzione da eseguire
🔧
Registri CPU
Valori correnti dei registri
📚
Stack
Parametri e variabili locali a funzioni
Stati di un processo
Durante la sua esistenza, un processo transita tra diversi stati:
init
creazione
crea
→
ready
pronto
assegna CPU
→
running
in esecuzione
termina
→
term.
terminato
Nota: Da running si può tornare a ready (revoca CPU) o passare a waiting (attesa evento I/O). Da waiting, quando l'evento si verifica, si torna a ready. In un sistema monoprocessore, al massimo un processo è nello stato running.
02 — Thread
Dai Processi ai Thread
Un thread è un'unità di esecuzione indipendente all'interno di un processo, più leggera ed economica di un processo separato.
Cos'è un thread
Definizione: Un thread (o processo leggero, lightweight process) è un'unità di esecuzione indipendente all'interno di un processo. Condivide lo spazio di indirizzamento con gli altri thread del processo. È rappresentato da un Thread Control Block (TCB) che punta al PCB del processo.
Risorse condivise e private
I thread di uno stesso processo si dividono le risorse in condivise (del processo) e private (di ciascun thread):
program counterregistri CPUstack proprioTSD (memoria privata)
Contesto: Thread vs Processo
Contesto di un thread
Stato della computazione (registri, stack, PC), attributi di scheduling e priorità, descrittore thread (tid, priorità, segnali pendenti), memoria privata TSD.
Contesto di un processo
Tutto quello che è nel contesto di un thread, più: spazio di memoria completo, risorse private con tabelle dei descrittori.
Strategie di threading
⚡
Threading asincrono
Fork indipendente
Il genitore crea un figlio e riprende subito la propria esecuzione in concorrenza. Ogni thread è indipendente, il genitore non sa quando terminano i figli. Scarsa condivisione di dati.
Esempio: web server che gestisce richieste.
🔀
Threading sincrono
Fork-join
Il genitore crea figli e attende che tutti terminino prima di proseguire. I figli si eseguono in concorrenza. Significativa condivisione di dati. Il genitore combina i risultati.
Esempi applicativi
🖥️
Foreground/Background
Un thread gestisce l'I/O utente mentre altri elaborano in background. Es: word processor (correzione ortografica mentre si scrive).
💾
Asincrono
Operazioni asincrone implementate come thread. Es: salvataggio automatico su disco senza bloccare l'utente.
🌐
Task paralleli
Task intrinsecamente paralleli. Es: file server, HTTP server con dispatcher thread + worker thread + cache condivisa.
03 — Confronto
Vantaggi, Svantaggi & Confronto
Perché usare i thread? Quando preferire i processi? Un confronto completo per rispondere alla verifica.
Vantaggi dei thread
⏱️
Tempo di risposta
Un programma multithreaded continua la computazione anche se un thread è bloccato (es. attesa I/O). Fondamentale per applicazioni interattive.
🤝
Condivisione risorse
I thread condividono per definizione memoria e risorse del processo. I processi comunicano tramite IPC (più oneroso).
💰
Economia
30× più costoso creare un processo vs thread
5× più costoso il context switch di processo vs thread
📈
Scalabilità
Su architetture multiprocessore i thread vengono eseguiti in vero parallelismo, aumentando il grado di parallelismo del sistema.
Svantaggi dei thread
🔒
Risorse private difficili
Thread dello stesso processo possono modificare dati gestiti da altri thread. Per memoria privata servono meccanismi appositi (TSD).
⚠️
Pericolo di interferenza
La condivisione accentua il rischio di interferenza. Gli accessi concorrenti devono essere sincronizzati (thread safeness). Un bug in un thread può corrompere l'intero processo.
Tabella comparativa: Processi vs Thread
Aspetto
Processo
Thread
Creazione / Distruzione
Richiede allocazione, copia e deallocazione di grandi quantità di memoria
Richiede solo la creazione di uno stack per il thread → molto più veloce
Errore / Crash
✓ Isolato Non può danneggiare altri processi
✗ Pericoloso Può danneggiare altri thread e l'intero processo
Codice
Un processo può modificare il proprio codice (es. cambio dell'eseguibile)
Il codice è fisso nella sezione text del processo contenitore
Condivisione
Onerosa, deve essere implementata dal programmatore (IPC, shared memory)
Automatica: tutti i thread condividono la memoria del processo
Mutua esclusione
Garantita automaticamente dall'isolamento del SO
Deve essere realizzata dal programmatore (semafori, mutex, ecc.)
Prestazioni
Limitate dall'overhead di gestione
Elevate
Concorrenza
Limitata dalla difficoltà di comunicazione
Elevata
Quale scegliere? Dipende dal caso. Usa i processi quando l'isolamento è critico (es. browser multi-processo). Usa i thread quando le prestazioni e la condivisione di dati sono prioritari (es. server, applicazioni interattive).
04 — Concorrenza
Interazione tra Processi
Quando processi o thread condividono risorse, sorgono problemi di accesso concorrente e sincronizzazione.
Classificazione dei processi
🔵
Processi indipendenti
P1 e P2 sono indipendenti se l'esecuzione di P1 non è influenzata da P2 e viceversa. Sia in sequenza che in parallelo, non generano situazioni problematiche. Basta rispettare l'ordine interno di ciascuno.
🔴
Processi interagenti
P1 e P2 sono interagenti se l'esecuzione dell'uno influenza l'altro. L'interazione può essere involontaria (accesso alla stessa risorsa) o volontaria (cooperazione esplicita).
Motivazioni dell'interazione
Competizione
I processi si coordinano nell'accesso e nell'acquisizione di risorse comuni. Devono garantire che non ci siano conflitti.
Cooperazione
I processi collaborano per risolvere lo stesso problema. Es: thread dello stesso task, applicazioni client/server.
Modalità di interazione
📨
Comunicazione
Scambio di informazioni tra processi. Nel modello ad ambiente locale avviene tramite canali/porte/file (IPC). Nel modello ad ambiente globale tramite variabili condivise.
⏰
Sincronizzazione
Imposizione di vincoli temporali sull'esecuzione. Es: l'istruzione K di P1 può essere eseguita solo dopo l'istruzione J di P2.
Problemi della programmazione concorrente
Race Condition (corsa critica): situazione in cui l'ordine con cui si accede ai dati condivisi influenza il risultato finale dell'esecuzione, che dipende dalla temporizzazione con cui vengono eseguiti i processi. Deve essere assolutamente evitata.
Esempio: differente velocità di esecuzione
Considera P0 (produttore) e P1 (consumatore) che usano un buffer condiviso:
Caso 1 — P0 più veloce: P0 sovrascrive il buffer prima che P1 lo legga → perdita di dati
Caso 2 — P1 più veloce: P1 legge lo stesso dato due volte prima che P0 lo aggiorni → duplicazione dati
Problema
Causa
Soluzione
Accesso concorrente
Due processi leggono/scrivono la stessa risorsa simultaneamente
Mutua esclusione della sezione critica
Sincronizzazione
Differente velocità di esecuzione dei processi
Semafori, variabili di condizione
Deadlock
Nessun processo può proseguire (attesa circolare)
Garanzia di progresso
Starvation
Un processo attende indefinitamente
Garanzia di attesa limitata
05 — Sezione critica
Sezione Critica
La porzione di codice da proteggere dagli accessi concorrenti, con i tre requisiti fondamentali da garantire.
Definizione
Sezione critica: la parte di codice in cui un processo accede a risorse condivise con altri processi (e dove potrebbero presentarsi race condition). L'esecuzione della sezione critica deve essere mutuamente esclusiva nel tempo: un solo processo alla volta può trovarsi nella propria sezione critica.
Struttura del processo
Ogni processo che usa una sezione critica deve avere questa struttura:
do {
[sezione d'ingresso] // richiede il permesso di entraresezione critica// accesso alla risorsa condivisa
[sezione d'uscita] // segnala che ha terminato
sezione non critica // resto del codice
} while (true)
I tre requisiti fondamentali
Obbligatorio
Mutua esclusione
Se Pi è in esecuzione nella propria sezione critica, nessun altro processo Pj può trovarsi nella propria. Un solo processo per volta.
Anti-deadlock
Progresso
Se nessun processo è nella sezione critica ed alcuni vogliono entrare, la decisione su chi entra non può essere rimandata indefinitamente. Evita lo stallo.
Anti-starvation
Attesa limitata
Deve esistere un limite al numero di volte in cui si consente ad altri di entrare prima di un processo in attesa. Ogni processo entra in tempo finito.
Importante: questi tre requisiti devono essere garantiti indipendentemente dalla velocità relativa dei processi. Non si può fare nessuna ipotesi sulla velocità relativa degli n processi.
06 — Soluzioni
Soluzioni alla Mutua Esclusione
Approcci software e hardware per garantire correttamente la mutua esclusione nella sezione critica.
Soluzione software: Algoritmo di Peterson
Algoritmo di Peterson (1981): soluzione software molto più semplice dell'algoritmo di Dekker (1965). Basato sulla "gentilezza": in caso di accesso contemporaneo, entrambi i processi cedono il passo all'altro. Usa attesa attiva (busy waiting), quindi non è efficiente in termini di CPU.
Usa due variabili condivise:
VoglioEntrare[2] indica se il processo vuole entrare
Turno indica a chi spetta l'accesso
// Variabili condiviseboolean VoglioEntrare[2]; // inizialmente falseint Turno;
// ──── Processo P0 ────────────// ──── Processo P1 ────────────VoglioEntrare[0] = true; VoglioEntrare[1] = true;
Turno = 1; Turno = 0;
while (VoglioEntrare[1] while (VoglioEntrare[0]
AND Turno==1) endwhile; AND Turno==0) endwhile;
<Sezione Critica> <Sezione Critica>
VoglioEntrare[0] = false; VoglioEntrare[1] = false;
Problema dell'attesa attiva: il processo esegue continuamente il ciclo while consumando CPU inutilmente. Questo è uno spreco di risorse: il SO potrebbe sospendere il processo in attesa invece di lasciarlo girare a vuoto.
Soluzioni hardware
Il problema dell'algoritmo precedente è che la coppia di istruzioni non è atomica. Le soluzioni hardware rendono questa coppia atomica:
🔌
Disabilitazione interrupt
Disabilitare le interruzioni prima delle istruzioni atomiche, riabilitarle dopo.
Pro semplice da implementare
Contro pericoloso su sistemi multiprocessore, non si può usare in user space
🔬
Istruzioni atomiche
Istruzioni macchina che controllano e modificano un dato in una sola operazione indivisibile.
Esempio Test-and-Set, Compare-and-Swap
Pro funzionano anche su sistemi multiprocessore
Perché serve l'atomicità? Il problema nasce perché la coppia while (Occupata) endwhile; Occupata = true; non è atomica: il SO può interrompere tra le due istruzioni e permettere a un altro processo di entrare. Se fossero un'unica istruzione atomica, il problema non esisterebbe.
Strumenti del SO: semafori e mutex
🚦
Semaforo
Variabile intera con due operazioni atomiche: wait() (P) e signal() (V). Il processo si blocca se il semaforo è 0 invece di fare busy waiting. Gestito dal kernel.
🔐
Mutex
Mutual Exclusion lock: variabile booleana che indica se la sezione critica è occupata. Solo il thread che acquisisce il lock può rilasciarlo. Più semplice del semaforo.
07 — Ripasso
Flashcard di Ripasso
Clicca su ogni card per vedere la risposta. Usale per testare la tua memoria prima del quiz.
08 — Verifica
Quiz Finale
10 domande su tutti gli argomenti. Rispondi a tutte prima di controllare.