Sistemi Operativi
0%
01 — Fondamenti

Processi & Sistema Operativo

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):

Processo single-thread
code
data
files
regs
stack
〰️
PC
stack
regs
Processo multi-thread
code
data
files
〰️
PC
stack
regs
〰️
PC
stack
regs
〰️
PC
stack
regs
Condiviso (del processo)
codice eseguibile variabili globali (dati) file aperti risorse kernel
Privato (di ogni thread)
program counter registri CPU stack proprio TSD (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

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

AspettoProcessoThread
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
ProblemaCausaSoluzione
Accesso concorrenteDue processi leggono/scrivono la stessa risorsa simultaneamenteMutua esclusione della sezione critica
SincronizzazioneDifferente velocità di esecuzione dei processiSemafori, variabili di condizione
DeadlockNessun processo può proseguire (attesa circolare)Garanzia di progresso
StarvationUn processo attende indefinitamenteGaranzia 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 entrare

        sezione 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 condivise
boolean VoglioEntrare[2];   // inizialmente false
int 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.

0
risposte
10
totale
punteggio
⭐⭐⭐
0/10
0%