Sistemi Operativi: Gestione Risorse

Descrizione della mappa mentale

Il sistema operativo agisce come intermediario fondamentale tra l'hardware fisico e le applicazioni utente, gestendo le risorse computazionali in modo efficiente, equo e sicuro. Questa mappa analizza i meccanismi core attraverso cui il kernel alloca, monitora e protegge CPU, memoria, storage e dispositivi I/O. Comprendere questa architettura è essenziale per ottimizzare le prestazioni del sistema, diagnosticare colli di bottiglia e sviluppare software robusto. Ogni ramo esplora un dominio specifico, evidenziando algoritmi, strutture dati e compromessi progettuali (trade-off) che definiscono il comportamento moderno dei sistemi come Linux, Windows e macOS. La gestione delle risorse determina direttamente la responsività del sistema, il throughput complessivo e la stabilità in ambienti multi-utente.

Caricamento mappa
0
0
0
2 visualizzazioni

Cosa contiene questa mappa

Sistemi Operativi: Gestione Risorse

Il sistema operativo agisce come intermediario fondamentale tra l'hardware fisico e le applicazioni utente, gestendo le risorse computazionali in modo efficiente, equo e sicuro. Questa mappa analizza i meccanismi core attraverso cui il kernel alloca, monitora e protegge CPU, memoria, storage e dispositivi I/O. Comprendere questa architettura è essenziale per ottimizzare le prestazioni del sistema, diagnosticare colli di bottiglia e sviluppare software robusto. Ogni ramo esplora un dominio specifico, evidenziando algoritmi, strutture dati e compromessi progettuali (trade-off) che definiscono il comportamento moderno dei sistemi come Linux, Windows e macOS. La gestione delle risorse determina direttamente la responsività del sistema, il throughput complessivo e la stabilità in ambienti multi-utente.

Gestione della CPU

La CPU è la risorsa più preziosa del sistema. Il sistema operativo deve decidere quale processo eseguire e per quanto tempo, massimizzando l'utilizzo mentre minimizza la latenza percepita. Questo modulo gestisce lo scheduling, il switching di contesto e il bilanciamento del carico su architetture multicore. Le decisioni prese dallo scheduler influenzano direttamente l'esperienza utente e l'efficienza energetica. In ambienti server, l'obiettivo è il throughput; in desktop, la responsività; in embedded, la prevedibilità temporale. La gestione include anche la mitigazione delle interruzioni e la coordinazione tra core fisici e logici (Hyper-Threading), assicurando che nessun ciclo di clock venga sprecato inutilmente mentre code di processi attendono esecuzione.

Algoritmi di Scheduling

Gli algoritmi di scheduling definiscono la politica di selezione del prossimo processo da eseguire sulla CPU. Esistono strategie preemptive e non preemptive, ognuna con implicazioni diverse su fairness e throughput. Algoritmi come Round Robin garantiscono tempi di risposta brevi per task interattivi, mentre Shortest Job First minimizza il tempo di attesa medio ma rischia starvation per processi lunghi. La scelta dell'algoritmo dipende dal carico di lavoro: batch, interattivo o real-time. I kernel moderni usano spesso code multilivello con feedback per adattarsi dinamicamente al comportamento dei processi, promuovendo o degradando la priorità in base all'uso storico della CPU e alle esigenze di I/O.

First-Come First-Served

Il FIFO è l'algoritmo di scheduling più semplice, gestendo i processi come una coda lineare senza prelazione. Il primo processo che richiede la CPU viene servito per intero fino al completamento o blocco I/O. Sebbene equo in termini di ordine di arrivo, soffre dell'effetto convoglio: processi brevi attendono eccessivamente dietro a processi lunghi, aumentando drasticamente il tempo di attesa medio. È raramente usato nei sistemi time-sharing moderni ma rimane rilevante in sistemi batch semplici o come componente base di scheduler più complessi. La sua implementazione è trivialmente efficiente ma penalizza fortemente la responsività del sistema in carichi misti.

Round Robin Scheduling

Il Round Robin assegna a ogni processo un quanto di tempo (time quantum) fisso, tipicamente tra 10 e 100 millisecondi. Se il processo non termina entro il quanto, viene prelazionato e rimesso in coda. Questo garantisce che nessun processo monopolizzi la CPU, offrendo eccellenti tempi di risposta per applicazioni interattive. Tuttavia, un quanto troppo piccolo aumenta l'overhead di context switching, riducendo il throughput effettivo. È lo standard per sistemi time-sharing generali. La scelta della dimensione del quanto è un compromesso critico tra latenza di switching e frequenza di aggiornamento dello stato dei processi attivi nella coda di ready.

Shortest Job First

Lo SJF seleziona il processo con il tempo di burst CPU più breve successivo. È optimalmente efficiente per minimizzare il tempo di attesa medio teorico. Tuttavia, richiede la conoscenza preventiva della durata del processo, spesso impossibile da ottenere esattamente; si usano quindi tecniche di predizione esponenziale basate sullo storico. Può causare starvation per processi lunghi se arriva continuamente flusso di task brevi. Viene spesso implementato in forma preemptiva (Shortest Remaining Time First) per ambienti dove la rapidità di completamento dei task piccoli è prioritaria rispetto alla fairness assoluta per i task pesanti.

Multilevel Feedback Queue

Questo scheduler avanzato utilizza multiple code con priorità diverse e permette ai processi di migrare tra di esse. Un processo inizia in una coda ad alta priorità; se consuma tutto il suo quanto, viene degradato in una coda a priorità inferiore con quanti più lunghi. Questo favorisce i processi I/O bound e interattivi rispetto ai CPU bound. È estremamente flessibile e adattivo, capace di distinguere automaticamente tra tipi di carico senza configurazione utente. Implementato in varianti in Windows e Unix, richiede una tuning accurato dei parametri di decadimento della priorità per evitare inversioni di priorità o comportamenti controintuitivi sotto carico elevato.

Context Switching

Il context switch è il meccanismo mediante il quale la CPU passa dall'esecuzione di un processo a un altro. Richiede il salvataggio dello stato del processo uscente (registri, PC, stack pointer) e il ripristino dello stato del processo entrante. Questa operazione è puramente overhead: non produce lavoro utile ma è necessaria per il multitasking. L'efficienza del context switch impacta direttamente le prestazioni dello scheduler. Hardware moderni includono set di registri multipli per ridurre questo costo. Un eccesso di switching (thrashing di CPU) degrada il throughput. Il kernel ottimizza questo processo minimizzando le aree di memoria da salvare e sfruttando cache coerenti.

Overhead e Costi

L'overhead del context switch include il tempo CPU per salvare/ripristinare registri, aggiornare strutture dati del kernel e flushare le cache (TLB e istruzioni). In sistemi ad alte prestazioni, questo tempo può essere significativo se i switch sono troppo frequenti. Ridurre la frequenza di switching aumenta l'efficienza ma peggiora la latenza percepita. I progettisti di OS bilanciano questo trade-off regolando la granularità dello scheduler. Tecniche come il lazy saving dei registri FPU o l'uso di register windows aiutano a mitigare il costo. Monitorare il rateo di switch è fondamentale per diagnosticare sistemi lenti non limitati da I/O ma da saturazione dello scheduler.

Salvataggio Stato

Durante un switch, il kernel deve persistere lo stato architetturale del processo nello Struct Task o PCB (Process Control Block). Questo include registri generali, registri di stato, puntatori di memoria e informazioni di accounting. La correttezza di questo salvataggio è critica: un errore porta a corruzione di dati o crash immediato al ripristino. In architetture complesse, alcuni stati vengono salvati solo se usati (lazy context switch). La memoria kernel utilizzata per il PCB deve essere accessibile rapidamente. La dimensione del PCB influenza la cache locality del kernel stesso durante le operazioni di scheduling frequenti.

Ruolo dell'Interrupt

Gli interrupt hardware sono il trigger principale per i context switch preemptivi. Un timer interrupt scatta periodicamente per forzare lo scheduler a rivalutare quale processo debba eseguire. Senza interrupt, un processo malizioso o bloccato potrebbe monopolizzare la CPU indefinitamente. La gestione dell'interrupt deve essere rapida (ISR) per minimizzare la latenza. Spesso l'handling completo viene deferito a tasklet o workqueue per non bloccare troppo a lungo le interruzioni. La priorità dell'interrupt determina se può interrompere un kernel già in esecuzione, influenzando la responsività del sistema a eventi esterni critici come input di rete o disco.

Ottimizzazioni Moderne

I kernel moderni usano tecniche come il kernel preemption per ridurre la latenza, permettendo allo scheduler di interrompere anche codice kernel non critico. L'uso di CPU affinity mantiene un processo sullo stesso core per sfruttare la cache calda, riducendo i miss rate dopo un switch. Alcuni scheduler evitano switch se il processo corrente ha appena rilasciato una risorsa richiesta da un altro processo in attesa (smart scheduling). L'hardware supporta con istruzioni rapide di swap contestuale. Queste ottimizzazioni sono vitali per sistemi real-time e carichi di lavoro ad alta frequenza di transazione dove ogni microsecondo di latenza conta.

Multiprocessing Symmetrico

L'SMP permette a più CPU di eseguire processi in parallelo condividendo la stessa memoria fisica. Il sistema operativo deve gestire la concorrenza sull'accesso alle strutture dati globali dello scheduler. Il locking diventa cruciale per prevenire race condition. L'SMP scala linearmente con il numero di core solo se il software è parallelizzabile e l'overhead di sincronizzazione è basso. Il kernel deve bilanciare il carico tra i core per evitare che uno sia idle mentre un altro è saturo. Questo modello domina l'hardware moderno, dai desktop ai server, richiedendo kernel progettati nativamente per la concorrenza massiva.

Load Balancing

Il load balancing distribuisce i processi eseguibili uniformemente tra tutti i core disponibili. Può essere push (un core sovraccarico spinge task ad altri) o pull (un core idle prende task da altri). Un bilanciamento aggressivo aumenta l'overhead di migrazione e cache miss; uno troppo lento causa squilibri di prestazione. Gli scheduler moderni usano bilanciamento periodico o triggerato da eventi di idle. L'obiettivo è massimizzare l'utilizzo aggregato mantenendo la locality dei dati. Algoritmi complessi valutano il costo di migrazione rispetto al guadagno di bilanciamento prima di spostare un processo.

Affinità di Processo

L'affinità di processo vincola un thread a eseguire su un sottoinsieme specifico di core CPU. Questo migliora le prestazioni mantenendo i dati nella cache locale di quel core, riducendo la coerenza della cache tra socket. È utile per applicazioni real-time o ad alte prestazioni (HPC) che richiedono prevedibilità. Tuttavia, riduce la flessibilità dello scheduler nel bilanciare il carico. Gli amministratori possono impostare affinità manuali tramite taskset o API di sistema. Il kernel rispetta questi vincoli a meno che non sia necessario per evitare starvation completa su quei core specifici.

Spinlock vs Mutex

In SMP, la sincronizzazione richiede primitive adeguate. Gli spinlock fanno attendere un thread in loop attivo mentre attende un lock, utile per attese brevissime dove il context switch costerebbe di più. I mutex mettono il thread in sleep, liberando la CPU per altri task, ideali per attese lunghe. La scelta errata porta a spreco di cicli CPU (spinlock su attesa lunga) o latenza eccessiva (mutex su attesa brevissima). In kernel programming, gli spinlock disabilitano spesso gli interrupt sul core locale per garantire atomicità stretta durante la modifica di strutture dati condivise critiche.

Scalabilità Core

La scalabilità misura quanto le prestazioni migliorano aggiungendo core. Colli di bottiglia come lock globali nello scheduler limitano la scalabilità. I kernel moderni usano strutture dati per-core (per-CPU variables) per evitare contenzione. La scalabilità non è infinita: la memoria condivisa e il bus di sistema diventano saturi. Il NUMA (Non-Uniform Memory Access) introduce ulteriore complessità, dove l'accesso alla memoria dipende dalla prossimità fisica al core. Gestire la topologia hardware è essenziale per sfruttare sistemi con decine o centinaia di core senza degradazione delle prestazioni per contenzione.

Sistemi Real-Time

I sistemi real-time garantiscono che i task critici completino entro deadline specifiche. Si dividono in Hard (mancare la deadline è fallimento catastrofico) e Soft (degradazione prestazioni). Richiedono scheduler preemptivi con latenza massima bounded. Il kernel deve essere configurabile per minimizzare jitter. Usati in robotica, automotive e controllo industriale. La gestione delle priorità è rigida: un task ad alta priorità deve sempre preemptare uno a bassa priorità immediatamente. La prevedibilità è più importante del throughput medio. Spesso richiedono kernel dedicati o patch real-time su kernel general purpose come Linux PREEMPT_RT.

Hard vs Soft Real-Time

La distinzione fondamentale risiede nelle conseguenze del mancato rispetto della scadenza. Hard real-time richiede garanzia deterministica assoluta (es. airbag, controllo volo). Soft real-time tollera occasionali miss con degradazione qualità (es. streaming video, audio live). I sistemi operativi general-purpose sono tipicamente soft real-time. Per hard real-time, si usano RTOS dedicati con overhead minimo e analisi di worst-case execution time (WCET). La progettazione del software deve evitare allocazioni dinamiche imprevedibili o path di codice non deterministici che potrebbero violare i vincoli temporali critici del sistema.

Deadline Scheduling

Algoritmi come Earliest Deadline First (EDF) assegnano priorità dinamiche basate sulla prossimità della scadenza. Il task con la deadline più imminente esegue per primo. È optimalmente efficiente per sistemi real-time dinamici. Richiede che i task dichiarino il loro tempo di esecuzione e deadline all'arrivo. Se il carico supera la capacità CPU, le deadline vengono mancate in modo prevedibile. L'implementazione nel kernel richiede timer ad alta risoluzione e overhead di scheduling molto basso. È preferibile al Rate Monotonic Scheduling quando le deadline non coincidono con i periodi di attivazione dei task.

Preemptive Kernel

Un kernel preemptive permette allo scheduler di interrompere processi in esecuzione anche mentre sono in kernel mode (spazio kernel). Questo riduce drasticamente la latenza di risposta agli interrupt e ai task ad alta priorità. Senza preemption kernel, un system call lunga potrebbe bloccare tutto il sistema real-time. Richiede attenzione nella sincronizzazione delle strutture dati kernel per evitare corruzione. Linux ha introdotto la preemption completa progressivamente. È essenziale per trasformare un OS general-purpose in una piattaforma capace di supportare carichi di lavoro time-sensitive con jitter minimo.

Latenza Massima

La latenza massima (worst-case latency) è il tempo più lungo possibile tra un evento trigger e l'inizio dell'esecuzione del handler. In real-time, questo valore deve essere conosciuto e bounded. Include tempo interrupt, scheduling, e disabling interrupt. Tecniche come threading degli interrupt e riduzione delle sezioni critiche critiche aiutano a abbassare questo tetto. Strumenti di analisi misurano il jitter nel tempo. Garantire una latenza massima bassa spesso richiede sacrifici sul throughput globale, disabilitando feature del kernel non deterministiche come la gestione dinamica della frequenza CPU o certi meccanismi di caching aggressivi.

Gestione Energia

Nei dispositivi mobili e datacenter, l'efficienza energetica è cruciale. Il OS gestisce stati di idle della CPU (C-states) e frequenze dinamiche (P-states). L'obiettivo è completare il lavoro velocemente per tornare in idle (race-to-idle) o ridurre la frequenza per risparmiare potenza (power-saving). Il governor della CPU decide la strategia. Un'errata gestione porta a surriscaldamento o batteria scarica. Il scheduler deve essere aware dell'energia, raggruppando task per permettere ad alcuni core di spegnersi completamente. Il trade-off è sempre tra prestazioni immediate e consumo energetico cumulativo nel tempo.

Dynamic Frequency Scaling

Tecnica come DVFS (Dynamic Voltage and Frequency Scaling) ajusta voltaggio e frequenza della CPU in base al carico. Se il carico è basso, la frequenza scende riducendo consumo quadraticamente. Se alto, sale per massimizzare prestazioni. Il kernel usa governor (ondemand, performance, powersave) per decidere. Il passaggio di frequenza introduce latenza. Un scaling troppo aggressivo causa stuttering nelle applicazioni; troppo lento spreca energia. I processori moderni gestiscono questo in hardware (Turbo Boost) ma il OS fornisce hint sul carico atteso. È fondamentale per bilanciare termica e prestazioni sostenute.

Idle States (C-States)

Gli C-states definiscono livelli di risparmio energetico quando la CPU è idle. C0 è attivo; C1 stop clock; C2+ spengono parti più profonde della CPU (cache, core). Entrare in stati profondi risparmia più energia ma richiede più tempo e energia per il wakeup (latenza). Il scheduler deve prevedere la durata dell'idle per scegliere lo stato ottimale. Se l'idle è brevissimo, entrare in C3 è controproducente. Il kernel usa timer broadcast per svegliare tutti i core simultaneamente e permettere agli altri di rimanere asleep più a lungo, massimizzando l'efficienza energetica globale del package processore.

Trade-off Performance

Ottimizzare per energia spesso riduce le prestazioni di picco. Un sistema configurato per risparmiare batteria risponderà più lentamente ai burst di carico. In server, si cerca il miglior performance-per-watt. Il OS deve adattarsi al contesto: un laptop staccato dalla corrente usa policy aggressive di risparmio; collegato usa policy performance. Gli utenti possono forzare profili specifici. Il compromesso implica accettare latenze maggiori in cambio di autonomia. Algoritmi di scheduling energy-aware tentano di minimizzare l'energia per task completato, non solo il tempo di esecuzione, spostando task su core più efficienti (big.LITTLE).

Thermal Throttling

Quando la temperatura supera soglie di sicurezza, l'hardware o il kernel riducono forzatamente le prestazioni (throttling) per prevenire danni. Questo è un meccanismo di protezione critica. Il OS monitora sensori termici e agisce prima che l'hardware intervenga brutalmente. Il throttling causa cali improvvisi di prestazioni percepiti dall'utente. Una buona gestione termica software previene il throttling distribuendo il carico termico o limitando proattivamente la frequenza. In datacenter, il thermal throttling impatta i SLA. Il cooling system e la gestione potenza software devono lavorare in concerto per mantenere frequenze elevate sostenute.

Metriche Prestazionali

Valutare l'efficienza della gestione CPU richiede metriche oggettive. Throughput (task completati per tempo), turnaround time (tempo totale submission a completamento), waiting time (tempo in coda ready) e response time (tempo prima della prima risposta). Queste metriche guidano il tuning dello scheduler. Diverse applicazioni privilegiano metriche diverse: batch vuole throughput, interattivo vuole response time. Monitorare queste metriche permette di identificare colli di bottiglia. Strumenti come top, perf o trace del kernel forniscono questi dati. L'ottimizzazione del sistema si basa sul miglioramento di queste KPI specifiche per il carico di lavoro target.

Throughput Sistema

Il throughput misura il numero di processi completati per unità di tempo. È la metrica primaria per sistemi batch e server ad alto carico. Massimizzare il throughput spesso significa minimizzare l'overhead di context switch e favorire processi lunghi. Tuttavia, aumentare il throughput può peggiorare il tempo di risposta per singoli utenti. Un sistema con alto throughput ma alta latenza è inefficiente per uso interattivo. Lo scheduler bilancia questo cercando di tenere la CPU sempre occupata con lavoro utile, riducendo i tempi morti di idle non pianificati e ottimizzando l'uso delle cache per ridurre i miss che stallano l'esecuzione.

Tempo di Attesa

Il tempo di attesa è la somma dei periodi passati dal processo nella coda dei pronti. È un indicatore di congestione del sistema. Un tempo di attesa alto indica che la CPU è sovraccarica rispetto alla domanda. Algoritmi come SJF minimizzano questa metrica globalmente. Per l'utente, un tempo di attesa elevato si traduce in lentezza percepita nell'avvio delle applicazioni. Ridurlo richiede aumentare la priorità di processi interattivi o aggiungere capacità computazionale. Monitorare la distribuzione dei tempi di attesa aiuta a capire se il problema è generalizzato o limitato a specifiche classi di processi a bassa priorità.

Tempo di Risposta

Il tempo di risposta è l'intervallo tra la submission di una richiesta e la produzione della prima risposta (non il completamento totale). Cruciale per sistemi interattivi e time-sharing. L'utente percepisce il sistema come 'veloce' se questo tempo è basso, anche se il task totale dura molto. Lo scheduler Round Robin è ottimizzato per questa metrica. Ridurre il tempo di risposta spesso implica prelazione frequente, che può ridurre il throughput. È il KPI principale per desktop e mobile. Tecniche come boosting di priorità per processi che attendono input utente migliorano drasticamente questa metrica percepita.

Utilizzo CPU

La percentuale di tempo in cui la CPU è occupata a eseguire istruzioni utili (user + system) vs idle. Un utilizzo del 100% non è sempre buono: potrebbe indicare starvation di I/O o loop infiniti. Un utilizzo troppo basso indica risorse sprecate. L'obiettivo è un utilizzo alto ma con coda di ready gestibile. Distinguere tra user time (applicazione) e system time (kernel) aiuta a diagnosticare se il collo di bottiglia è nel codice applicativo o nelle system call overhead. Un alto system time può indicare eccessivo context switching o driver inefficienti che consumano cicli CPU in kernel mode.

Gestione della Memoria

La memoria RAM è una risorsa finita e critica. Il sistema operativo deve allocare spazio per processi, kernel e cache, garantendo isolamento e protezione. La memoria virtuale astrae la RAM fisica, permettendo di eseguire programmi più grandi della memoria disponibile usando il disco come estensione. La gestione include paging, segmentation, allocazione dinamica e protezione. Errori qui causano crash, leak o vulnerabilità di sicurezza. L'efficienza della gestione memoria impatta le prestazioni più di qualsiasi altro sottosistema a causa della gerarchia di memoria (cache, RAM, disco). Il kernel deve bilanciare l'uso della memoria per cache file vs spazio per applicazioni attive.

Memoria Virtuale

La memoria virtuale fornisce a ogni processo uno spazio di indirizzamento lineare e isolato, indipendente dalla RAM fisica. Permette di caricare solo le parti necessarie di un programma in RAM, lasciando il resto su disco. Facilita il linking, la condivisione di librerie e la protezione. Ogni processo 'crede' di avere memoria contigua esclusiva. Il MMU (Memory Management Unit) traduce indirizzi virtuali in fisici. Questo permette di eseguire più processi di quanto la RAM fisica permetterebbe fisicamente. È la base del multitasking moderno, permettendo isolamento totale: un processo non può accedere alla memoria di un altro senza esplicita condivisione.

Paginazione su Disco

Quando la RAM è piena, le pagine meno usate vengono scritte su un'area di disco dedicata (swap o pagefile). Questo libera RAM per processi attivi. L'accesso al disco è ordini di grandezza più lento della RAM, causando rallentamenti severi se eccessivo (thrashing). Il kernel usa algoritmi di replacement (LRU approssimato) per scegliere le vittime. La dimensione dello swap determina quanti processi possono rimanere in memoria sospesa. SSD moderni hanno ridotto la penalità di swap, ma rimane una misura di emergenza. Una configurazione corretta dello swap è vitale per stabilità sotto carico di memoria elevato.

Table Mapping

Le page table sono strutture dati che mappano indirizzi virtuali a fisici. Possono essere multilivello per risparmiare spazio (solo le parti usate della tabella esistono in RAM). Ogni accesso alla memoria richiede una consultazione della tabella, spesso cached nella TLB. La dimensione della page table cresce con la memoria del processo. Aggiornare le page table durante lo switch di contesto o allocazioni è costoso. Kernel usano tecniche come huge pages per ridurre il numero di entry e migliorare la traduzione. La corruzione delle page table porta a fault di pagina invalidi e crash immediati del processo.

Vantaggi Isolamento

L'isolamento impedisce a un processo di leggere o scrivere memoria altrui, garantendo stabilità e sicurezza. Un bug in un'app non corrompe il sistema operativo o altre app. Questo è fondamentale in ambienti multi-utente e server. La protezione è hardware-enforced dal MMU: accessi illegali triggerano exception gestite dal kernel (segfault). Permette anche di nascondere la disposizione fisica della memoria, rendendo più difficile per malware sfruttare indirizzi fissi. L'isolamento è la base per sandboxing e containerizzazione. Senza memoria virtuale, il debugging e la sicurezza sarebbero enormemente più complessi e rischiosi.

Indirizzi Logici vs Fisici

La CPU genera indirizzi logici (virtuali) che vengono tradotti dal MMU in indirizzi fisici sulla RAM. Questa separazione permette di spostare dati in RAM senza cambiare il codice del programma (ricollocabilità). Permette anche di caricare programmi in posizioni fisiche diverse ogni volta. La traduzione avviene per ogni accesso memoria, quindi deve essere velocissima (hardware). La differenza tra i due spazi è trasparente al software utente. Questo meccanismo abilita la memoria virtuale e la protezione. Comprendere questa distinzione è chiave per ottimizzare l'uso della cache e capire i performance penalty di certi accessi memoria.

Paging e Segmentation

Sono due schemi di gestione memoria. Il paging divide la memoria in blocchi fissi (pagine), eliminando la frammentazione esterna ma causando frammentazione interna. La segmentation divide la memoria in blocchi logici variabili (codice, dati, stack), più intuitiva ma soggetta a frammentazione esterna. I sistemi moderni usano quasi esclusivamente paging o una combinazione (segmented paging). Il paging semplifica l'allocazione e lo swapping. La dimensione della pagina (4KB, 2MB, 1GB) influenza l'overhead delle tabelle e l'efficienza della TLB. La scelta dello schema impatta la complessità del linker e del loader dei programmi eseguibili.

Struttura Pagina

Una pagina è un blocco di memoria fisica di dimensione fissa, tipicamente 4KB. La memoria virtuale è divisa in pagine della stessa dimensione. Questo permette di mappare qualsiasi pagina virtuale su qualsiasi frame fisico libero. La dimensione fissa semplifica l'allocazione e il recupero della memoria libera (bitmask o free list). Pagine più grandi (huge pages) riducono il numero di entry nelle page table e i miss della TLB, migliorando prestazioni per database o HPC. Tuttavia, allocare huge pages può causare spreco se il processo usa poca memoria. La struttura è gestita interamente dal hardware MMU e dal kernel.

External Fragmentation

La frammentazione esterna ocorre quando c'è memoria libera totale sufficiente per una richiesta, ma non è contigua. Tipica della segmentazione o allocazione dinamica. Rende impossibile allocare blocchi grandi nonostante memoria disponibile. Richiede compattazione della memoria (spostare processi), operazione costosa. Il paging elimina quasi totalmente la frammentazione esterna perché alloca in blocchi fissi. In sistemi con allocazione fisica contigua, la frammentazione esterna degrada le prestazioni nel tempo fino a richiedere riavvio o deframmentazione. I moderni allocatori di memoria usano strategie per minimizzare questo fenomeno.

Internal Fragmentation

La frammentazione interna avviene quando la memoria allocata è leggermente più grande di quella richiesta. Tipica del paging: se un processo chiede 4.1KB e la pagina è 4KB, ne usa due (8KB), sprecando 3.9KB. Lo spreco medio è metà della dimensione della pagina. Ridurre la dimensione della pagina riduce lo spreco ma aumenta l'overhead delle tabelle. È un trade-off classico. In sistemi con molta memoria, la frammentazione interna è accettabile per i benefici del paging. Gli allocatori di heap cercano di minimizzare questo accoppiando richieste di dimensioni simili negli stessi blocchi di allocazione per ridurre lo spreco interno medio.

TLB Cache

La Translation Lookaside Buffer è una cache hardware per le traduzioni di indirizzi virtuali-fisici. Evita di accedere alla page table in RAM per ogni accesso memoria, che sarebbe lentissimo. Un hit nella TLB è immediato; un miss richiede un page walk in memoria. La dimensione della TLB è piccola, quindi gestire il suo riempimento è critico. Cambiamenti frequenti di spazio di indirizzamento (switch processo) flushano la TLB, costoso. Alcune CPU hanno TLB taggate per ASID per evitare flush completi. Ottimizzare la locality spaziale del codice aiuta a massimizzare i hit rate della TLB.

Allocazione Dinamica

I processi richiedono memoria durante l'esecuzione (heap). Il kernel o le librerie runtime gestiscono queste richieste (malloc/new). L'allocazione deve essere veloce e minimizzare la frammentazione. Strategie come buddy system o slab allocator sono usate nel kernel. Nell'user space, gli allocatori gestiscono pool di memoria. Errori comuni includono memory leak (memoria non liberata) e dangling pointer. La gestione dinamica impatta le prestazioni se frammenta l'heap o richiede syscall frequenti (brk/mmap). Allocatori moderni usano thread-local cache per ridurre la contenzione di lock durante allocazioni concorrenti.

Heap Management

L'heap è la regione di memoria per allocazioni dinamiche a runtime. Cresce verso indirizzi alti. Il gestore dell'heap mantiene liste di blocchi liberi e occupati. Quando si libera memoria, i blocchi adiacenti vengono coalesciuti per ridurre frammentazione. Allocazioni frequenti di piccole dimensioni possono frammentare l'heap, riducendo efficienza. Gli allocatori usano bin per dimensioni specifiche per velocizzare il recupero. La gestione dell'heap è critica per applicazioni long-running. Un heap frammentato aumenta l'uso di memoria virtuale e riduce la cache locality. Strumenti di profiling analizzano l'uso dell'heap per ottimizzare pattern di allocazione.

Memory Leaks

Un memory leak ocorre quando memoria allocata non viene mai liberata, riducendo progressivamente la memoria disponibile. In processi long-running (daemon, server), i leak portano a esaurimento memoria e crash (OOM). Il kernel non recupera automaticamente memoria leakata di un processo attivo. Strumenti come Valgrind o AddressSanitizer rilevano leak. Linguaggi con garbage collection mitigano il problema ma non lo eliminano (leak di riferimenti). Prevenire leak richiede disciplina nel pairing alloc/free. In kernel space, i leak sono critici e possono richiedere riavvio del sistema se consumano memoria non paginabile o strutture globali.

Garbage Collection

Il Garbage Collection (GC) automatizza il recupero di memoria non più raggiungibile. Usato in Java, Python, Go. Rimove il burden dal programmatore ma introduce overhead e pause (stop-the-world). GC moderni sono concurrenti e generazionali per minimizzare pause. Il GC analizza il grafo degli oggetti per trovare radici vive. Impatta le prestazioni real-time a causa della non deterministica latenza di raccolta. L'interazione tra GC OS e GC linguaggio è complessa: entrambi competono per memoria. Tuning del GC è essenziale per applicazioni high-throughput per bilanciare throughput e latenza di raccolta.

Buddy System

Algoritmo di allocazione kernel che gestisce memoria fisica in potenze di 2. Se serve una pagina, cerca un blocco libero della dimensione giusta. Se non trova, spacca un blocco più grande (buddy). Quando si libera, tenta di fondere con il buddy per ricreare blocchi grandi. Riduce frammentazione esterna e velocizza ricerca blocchi liberi. Usato per allocazione pagine fisiche nel kernel Linux. Efficiente ma causa frammentazione interna se le richieste non sono potenze di 2. Permette di soddisfare rapidamente richieste di memoria contigua fisica, necessarie per DMA o mapping hardware diretto.

Swapping e Thrashing

Lo swapping sposta processi interi o pagine tra RAM e disco. Il thrashing è una condizione patologica dove il sistema passa più tempo a swappeare pagine che a eseguire lavoro utile. Accade quando la somma dei working set dei processi eccede la RAM fisica. Il throughput crolla drasticamente. Il kernel deve rilevare thrashing e ridurre il grado di multiprogrammazione (sospendere processi). Aumentare RAM è la soluzione hardware. Ottimizzare la locality del codice software riduce la pressione sullo swap. Monitorare i page fault è essenziale per diagnosticare thrashing prima che il sistema diventi inutilizzabile.

Definizione Thrashing

Il thrashing è lo stato in cui il paging activity è così intenso da saturare il bus I/O e la CPU, lasciando poco tempo per l'esecuzione effettiva. I processi faultano continuamente perché le pagine appena caricate vengono subito sostituite per far spazio ad altre. Il sistema appare bloccato o lentissimo. È causato da sovraccarico di memoria (overcommit). Il kernel rileva thrashing monitorando il rateo di page fault e l'utilizzo disco. La contromisura è sospendere processi attivi per ridurre la pressione. Prevenire thrashing richiede stimare correttamente il working set necessario per i processi attivi.

Working Set Model

Il working set è l'insieme di pagine attivamente usate da un processo in una finestra temporale. Per evitare thrashing, la somma dei working set di tutti i processi deve stare in RAM. Il kernel stima il working set per decidere quante pagine mantenere residenti. Se la RAM non basta per i working set minimi, il sistema è sovrasaturo. Politiche di sostituzione pagine dovrebbero preservare il working set. Comprendere il working set aiuta a dimensionare la RAM per server o VM. Applicazioni con working set grande e casuale soffrono più di thrashing rispetto a quelle con accesso sequenziale.

Page Fault Rate

Il page fault rate è la frequenza con cui si verificano accessi a pagine non in RAM. Un rateo basso è normale; un rateo alto indica pressione memoria. I fault possono essere major (richiedono I/O disco) o minor (risolti in RAM). I major fault sono costosi. Monitorare questo rateo permette di tuningare la memoria assegnata a VM o container. Un aumento improvviso indica leak o cambio di carico di lavoro. Il kernel regola aggressività dello swapping basandosi su questo rateo. Strumenti come vmstat mostrano colonne specifiche per page in/out per diagnosticare problemi di memoria in tempo reale.

Strategie di Sostituzione

Quando serve spazio, il kernel deve scegliere quale pagina espellere. Algoritmi comuni: LRU (Least Recently Used), FIFO, Clock. LRU è optimalmente teorico ma costoso da implementare esattamente; si usano approssimazioni (Second Chance). La scelta influenza quali processi vengono penalizzati. Pagine di codice sono facili da ricaricare (da disco eseguibile); pagine di dati dirty richiedono scrittura. Le strategie cercano di minimizzare i fault futuri. In sistemi real-time, la sostituzione deve essere prevedibile. La politica di sostituzione è cruciale per prestazioni sotto carico di memoria sostenuto.

Protezione Memoria

La protezione impedisce accessi non autorizzati alla memoria. Ogni pagina ha bit di protezione (read, write, execute). Il kernel mappa spazi kernel inaccessibili all'user mode. Tecniche come ASLR randomizzano indirizzi per ostacolare exploit. Violazioni triggerano exception hardware. Questa protezione è la prima linea di difesa contro malware e bug software. Senza di essa, un errore di pointer potrebbe corrompere il kernel. La protezione si estende alla condivisione memoria tra processi: solo regioni esplicitamente mappate come shared sono accessibili. Garantisce integrità dei dati e confidenzialità tra utenti diversi sullo stesso sistema.

Bit Read/Write/Execute

Ogni entry nella page table include bit di permesso. Read permette lettura, Write modifica, Execute lancia codice. Separare Execute da Write previene attacchi code-injection (scrivere codice e eseguirlo). Il MMU controlla questi bit ad ogni accesso. Tentare di scrivere su pagina read-only causa segmentation fault. Questi bit permettono di proteggere codice da modifiche accidentali o malevole. Il kernel usa pagine read-only per dati costanti. La granularità è per pagina. Gestire correttamente questi permessi è vitale per sicurezza. W^X (Write XOR Execute) è una policy di sicurezza rigorosa applicata in molti OS moderni.

Kernel Space Isolation

La memoria del kernel è mappata in uno spazio indirizzi separato o protetto (bit U/S nelle page table). Processi user non possono accedere a queste pagine. Questo previene che applicazioni leggano dati sensibili (password, chiavi) o modifichino strutture critiche del kernel. In alcune architetture, la mappatura kernel è presente in tutti spazi ma inaccessibile. In altre (KPTI), viene rimossa completamente durante esecuzione user per mitigare side-channel attack (Meltdown). L'isolamento aggiunge overhead di switch ma è essenziale per sicurezza. Viola questo isolamento porta a privilege escalation immediata.

Buffer Overflow Prevention

Tecniche come stack canaries, NX bit (No Execute) e ASLR mitigano buffer overflow. Il NX bit marca lo stack come non eseguibile, impedendo esecuzione di shellcode iniettato. I canaries rilevano sovrascritture dello stack prima del return. L'ASLR randomizza posizioni di stack e heap, rendendo difficile indirizzare exploit. Queste protezioni sono gestite dal kernel e hardware. Non eliminano il bug ma ne neutralizzano l'effetto exploit. Compiler moderni abilitano queste protezioni di default. Sono fondamentali per difendersi da vulnerabilità di memoria comuni in linguaggi non sicuri come C/C++.

Address Space Layout Randomization

ASLR randomizza la posizione di aree di memoria (stack, heap, librerie) ad ogni esecuzione. Rende difficile per un attaccante prevedere indirizzi di funzioni o gadget per exploit. Richiede codice position-independent (PIC). Implementato nel loader del sistema operativo. Aumenta la sicurezza rendendo gli attacchi non deterministici. Può essere disabilitato per debugging o prestazioni specifiche. L'efficacia dipende dall'entropia della randomizzazione. Combinato con DEP (Data Execution Prevention), rende gli exploit di memoria significativamente più complessi da sviluppare e affidabili, proteggendo il sistema da vulnerabilità zero-day non patchate.

Cache Hierarchy

La gerarchia di memoria include registri, cache L1/L2/L3, RAM e disco. Il OS deve essere aware di questa gerarchia per ottimizzare prestazioni. La locality (spaziale e temporale) è chiave. Il kernel gestisce la coerenza delle cache in sistemi multicore. Allocazioni memoria allineate alle cache line migliorano prestazioni. Ignorare la cache hierarchy porta a sistemi lenti anche con CPU veloci. Tecniche come huge pages o coloring delle cache aiutano. Il software dovrebbe accedere dati in modo sequenziale per sfruttare prefetching hardware. La gestione OS influenza quanto efficacemente l'hardware usa le cache.

L1 L2 L3 Cache

Le cache sono memorie veloci on-chip. L1 è divisa in istruzioni e dati, velocissima ma piccola. L2 è più grande, L3 condivisa tra core. Il OS non gestisce direttamente le cache (hardware) ma influenza il loro uso tramite pattern di accesso memoria. Context switch flushano cache, riducendo efficienza. Scheduler aware di cache cercano di mantenere processi sugli stessi core. La dimensione delle cache determina il working set massimo per prestazioni massime. Comprendere la gerarchia aiuta a ottimizzare algoritmi per ridurre cache miss. Miss in L3 portano ad accesso RAM, costoso in cicli CPU.

Coerenza Cache

In multicore, ogni core ha cache private. Se un core modifica un dato, le cache altrui devono invalidare la copia obsoleta. Protocolli come MESI gestiscono questa coerenza hardware. Il kernel deve usare istruzioni di memory barrier per garantire ordine di visibilità tra thread su core diversi. La coerenza ha un costo in bandwidth e latenza. False sharing (thread diversi su stesso cache line) degrada prestazioni drasticamente. Il OS fornisce primitive di sincronizzazione che includono barriere necessarie. Programmare senza considerare la coerenza porta a bug di concorrenza subtili e prestazioni imprevedibili.

Policy di Scrittura

Quando scrivere dati dalla cache alla RAM? Write-through (ogni scrittura va a RAM, sicuro ma lento) o Write-back (scrive solo quando la linea è espulsa, veloce ma rischio perdita dati su crash). I sistemi usano write-back per prestazioni, con cache flushing periodico o su sync. Il kernel gestisce la dirty bit nelle page table per tracciare pagine modificate da scrivere su disco. Policy di scrittura influenzano durabilità dati e prestazioni I/O. In sistemi critici, si forza flush frequenti. Il compromesso è tra velocità percepita e integrità dati garantita in caso di perdita potenza.

Località Spaziale e Temporale

Località temporale: dati usati ora saranno usati presto (loop). Località spaziale: dati vicini a quelli usati ora saranno usati presto (array). Gli scheduler e allocatori memoria cercano di preservare queste località. Mantenere processi sugli stessi core sfrutta località temporale (cache calda). Allocare memoria contigua sfrutta località spaziale (prefetching). Algoritmi che violano queste località (accessi random) soffrono penalty prestazioni. Ottimizzare codice per località è spesso più efficace che ottimizzare istruzioni. Il OS supporta questo con politiche di allocazione memoria e scheduling CPU affinity.

Gestione I/O e Device

Il sottosistema I/O gestisce la comunicazione con periferiche (dischi, rete, tastiera). È eterogeneo e lento rispetto alla CPU. Il OS usa driver per astrazione, interrupt per efficienza e buffering per velocità. La gestione I/O influenza drasticamente le prestazioni percepite. Tecniche come DMA scaricano la CPU. Lo scheduling I/O ottimizza movimenti meccanici (dischi). La uniformità dell'interfaccia (tutto è file) semplifica la programmazione. Gestire errori I/O è critico per stabilità. Il kernel deve bilanciare throughput I/O e latenza di risposta, gestendo code di richieste complesse per dispositivi con caratteristiche fisiche molto diverse.

Driver Dispositivi

I driver sono moduli kernel che parlano l'hardware specifico. Astraggono dettagli hardware fornendo API uniformi al resto del OS. Possono essere loadable modules. Un driver bugghato può crashare l'intero sistema (kernel panic). La qualità dei driver è essenziale per stabilità hardware. Supportano operazioni open, read, write, ioctl. Isolano le peculiarità hardware (registri, timing). Lo sviluppo driver richiede conoscenza profonda hardware e kernel. Driver user-mode sono più sicuri ma più lenti. Il kernel gestisce il versioning e la compatibilità dei driver per permettere aggiornamenti hardware senza ricompilare il kernel intero.

Kernel Mode Drivers

Eseguono in spazio kernel, con accesso completo all'hardware e memoria. Massime prestazioni, minimo overhead. Tuttavia, un errore nel driver corrompe il kernel (BSOD o panic). Usati per dispositivi critici (disco, rete, video). Richiedono sviluppo rigoroso e testing. Non possono usare librerie standard user-space. Hanno priorità alta nello scheduling. La complessità di debug è alta. Sono essenziali per dispositivi ad alte prestazioni dove ogni microsecondo conta. La tendenza moderna è spostare logica non critica in user-mode per stabilire un confine di sicurezza più netto.

User Mode Drivers

Eseguono come processi utente. Un crash del driver non blocca il sistema, solo il dispositivo. Più sicuri e facili da sviluppare/debuggare. Tuttavia, hanno overhead di context switch per accedere hardware. Usati per dispositivi meno critici o dove la stabilità è prioritaria (es. alcune stampanti, USB). Il kernel fornisce framework per mappare accesso hardware in user-space (es. UIO). Riducono la superficie di attacco del kernel. La performance penalty è accettabile per molti dispositivi moderni veloci. Facilitano aggiornamenti driver senza riavvio del sistema operativo.

Plug and Play

Permette al sistema di rilevare e configurare hardware automaticamente all'inserimento. Il kernel identifica il dispositivo, carica il driver appropriato e assegna risorse (IRQ, memoria). Elimina configurazione manuale complessa. Richiede standard hardware (PCIe, USB) e supporto OS. Gestisce hot-plugging (rimozione sicura). Semplifica l'esperienza utente. Il sottosistema PnP mantiene un database di ID hardware e driver. In caso di conflitto risorse, il OS riassegna dinamicamente. Fondamentale per laptop e dispositivi consumer dove l'hardware cambia frequentemente.

API Standardizzate

Il kernel espone API uniformi per I/O (read/write) indipendentemente dal device. Questo permette di scrivere codice generico che funziona su disco, rete o tty. Le API astraggono buffer, blocking e error handling. Standard come POSIX definiscono queste interfacce. Driver implementano queste API per il loro hardware specifico. Questa astrazione è il cuore della portabilità del software Unix-like. Permette redirezione I/O (pipe). Senza standardizzazione, ogni dispositivo richiederebbe codice specifico, rendendo lo sviluppo software insostenibile. L'uniformità facilita scripting e automazione amministrativa.

Interrupt Handling

Gli interrupt segnalano alla CPU eventi hardware (tasto premuto, disco pronto). Il CPU sospende il lavoro corrente ed esegue una ISR (Interrupt Service Routine). Devono essere gestiti rapidamente per non perdere eventi. Interrupt annidabili permettono priorità. Maskable interrupt possono essere disabilitati temporaneamente. La gestione interrupt è asincrona e critica per la responsività. Troppi interrupt saturano la CPU (interrupt storm). Il kernel usa top-half (urgente) e bottom-half (deferito) per bilanciare velocità e complessità. L'efficienza dell'handling determina la latenza di risposta a input esterni.

Interrupt Vector Table

Tabella che mappa numeri di interrupt agli indirizzi delle ISR. Ogni dispositivo ha un IRQ associato. All'arrivo interrupt, CPU consulta la tabella per saltare al handler corretto. Gestita dal kernel all'avvio. In sistemi moderni (APIC), la tabella è più complessa e supporta routing verso core specifici. Permette di gestire decine di fonti interrupt diverse. La corruzione di questa tabella rende il sistema instabile. Il kernel registra handler di driver in questa tabella dinamicamente quando i driver vengono caricati. È il punto di ingresso fondamentale per l'interazione hardware-asincrona.

Maskable vs Non-Maskable

Interrupt maskable possono essere disabilitati dal software (CLI/STI) per sezioni critiche. Usati per la maggior parte dei device. Non-Maskable Interrupt (NMI) non possono essere disabilitati, usati per errori hardware critici (parity error, watchdog). Gli NMI bypassano le protezioni standard per garantire attenzione immediata. Gestire NMI richiede cura estrema perché non possono essere bloccati. I maskable permettono al kernel di proteggere operazioni atomiche. La distinzione è vitale per stabilità: un errore hardware grave deve sempre essere segnalato, mentre I/O normale può attendere brevemente.

Interrupt Service Routine

La ISR è la funzione eseguita all'arrivo dell'interrupt. Deve essere brevissima: legge registro device, ack interrupt, schedula lavoro pesante dopo. Non può bloccare o dormire. Esegue in contesto interrupt, limitato nelle operazioni permesse. Un ISR lenta blocca altri interrupt e il sistema. Il kernel divide lavoro in top-half (ISR) e bottom-half (tasklet, workqueue). Questa separazione garantisce bassa latenza di risposta mantenendo throughput. Scrivere ISR efficienti è una skill critica nel kernel development. Errori qui causano freeze o perdita di dati hardware.

Latenza Interrupt

Tempo tra arrivo segnale hardware e inizio esecuzione ISR. Include tempo salvataggio contesto e disabling interrupt. Deve essere minimizzata per device real-time o alta velocità (rete 10Gbps+). Interrupt coalescing raggruppa interrupt per ridurre overhead ma aumenta latenza. Tuning di questa latenza è cruciale per server network-intensive. Hardware moderni supportano MSI-X per ridurre contenzione. Monitorare la latenza interrupt aiuta a diagnosticare jitter di sistema. Una latenza alta rende il sistema 'laggy' nell'input utente o nella risposta di rete.

Buffering e Caching

Buffer e cache mitigano la differenza di velocità tra CPU e I/O. Il buffering accumula dati prima di scriverli/leggerli (es. stampa, disco). La caching mantiene copie di dati frequenti in RAM veloce. Riduce accessi fisici lenti. Il kernel gestisce page cache per file. Buffering permette I/O asincrono. La dimensione dei buffer influenza latenza e throughput. Buffer troppo piccoli causano overhead; troppo grandi causano ritardo. La gestione cache deve bilanciare memoria per app e per cache file. È essenziale per prestazioni disco e rete.

Double Buffering

Usa due buffer: mentre uno è riempito dal producer, l'altro è svuotato dal consumer. Elimina tempi di attesa tra produzione e consumo. Usato in grafica (front/back buffer) e I/O disco. Permette continuità di flusso dati. Quando un buffer è pieno, si switcha istantaneamente all'altro. Riduce jitter e migliora smoothness. Richiede doppia memoria ma il guadagno prestazioni vale il costo. Fondamentale per streaming video e audio dove interruzioni nel flusso sono visibili/udibili. Il kernel gestisce questo transparently per molte operazioni I/O block.

Circular Buffer

Struttura dati a dimensione fissa dove la scrittura riparte dall'inizio quando raggiunge la fine. Efficiente per flussi dati continui (log, rete, audio). Non richiede spostamento dati. Gestisce producer/consumer a velocità diverse. Se pieno, sovrascrive dati vecchi o blocca il producer. Usato intensivamente nei driver per gestire interrupt burst. Implementazione lock-free possibile per alte prestazioni. Minimizza allocazioni dinamiche durante I/O. È la struttura base per code di interrupt e buffer di rete nei kernel moderni per efficienza e prevedibilità temporale.

Disk Cache

Il kernel usa RAM libera per cacheare blocchi disco letti/scritti. Letture successive sono servite da RAM (veloci). Scritture sono accumulate e flushate periodicamente (write-back). Migliora drasticamente prestazioni file system. La cache è gestita dinamicamente: se app richiedono RAM, la cache si riduce. Algoritmi LRU gestiscono l'eviction. Sync e fsync forzano flush per durabilità. Una cache grande maschera la lentezza dei dischi meccanici. Su SSD, la cache è meno critica ma ancora utile per metadati e accessi random. Tuning della cache influenza I/O throughput.

Spooling Stampa

Simultaneous Peripheral Operations On-Line. I job di stampa sono accodati su disco invece di inviare direttamente alla stampante lenta. L'applicazione rilascia subito il controllo. Un daemon di stampa gestisce la coda fisicamente. Permette multi-utente e gestione errori senza bloccare app. Bufferizza grandi documenti. Il spooling trasforma un device dedicato e lento in una risorsa condivisa efficiente. Gestisce priorità e scheduling di stampa. È un esempio classico di buffering per disaccoppiare velocità producer (CPU) e consumer (stampante).

Direct Memory Access

DMA permette a dispositivi hardware di leggere/scrivere memoria RAM senza CPU. La CPU initia il trasferimento e viene interruptata solo alla fine. Libera la CPU per altro lavoro durante trasferimenti grandi (disco, rete, GPU). Essenziale per throughput moderno. Il controller DMA gestisce indirizzi e conteggi. Richiede memoria buffer fisicamente contigua o mappata. Senza DMA, la CPU sarebbe saturata da copie dati (programmed I/O). Il kernel gestisce mapping DMA e coerenza cache. È fondamentale per prestazioni I/O ad alta velocità.

Bypass CPU

Il vantaggio principale del DMA è evitare che la CPU copi ogni byte. La CPU imposta puntatori sorgente/destinazione e lunghezza, poi il controller DMA esegue il bus transfer. La CPU è libera durante il trasferimento. Riduce utilizzo CPU per I/O da 100% a quasi 0% durante il bulk transfer. Permette multitasking efficiente anche sotto carico I/O pesante. Senza DMA, server ad alto traffico network o storage sarebbero impossibili. Il bypass è gestito hardware ma coordinato dal driver kernel che prepara i descrittori di trasferimento.

DMA Controller

Hardware dedicato che gestisce i trasferimenti. Supporta canali multipli per device diversi. Gestisce arbitraggio sul bus di sistema. I controller moderni (Bus Mastering) prendono controllo del bus direttamente. Il kernel deve programmare il controller con indirizzi fisici (non virtuali). Supporta scatter-gather per gestire buffer non contigui. Il controller genera interrupt al completamento. La complessità del controller influenza le capacità I/O del sistema. Driver devono essere compatibili con le capacità del controller DMA specifico della motherboard o device.

Bus Mastering

Capacità del device di initiare trasferimenti sul bus senza intervento CPU centrale. Il device diventa master del bus temporaneamente. Riduce latenza e overhead di coordinamento. Comune in PCIe e SATA. Richiede supporto hardware e driver. Permette trasferimenti device-to-device (es. rete a disco) senza passare per RAM CPU (zero-copy). Aumenta efficienza sistema complessiva. Il kernel deve gestire permessi di bus mastering per sicurezza (IOMMU). È una feature critica per prestazioni storage e network moderne ad alta banda.

Interrupt al Completamento

Il DMA non elimina gli interrupt, li riduce. Un solo interrupt segnala la fine del blocco trasferito. Questo riduce il rateo di interrupt rispetto al byte-per-byte. Il kernel processa i dati dopo l'interrupt. Se il blocco è grande, l'efficienza è massima. Se troppo grande, aumenta la latenza di risposta. Il driver configura la dimensione del blocco DMA per bilanciare throughput e latenza. L'interrupt di completamento triggera il wakeup di processi in attesa sui dati. Questo meccanismo è il cuore dell'I/O asincrono ad alte prestazioni.

Scheduling I/O

Le richieste I/O verso disco devono essere ordinate per minimizzare movimenti meccanici (seek time). Lo scheduler I/O riordina le richieste in coda. Algoritmi simili a CPU scheduling ma ottimizzati per geometria disco. SSD richiedono scheduling diverso (nessun seek meccanico). Il kernel usa elevator algorithms. Lo scheduling I/O influenza latenza di scrittura e lettura. In VM, lo scheduling host e guest interagiscono. Scegliere lo scheduler giusto (noop, deadline, cfq) dipende dall'hardware storage. È cruciale per database e server file.

FCFS Disk

First-Come First-Served per richieste disco. Semplice e equo, ma inefficiente. La testina del disco si muove randomicamente, massimizzando seek time. Throughput basso sotto carico. Usato solo se altre politiche non sono possibili o per SSD dove l'ordine conta meno. Non ottimizza la posizione della testina. Può causare starvation indiretta se richieste lontane si accumulano. È il baseline contro cui misurare altri algoritmi. In sistemi moderni è raramente usato per dischi meccanici a causa della penalità prestazioni significativa.

SSTF (Shortest Seek Time First)

Seleziona la richiesta più vicina alla posizione corrente della testina. Minimizza il movimento braccio, massimizzando throughput. Simile a SJF per CPU. Rischia starvation per richieste lontane (se arrivano continuamente richieste vicine). Efficiente ma non equo. Richiede conoscenza posizione attuale. Implementazione richiede ordinamento coda costante. Buon compromesso per carichi misti. Tuttavia, la varianza del tempo di servizio può essere alta per request lontane. Usato come componente di algoritmi più complessi che aggiungono aging per prevenire starvation.

SCAN Algorithm

La testina si muove in una direzione servendo richieste, poi inverte al bordo del disco (come un ascensore). Elimina starvation garantendo servizio a tutte le richieste. Riduce movimento massimo rispetto a FCFS. Più equo di SSTF. Variazioni come C-SCAN tornano all'inizio senza servire al ritorno per uniformare tempi di attesa. Ottimizza il throughput mantenendo fairness accettabile. È lo standard de facto per dischi meccanici (Elevator algorithm). Il kernel implementa varianti per adattarsi a pattern di accesso specifici (seq vs random).

Elevator Algorithm

Implementazione pratica dello SCAN nel kernel Linux (es. deadline, noop, bfq). Ordina richieste per settore cilindro. Riduce seek time meccanico. Per SSD, algoritmi semplici (noop) sono spesso meglio perché non c'è seek meccanico e l'ordinamento può interferire con controller interno SSD. Il kernel rileva tipo disco e adatta scheduler. Tuning dello scheduler I/O può migliorare prestazioni database del 20-30%. Monitorare la coda I/O aiuta a capire se il disco è il collo di bottiglia. L'algoritmo gestisce anche priorità di lettura vs scrittura.

Device Independence

Il principio per cui il software non deve dipendere da hardware specifico. Il OS astrae device in interfacce uniformi (file, socket). Permette di cambiare hardware senza ricompilare app. Facilita portabilità. Tutto è trattato come stream di byte o file. Errori gestiti uniformemente. Naming uniforme (/dev/sda, /dev/tty). Questa astrazione è fondamentale per l'ecosistema Unix. Permette redirezione e piping tra device diversi. Riduce complessità sviluppo software. Il kernel traduce operazioni astratte in comandi hardware specifici tramite driver.

Uniform Naming

Tutti i device hanno nomi nel file system (es. /dev). Accesso tramite path standard. Non importase se è disco, stampante o rete. L'utente usa comandi standard (cat, cp) su device. Semplifica scripting e automazione. Nasconde dettagli hardware (indirizzi IRQ, porte). Il kernel mappa nomi a driver. Permette mounting di file system su qualsiasi block device. L'uniformità riduce la curva di apprendimento e aumenta la flessibilità operativa. È un pilastro del design Unix che influenza ancora tutti i sistemi operativi moderni.

Error Handling Uniforme

Errori I/O restituiscono codici standard (errno) indipendentemente dal device. EIO, ENOSPC, ecc. Le app gestiscono errori logicamente senza sapere l'hardware. Semplifica il codice applicativo. Il kernel traduce errori hardware specifici in codici generici. Permette recovery generico (retry, abort). Senza uniformità, ogni device richiederebbe gestione errori custom. Facilita debugging e logging. La coerenza nella segnalazione errori è cruciale per robustezza del software. Il kernel garantisce che violazioni di protezione o guasti hardware siano riportati in modo prevedibile.

Blocking vs Non-Blocking

I/O può bloccare il processo fino a completamento (blocking) o restituire subito (non-blocking). Blocking è semplice da programmare ma inefficiente per server concorrenti. Non-blocking richiede polling o select/epoll ma scala meglio. Il kernel supporta entrambi i modi tramite flag (O_NONBLOCK). Socket e file supportano queste modalità. La scelta influenza architettura software (thread per connection vs event loop). Il kernel gestisce code di ready per I/O non-blocking. Flexibilità fondamentale per sviluppare server ad alte prestazioni.

Dedicated vs Shared

Device possono essere dedicati (esclusivi, come stampante in uso) o condivisi (disco, rete). Il OS gestisce accesso concorrente a device shared tramite locking e code. Device dedicati richiedono locking a livello applicativo o OS. La distinzione influenza politiche di scheduling e protezione. Device shared richiedono fairness. Il kernel serializza accessi a device non condivisibili fisicamente. Comprendere la natura del device è essenziale per progettare sistemi multi-utente. Il OS astrae questa complessità fornendo lock file o semaphori per device.

File System e Storage

Il file system organizza dati persistenti su storage. Fornisce astrazione logica (file, directory) sopra blocchi fisici. Gestisce allocazione spazio, metadati, permissions e integrità. Diversi FS (ext4, NTFS, ZFS) offrono trade-off diversi. Il kernel usa VFS per uniformare accesso. La gestione storage include RAID e ridondanza. Performance FS dipende da allocazione e caching. Integrità dati è critica (journaling). Il file system è l'interfaccia primaria per storage permanente. Errori qui portano a perdita dati. La progettazione FS bilancia velocità, sicurezza e affidabilità.

Struttura Directory

Le directory organizzano file gerarchicamente. Possono essere single-level, two-level, tree o graph. Tree-structured è lo standard (Unix/Windows). Permette grouping logico e path unici. Acyclic graph permette sharing (link). La struttura influenza velocità di ricerca e backup. Directory profonde possono rallentare path resolution. Il kernel mantiene cache directory (dentry) per velocità. La struttura logica è indipendente dall'allocazione fisica. Organizzazione efficiente migliora produttività utente e gestione amministrativa. Il file system deve gestire creazione, cancellazione e rename di directory atomicamente.

Single-Level Directory

Tutti i file in una unica lista. Semplice ma non scala. Conflitti di nomi frequenti. Nessuna organizzazione logica. Usato in sistemi embedded molto semplici o vecchi. Impossibile separare dati utente. Ricercare file è lento se la lista è lunga. Non supporta multi-utente efficacemente. È il modello base da cui si è evoluti. Oggi obsoleto per sistemi general-purpose. Mostra l'importanza dell'organizzazione gerarchica per gestire complessità. Richiede nomi globalmente unici per tutti i file nel sistema.

Two-Level Directory

Separazione tra directory utente e file sistema master. Ogni utente ha sua directory. Risolve conflitti nomi tra utenti. Migliora sicurezza e isolamento. Ma limita condivisione file tra utenti. Struttura rigida. Passaggio intermedio verso tree. Utile per sistemi multi-utente semplici. Il master directory mappa utenti a path. Riduce ricerca a scope utente. Non permette sottocartelle personali. È un compromesso storico tra semplicità e organizzazione. Oggi sostituito da tree structure più flessibile.

Tree-Structured Directory

Gerarchia ad albero con root e sottodirectory illimitate. Standard moderno. Permette organizzazione logica profonda. Path assoluti e relativi. Facile navigare e gestire permessi per ramo. Supporta multi-utente e sharing controllato. Ricerca può essere lenta se profonda (mitigata da cache). Permette mounting di altri FS in punti dell'albero. Flessibile e scalabile. La radice (/) è il punto di partenza unico. Questa struttura è fondamentale per l'organizzazione dati moderna e scripting.

Acyclic-Graph Directory

Permette directory/file di avere più genitori (link simbolici o hard). Condivisione efficiente senza duplicazione dati. Attenzione a loop (cicli) che rompono backup o traversal. Richiede gestione reference counting per cancellazione. Flessibile per organizzazione dati complessa. Hard link condividono stesso inode; soft link sono puntatori path. Utile per librerie condivise e configurazioni multiple. Il kernel deve gestire risoluzione path complessa. Aumenta potenza organizzativa ma richiede cura per evitare inconsistenze.

Allocazione Spazio

Come i file occupano blocchi su disco. Contigua (veloce ma frammenta), Linked (flessibile ma lenta accesso random), Indexed (flessibile e veloce, overhead tabella). L'allocazione influenza prestazioni lettura/scrittura e frammentazione. FS moderni usano varianti di indexed allocation (extents). Gestire spazio libero è critico (bitmaps o free lists). L'efficienza allocazione determina velocità scrittura e longevità SSD (wear leveling). Il kernel deve allocare spazio rapidamente senza frammentare eccessivamente il disco.

Contigua

Il file occupa blocchi sequenziali sul disco. Lettura veloce (nessun seek). Soffre frammentazione esterna severa. Difficile espandere file dinamicamente. Richiede allocazione dimensione nota a priori. Usato per DVD/ISO o sistemi read-only. Semplice da implementare. Prestazioni ottimali per accesso sequenziale. Impraticabile per sistemi generali con file che crescono. Richiede compattazione periodica del disco per recuperare spazio contiguo. È il metodo base teorico ma raramente usato puro per file system writable moderni.

Linked Allocation

Ogni blocco contiene puntatore al prossimo. Elimina frammentazione esterna. File possono crescere dinamicamente. Accesso random lento (deve seguire catena). Overhead spazio per puntatori. Affidabilità bassa: se un puntatore si corrompe, si perde il resto del file. Usato in vecchi FS o per specifiche strutture. Non richiede allocazione contigua. Semplice gestione spazio libero. La mancanza di supporto efficiente per accesso random lo rende inadatto per database o esecuzione codice diretto da disco.

Indexed Allocation

Un blocco indice contiene puntatori a tutti i blocchi dati. Supporta accesso random diretto. Nessun overhead per blocco dati. Limite dimensione file basato su dimensione indice. Varianti usano multi-level indexing per file grandi (inode Unix). Efficiente e flessibile. Overhead minimo per file piccoli. Standard per FS moderni (ext, NTFS). Permette di mappare blocchi sparsi fisicamente in sequenza logica. Il kernel legge prima l'indice per localizzare dati. Bilancia velocità e flessibilità di allocazione.

Extents

Un extent descrive un range contiguo di blocchi (start + length). Riduce dimensione metadati rispetto a indexed puro per file grandi. Efficiente per file multimediali o database. Riduce frammentazione logica. Usato in ext4, XFS, NTFS. Permette allocazione pre-emptive di spazio contiguo. Migliora prestazioni sequenziali. Il kernel gestisce liste di extents per file. Se il file frammenta, si usano multipli extents. Ottimizzazione moderna per dischi grandi e veloci.

Journaling e Integrity

Il journaling registra cambiamenti in un log prima di applicarli al FS. Previene corruzione in caso di crash/power loss. Permette recovery rapido (replay log). Costo in prestazioni (scritture extra). Tipi: metadata only o full data. Garantisce consistenza strutturale FS. Essenziale per sistemi produzione. Senza journaling, richiede fsck lungo all'avvio. Il journal è un'area circolare dedicata sul disco. Il kernel gestisce commit atomici nel journal. Trade-off tra sicurezza dati e velocità scrittura.

Write-Ahead Logging

Principio base: scrivi log prima di modificare dati reali. Se crash accade, il log permette di completare o rollback operazioni. Garantisce atomicità e durabilità (proprietà ACID). Usato in database e FS journaling. Il log è sequenziale (veloce). Recovery è lineare nel log. Aggiunge latenza di scrittura (devi scrivere due volte). Fondamentale per integrità dati critici. Il kernel assicura che log sia flushato su disco stabile prima di confermare operazione all'app.

Metadata Journaling

Solo metadati (inode, directory) sono journalati. I dati file no. Recovery veloce per struttura FS. Rischio dati file corrotti ma FS consistente. Compromesso prestazioni/sicurezza. Usato da ext3 default. Meno overhead di full journaling. Protegge da perdita directory o file orphan. I dati possono essere vecchi ma la struttura è sana. È il bilanciamento più comune per desktop e server general-purpose dove velocità è prioritaria rispetto a garanzia assoluta su ogni byte.

Full Data Journaling

Sia metadati che dati sono journalati. Massima sicurezza. Recovery garantisce consistenza totale. Overhead prestazioni alto (scrivi dati due volte). Usato per dati critici. Protegge da corruzione dati parziale. Raddoppia scrittura su disco. Riduce vita SSD. Usato solo quando integrità dati è prioritaria assoluta. Il kernel gestisce il commit coordinato di dati e log. Garantisce che non esistano file con dati misti vecchi/nuovi in caso di crash improvviso.

Recovery Post-Crash

All'avvio dopo crash, il kernel legge il journal. Applica transazioni pendenti (commit) o scarta incomplete. Ripristina consistenza FS in secondi/minuti. Senza journaling, richiede scan completo disco (fsck) che può durare ore. Il recovery è automatico e trasparente. L'utente non perde accesso al sistema. Il journal deve essere su storage stabile. Il processo di recovery è critico per disponibilità sistema. Un journaling efficiente riduce downtime a minimi storici.

Permissions e ACL

Controlla chi può accedere a file/directory. Unix usa owner/group/others (rwx). ACL (Access Control Lists) permettono granularità fine per utente. Protegge dati sensibili. Il kernel controlla permessi ad ogni accesso. Errori di permission bloccano app o espongono dati. Root bypassa permessi. Permissions sono metadati sull'inode. Gestione corretta è vitale per sicurezza multi-utente. ACL estendono modello base per esigenze enterprise complesse.

Unix Permissions (rwx)

Modello classico: Read, Write, Execute per Owner, Group, Others. Semplice ed efficace. Implementato tramite bit su inode. Facile da gestire (chmod). Limitato: non puoi dare permessi diversi a più utenti singoli senza gruppi. Standard de facto per sistemi Unix-like. Il kernel controlla bit prima di ogni syscall. Execute su directory significa traversare. È la base della sicurezza file system Linux. Sufficiente per molti casi d'uso personali e server semplici.

Access Control Lists

Lista di entry (utente:permessi) associate al file. Permette controllo granulare per molti utenti. Estende modello Unix. Overhead metadati maggiore. Supportato da NTFS, ext4, ZFS. Gestito tramite setfacl/getfacl. Necessario per ambienti enterprise complessi. Il kernel deve valutare la ACL ad ogni accesso (leggermente più lento). Permette deny espliciti. Flessibilità massima per sharing sicuro. Essenziale per server file con molti utenti e progetti incrociati.

Capabilities

Token di autorità associati a processi o file. Invece di controllare identità utente, si controlla possesso di capability (es. 'può scrivere file X'). Più flessibile di ACL. Usato in sistemi distribuiti o sicurezza avanzata. Riduce rischio privilege escalation. Il kernel verifica capability invece di ID utente. Modello meno comune in FS standard ma usato in sicurezza kernel (Linux Capabilities). Permette delega autorità senza dare root. Approccio orientato all'oggetto più che al soggetto.

Owner Group Others

La triade base dei permessi Unix. Ogni file ha un proprietario (UID) e un gruppo (GID). 'Others' sono tutti gli utenti. Questa classificazione semplifica logica di controllo. Cambiare owner (chown) richiede root. I gruppi permettono sharing collaborativo. È il meccanismo primario di isolamento dati tra utenti. Il kernel usa UID/GID del processo per confrontare con inode. Se match, applica permessi corrispondenti. Semplice, efficiente e comprensibile.

Virtual File System

VFS è un layer di astrazione nel kernel che permette supportare multipli file system (ext4, nfs, tmpfs) con stesse syscall. Definisce interfaccia comune (inode, dentry, file object). I FS specifici implementano questa interfaccia. Permette mounting trasparente. Essenziale per flessibilità OS. Le app non sanno quale FS usano. Il VFS gestisce cache globale inode. Aggiungere un nuovo FS richiede solo implementare driver VFS. È il collante che unifica storage eterogeneo.

Astrazione Layer

VFS nasconde differenze implementative tra FS. Syscall come open() lavorano su oggetti VFS generici. Il VFS dispatcha al driver specifico. Permette di avere /home su ext4 e /mnt/net su NFS contemporaneamente. Semplifica codice kernel e applicativo. L'astrazione ha un piccolo costo prestazioni ma vale per flessibilità. Definisce strutture dati comuni (superblock, inode). È il cuore della portabilità storage in Unix. Senza VFS, ogni FS richiederebbe syscall diverse.

Supporto Multi-FS

Grazie a VFS, il kernel supporta decine di FS contemporaneamente. Network FS, disk FS, pseudo FS (proc). L'utente vede un albero unificato. Il kernel gestisce traduzione protocolli diversi. Permette migrazione dati tra FS trasparente. Essenziale per server eterogenei. Il mounting associa un FS a un punto directory. VFS gestisce transizione tra FS diversi nel path. Supporta feature comuni (permessi, timestamp) mappandole tra formati diversi.

Mount Points

Directory dove un FS secondario è agganciato all'albero principale. Il VFS reindirizza accesso sotto quel path al nuovo FS. Permette espansione storage senza cambiare path logici. Unmounting stacca il FS. I mount points sono gestiti dalla VFS table. Permette di nascondere struttura fisica dischi. Utente vede solo directory. Fondamentale per gestione storage flessibile. Il kernel risolve path attraversando mount points durante la lookup.

System Calls Uniformi

Read, write, open, close funzionano uguali per file, socket, pipe. VFS garantisce questa uniformità. Il codice applicativo è agnostico al tipo di risorsa. Semplifica programmazione I/O. Il kernel traduce la syscall in operazione specifica VFS. Permette redirezione standard input/output. È il principio 'Everything is a file'. Riduce complessità API. Sviluppare contro VFS assicura compatibilità forward. Le syscall sono il contratto stabile tra app e kernel.

RAID e Ridondanza

RAID (Redundant Array of Independent Disks) combina dischi per prestazioni o ridondanza. Livelli 0, 1, 5, 6, 10 offrono trade-off diversi. Hardware RAID (controller dedicato) o Software RAID (kernel). Protegge da failure disco. Migliora throughput (striping). Il kernel gestisce RAID software (mdadm). Essenziale per server critici. La ridondanza permette sostituzione disco a caldo. Il FS vede un unico device logico. Gestire RAID richiede monitoraggio stato health.

RAID 0 Striping

Dati distribuiti su più dischi senza ridondanza. Prestazioni lettura/scrittura sommate. Capacità totale somma dischi. Se un disco fallisce, si perdono tutti i dati. Rischioso per dati critici. Usato per cache o dati temporanei veloci. Nessuna parità. Il kernel scrive blocchi alternati su dischi. Massimizza bandwidth. Utile per video editing o scratch space. Non offre protezione failure.

RAID 1 Mirroring

Dati duplicati su due dischi. Ridondanza totale. Se uno fallisce, l'altro continua. Lettura veloce (da entrambi), scrittura lenta (doppia). Capacità utile 50%. Costo alto per storage. Sicurezza massima per dati critici. Il kernel scrive identicamente su entrambi. Recovery immediato swap disco guasto. Usato per boot drive o database critici. Semplice da implementare e gestire.

RAID 5 Parity

Striping con parità distribuita. Richiede min 3 dischi. Tolleranza 1 disco guasto. Capacità utile N-1. Prestazioni lettura buone, scrittura penalizzata da calcolo parità. Compromesso efficienza/spazio/sicurezza. Usato per file server. Il kernel calcola XOR per parità. Reconstruction dopo failure è lenta. Balance popolare per storage generale. Offre protezione senza costo 50% di RAID 1.

Hardware vs Software RAID

Hardware RAID usa controller dedicato con CPU/RAM propria. Trasparente al OS, alte prestazioni, costoso. Software RAID usa CPU host (kernel). Flessibile, economico, portabile (mdadm). Oggi CPU sono veloci, software RAID è competitivo. Hardware RAID nasconde dettagli health al OS. Software RAID permette monitoring fine nel kernel. Scelta dipende budget e requisiti gestione. Linux software RAID è molto robusto e diffuso.

Processi e Thread

Il processo è l'unità di esecuzione con risorse proprie. Il thread è unità di esecuzione dentro un processo che condivide memoria. Il OS gestisce creazione, scheduling e terminazione. IPC (Inter-Process Communication) permette cooperazione. Sincronizzazione previene race condition. Deadlock blocca il sistema. La gestione processi è il cuore del multitasking. Distinzione processo/thread influenza prestazioni e isolamento. Kernel gestisce tabelle processi (PID). Context switch tra thread è più leggero che tra processi. Comprendere questo modello è essenziale per programmazione concorrente.

Ciclo di Vita Processo

Stati: New, Ready, Running, Waiting, Terminated. Il processo transita tra stati per eventi (I/O, scheduler, exit). Il kernel traccia stato nel PCB. Transizioni sono triggerate da interrupt o syscall. Comprendere il ciclo aiuta a debuggare hang o zombie. Il processo nasce da fork/exec. Muore per exit o signal. Stati waiting liberano CPU per altri. Il ciclo è una macchina a stati finita gestita dal kernel.

New State

Il processo è in creazione. Il kernel alloca PCB e risorse iniziali. Non ancora eleggibile per scheduling. Caricamento programma da disco. Fase breve ma critica. Se fallisce (memoria insufficiente), processo muore qui. Il kernel prepara spazio indirizzi. È lo stato iniziale dopo fork. Transizione a Ready quando caricamento completo. Gestione errori in questa fase previene processi corrotti.

Ready State

Processo caricato e in attesa di CPU. In coda di scheduling. Eleggibile per esecuzione. Può esserci molti processi ready. Lo scheduler sceglie chi passa a Running. Tempo in ready dipende carico sistema. Processi real-time hanno priorità in questa coda. È lo stato di 'prontezza'. Il kernel mantiene code multiple per priorità. Un processo può tornare qui dopo I/O o timeout quanto.

Running State

Istruzioni eseguite sulla CPU. Solo uno (per core) in questo stato. Il processo ha controllo risorse CPU. Può transitare a Waiting (I/O) o Ready (timeout). È lo stato attivo. Il kernel traccia tempo esecuzione per accounting. Prelazione sposta processo fuori da qui. Massimizzare tempo in running è obiettivo prestazioni. Context switch avviene entrando/uscendo da questo stato.

Terminated State

Processo ha finito esecuzione o è stato ucciso. Risorse rilasciate tranne PCB (fino a wait parent). Diventa zombie se parent non legge exit status. Il kernel pulisce entry tabella processi. Codice exit indica successo/fallimento. Stato finale irreversibile. Il processo non esiste più logicamente. Il kernel recupera PID per riuso. Gestione corretta previene leak di entry tabella processi.

Inter-Process Communication

Meccanismi per processi scambiarsi dati. Shared memory (veloce, richiede sync), Message Passing (più sicuro, overhead), Pipes, Sockets. IPC è essenziale per cooperazione. Il kernel media l'accesso. Sincronizzazione critica per shared memory. IPC permette architettura microservizi o pipeline. Scelta meccanismo dipende velocità e sicurezza. Il kernel gestisce buffer e code per IPC. Senza IPC, processi sarebbero isolati inutilmente.

Shared Memory

Regione memoria mappata in spazi di più processi. Scambio dati velocissimo (copia zero). Richiede sincronizzazione esplicita (semaphori) per evitare race. Il kernel alloca segmento condiviso. Rischi di corruzione se sync fallisce. Usato per prestazioni critiche (database, grafica). Setup iniziale overhead, poi veloce. Permette comunicazione bidirezionale naturale. È il metodo IPC più efficiente in termini di throughput.

Message Passing

Processi si scambiano messaggi tramite kernel (send/receive). Kernel copia dati tra spazi. Più sicuro (isolamento mantenuto). Overhead copia dati. Sincronizzazione implicita nel trasferimento. Usato in sistemi distribuiti o microkernel. Elimina rischi shared memory. Può essere blocking o non-blocking. Il kernel gestisce code messaggi. Fondamentale per comunicazione tra processi non trustati.

Pipes e FIFO

Canali unidirezionali per flusso byte. Pipe anonime (parent-child), FIFO named (qualsiasi processo). Flusso stream. Blocking se vuoto/pieno. Semplice da usare (shell). Kernel gestisce buffer circolare. Chiude connessione quando writer chiude. Usato per pipeline comandi. Limitato a flusso sequenziale. Efficiente per comunicazione producer-consumer locale. Base dello scripting Unix.

Sockets

Endpoint per comunicazione rete o locale (Unix sockets). Supporta client-server. Indipendente da macchina (rete). Overhead protocollo stack. Versatile (TCP/UDP). Kernel gestisce buffer socket e handshake. Fondamentale per network programming. Unix sockets veloci per IPC locale. Permette comunicazione distribuita trasparente. Astrazione potente per connettività.

Thread vs Processi

Processi isolati, thread condividono memoria. Thread più leggeri (creazione/switch veloci). Crash thread può uccidere processo. Processi più robusti. Thread utili per parallelismo dentro app. Processi per isolamento servizi. Kernel moderni gestisce thread come processi leggeri (Linux). Programmazione thread richiede cura (sync). Scelta influenza architettura software.

Condivisione Memoria

Thread condividono heap, dati globali, file aperti. Hanno stack e registri privati. Comunicazione naturale tramite variabili. Rischio race condition alto. Processi hanno memoria separata, comunicazione esplicita. Condivisione thread riduce overhead IPC. Permette accesso diretto dati. Richiede locking per integrità. La condivisione è il vantaggio prestazionale principale dei thread.

Overhead Creazione

Creare processo richiede copia spazio indirizzi (fork), costoso. Creare thread alloca solo stack, veloce. Switch thread più leggero (stesso spazio indirizzi, no flush TLB). Thread scalano meglio per task fini. Processi isolano meglio errori. Overhead processo giustifica isolamento. Thread preferiti per concurrency alta dentro singola app. Il kernel ottimizza creazione thread per ridurre latenza.

Many-to-One Model

Molti thread utente mappati su un thread kernel. Se uno blocca, tutti bloccano. Scheduling user-space. Veloce ma no parallelismo reale multicore. Usato in vecchie librerie thread. Kernel vede un solo processo. Limitato per sistemi moderni. Utile se blocking raro. Non sfrutta multicore. Obsoleto per prestazioni.

Many-to-Many Model

Molti thread utente su molti thread kernel. Flessibile. Scheduling ibrido. Parallelismo reale. Se uno blocca, altri girano. Complesso da implementare. Standard moderno (NPTL Linux). Bilancia overhead e prestazioni. Permette di creare migliaia di thread utente efficientemente. Il kernel gestisce mapping dinamico. Massima scalabilità per applicazioni concorrenti.

Sincronizzazione

Coordina accesso risorse condivise. Mutex (lock esclusivo), Semaphores (contatore), Monitors (alto livello). Previene race condition. Deadlock rischio se mal gestito. Performance penalty di locking. Kernel fornisce primitive atomiche. Sincronizzazione essenziale per correttezza dati. Locking fine granularità meglio prestazioni. Sincronizzazione è il costo della concorrenza.

Mutex Locks

Lock binario (locked/unlocked). Solo un thread tiene il lock. Altri attendono. Garantisce esclusione mutua. Semplice da usare. Rischio deadlock se ordine errato. Il kernel mette thread in sleep se lock occupato. Overhead context switch se attesa lunga. Fondamentale per proteggere sezioni critiche. Usato ovunque in kernel e app.

Semaphores

Contatore intero per risorse multiple. Wait (decrementa) e Signal (incrementa). Controlla accesso a N istanze risorsa. Più flessibile di mutex. Usato per producer-consumer. Implementato nel kernel. Può essere counting o binary. Gestisce code di attesa. Strumento potente per sincronizzazione complessa.

Monitors

Costrutto alto livello (linguaggio). Incapsula dati e lock. Condition variables per attesa eventi. Più sicuro di mutex nudi. Gestito da runtime linguaggio. Kernel supporta primitive base. Riduce errori programmatore. Astrazione per concorrenza strutturata. Usato in Java, C#. Semplifica sviluppo thread-safe.

Condition Variables

Permette thread di attendere condizione specifica dentro lock. Wait rilascia lock e sospende. Signal sveglia thread. Evita polling attivo. Efficiente per attesa eventi. Usato con mutex. Il kernel gestisce code di wait. Fondamentale per sincronizzazione stato. Riduce consumo CPU rispetto a spinlock per attese lunghe.

Deadlock e Starvation

Deadlock: processi si bloccano a vicenda aspettando risorse. Starvation: processo non ottiene mai risorse. Condizioni deadlock: Mutua esclusione, Hold and Wait, No Preemption, Circular Wait. Kernel può prevenire, evitare o rilevare. Ostracismo risorse causa starvation. Gestione deadlock critica per stabilità. Algoritmi come Banker's evitano deadlock. Rilevamento richiede overhead.

Mutual Exclusion

Risorsa non condivisibile (es. stampante). Necessaria per deadlock. Se tutte risorse shared, no deadlock. Condizione necessaria ma non sufficiente. Hardware impone per alcune risorse. Il kernel gestisce accesso esclusivo. Fondamentale per integrità dati. Prima delle 4 condizioni di Coffman.

Hold and Wait

Processo tiene risorse e ne aspetta altre. Crea dipendenza. Prevenire richiede chiedere tutte risorse insieme. Riduce efficienza. Condizione necessaria per deadlock. Il kernel può forzare rilascio prima di attesa. Strategia di prevenzione comune. Aumenta rischio starvation se risorse scarse.

No Preemption

Risorse non possono essere forzatamente tolte. Se preemptabili, no deadlock. Difficile per alcune risorse (stato processo). Condizione necessaria. Il kernel può implementare preemption risorse per risolvere deadlock. Complesso da gestire stato parziale. Raro in pratica per risorse non CPU.

Circular Wait

Catena circolare di processi che si aspettano risorse. Completa il deadlock. Prevenire ordinando risorse numericamente. Rompe il ciclo. Condizione necessaria. Il kernel può rilevare grafi di attesa. Rilevamento ciclo indica deadlock. Strategia di prevenzione efficace se ordinamento possibile.

Zombie e Orphan

Zombie: processo terminato ma entry PCB presente (parent non ha fatto wait). Orphan: parent terminato prima del figlio. Kernel gestisce adoption (init). Zombie consumano entry tabella processi. Orphan non sono problematici. Wait system call pulisce zombie. Gestione corretta previene leak PID.

Definizione Zombie

Processo morto ma entry ancora in tabella processi. Attende parent legga exit status. Non usa risorse tranne entry PID. Troppi zombie saturano tabella processi. Visibili come stato 'Z' in top. Non possono essere uccisi (già morti). Solo parent può reaperli. Problema di programmazione parent.

Definizione Orphan

Processo il cui parent è terminato. Non è errore. Kernel riassegna a init (PID 1). Init fa wait periodicamente. Nessun leak risorse. Gestione automatica kernel. Comune in daemonizzazione. Non richiede intervento utente. Il kernel garantisce che ogni processo abbia un parent.

Wait System Call

Syscall usata da parent per attendere terminazione figlio. Recupera exit status. Rimove entry zombie. Blocking fino a terminazione figlio. Essenziale per pulizia. Se parent non chiama wait, zombie persistono. Il kernel mantiene stato exit fino a wait. Parte del ciclo vita processo.

Init Process Adoption

Quando parent muore, figli orfani sono adottati da init (o systemd). Init fa wait su di loro. Previene zombie per orfani. Meccanismo automatico kernel. Garantisce pulizia sistema. Init è antenato di tutti i processi. Fondamentale per stabilità long-running. Assicura che nessun processo rimanga senza supervisione.

Sicurezza e Protezione

La sicurezza protegge il sistema da accessi non autorizzati e malware. La protezione controlla accesso risorse interne. Meccanismi: User/Kernel mode, Autenticazione, Isolamento, Audit. Vulnerabilità sfruttano bug OS. Sandbox limita danni. Sicurezza è layered. Kernel è Trusted Computing Base. Compromesso tra sicurezza e usabilità. Aggiornamenti patch vulnerabilità. Sicurezza critica per server e dati sensibili. Il OS deve assumere ostilità ambiente.

User vs Kernel Mode

Dual mode hardware. User mode limitato, Kernel mode privilegiato. System call trap cambia mode. Previene app da danneggiare kernel. Bit mode controllato hardware. Fondamentale per protezione. Violazione mode causa exception. Isolamento base del sistema. Kernel mode ha accesso completo hardware. User mode accesso limitato tramite API.

Ring Protection

Archi x86 usano anelli (0-3). Ring 0 kernel, Ring 3 user. Anelli intermedi poco usati. Hardware enforce accesso. Istruzioni privilegiate solo Ring 0. Isolamento hardware forte. Cambiamento anello costoso (trap). Base architettura sicurezza moderna. Previene escalation privilegi arbitraria.

Privileged Instructions

Istruzioni CPU riservate kernel (I/O, gestione memoria, interrupt). Esecuzione in user mode causa trap. Protegge hardware da app. Kernel media accesso. Essenziale per stabilità. Lista istruzioni definita architettura. Violazione blocca processo. Garantisce che solo OS controlli risorse critiche.

Mode Bit

Bit hardware indica stato corrente (user/kernel). Controllato da CPU ad ogni istruzione. Cambia su interrupt/trap. Determina permessi accesso. Velocissimo controllo. Base implementazione dual mode. Il kernel resetta bit prima di tornare a user. Hardware garantisce integrità bit.

System Call Trap

Meccanismo per app richiedere servizi kernel. Interrupt software. Cambia mode bit. Salta a vector table kernel. Esegue servizio privilegiato. Ritorna a user mode. Punto ingresso controllato. Validazione parametri critica. Trap è il gateway sicuro tra user e kernel.

Autenticazione

Verifica identità utente. Password, biometrica, token. Login manager gestisce. Hash password salvati (non plaintext). Multi-factor aumenta sicurezza. Sessioni gestite dal OS. Autenticazione fallita blocca accesso. Fondamentale per multi-utente. Il kernel traccia UID dopo login. Base per autorizzazione successiva.

Password Hashing

Password non salvate in chiaro. Hash crittografico (SHA, bcrypt). Sale (salt) previene rainbow table. Confronto hash al login. Se database rubato, password non immediate. Algoritmi lenti ostacolano brute-force. Gestito da subsystem authentication. Best practice sicurezza essenziale. Il kernel non vede password in chiaro.

Multi-Factor Auth

Richiede 2+ prove (password + token/phone). Riduce rischio credential theft. Implementato a livello login OS o app. Aumenta sicurezza accesso remoto. Standard per admin access. Il OS può integrare PAM per MFA. Compromette usabilità per sicurezza. Critico per accesso privilegiato.

Biometric Systems

Impronta, volto, iris. Comodo ma privacy risk. Template salvati localmente secure enclave. Falsi positivi/negativi possibili. Hardware dedicato richiesto. Integrato nel login OS. Non sostituibile se compromesso (non cambi impronta). Usato come factor aggiuntivo. Il kernel gestisce driver sensori biometrici.

Token Based

Hardware token o app generano codici temporanei. Time-based OTP. Indipendente da rete. Usato per MFA. Fisicamente separato da PC. Sicuro contro phishing. Il OS valida token tramite server o locale. Gestito da PAM o login manager. Standard enterprise per accesso remoto sicuro.

Isolamento Risorse

Impedisce a processi di interferire. Namespace, Cgroups, VM, Container. Limita visibilità e risorse. Contiene danni malware. Fondamentale per cloud e multi-tenant. Kernel enforce isolamento. Violazione isolamento è breach grave. Tecniche moderne (Docker, K8s) basate su questo. Il kernel fornisce primitive isolamento.

Namespace Linux

Isola viste risorse (PID, network, mount). Processo vede solo sue risorse. Base containerizzazione. 6+ tipi namespace. Kernel gestisce mappature. Permette virtualizzazione leggera. Isolamento non completo come VM ma efficiente. Fondamentale per Docker. Il kernel traccia appartenenza namespace.

Cgroups

Control Groups limita uso risorse (CPU, RAM, I/O) per gruppi processi. Previene starvation. Usato per container limits. Kernel enforce limiti. Permette priorità e accounting. Essenziale per multi-tenant. Impedisce a un container di saturare host. Gestione risorse granulare.

Virtual Machines

Isolamento hardware completo via hypervisor. OS guest non vede host. Sicurezza massima. Overhead prestazioni. Usato per isolamento trust boundary. Kernel host gestisce VM come processi. Breach VM difficile ma possibile (VM escape). Standard per cloud isolation.

Containers Docker

Isolamento a livello OS (namespace+cgroups). Condivide kernel host. Leggero e veloce. Meno isolato di VM. Rischio se kernel compromesso. Standard deploy moderno. Kernel deve supportare feature container. Sicurezza dipende hardening kernel. Efficiente per microservizi.

Audit e Logging

Registra eventi sistema per analisi post-incidente. Log accessi, errori, cambi config. Essenziale per forensics. Compliance richiede logging. Performance impact da monitorare. Log rotazione gestisce spazio. Kernel genera log eventi sicurezza. Strumenti (auditd) raccolgono dati. Tracciabilità azioni utente e sistema.

System Logs

Registri eventi generali (syslog, journald). Boot, servizi, errori. Centralizzati. Analisi troubleshooting. Rotazione automatica. Accessibili ad admin. Kernel scrive messaggi ring buffer. Fondamentale per ops. Conservazione storica eventi.

Security Events

Log specifici sicurezza (login fail, sudo, perm change). Audit subsystem. Critici per detection intrusioni. Alerting possibile. Compliance (PCI-DSS) richiede. Kernel traccia syscall sensibili. Separati da log generali. Focus su anomalie.

Intrusion Detection

Software monitora log e attività per pattern attacco. IDS/IPS. Rileva exploit noti. Integrazione con kernel hooks. Alert real-time. Può bloccare traffico. Complessità configurazione. Essenziale per server esposti. Kernel fornisce dati telemetria.

Compliance Standards

Regole (GDPR, HIPAA) richiedono logging specifico. Retention policy. Accesso controllato ai log. Audit esterni verificano. Il OS deve supportare feature compliance. Configurazione rigorosa richiesta. Logging non solo tecnico ma legale. Garantisce accountability.

Vulnerabilità Commoni

Bug software sfruttati per attacco. Buffer overflow, race condition, privilege escalation. Patching critico. Kernel hardening riduce superficie attacco. Vulnerabilità zero-day pericolose. Security community reporta bug. Il OS deve essere aggiornabile. Conoscere vulnerabilità aiuta prevenzione.

Buffer Overflow

Scrivere oltre limite buffer. Sovrascrive memoria adiacente (return address). Permette code execution. Mitigato da ASLR, NX, stack canaries. Comune in C/C++. Kernel patcha bug simili. Classica vulnerabilità memoria.

Race Conditions

Risultato dipende timing eventi. TOCTOU (Time of Check to Time of Use). Sfrutta finestra tra check e uso. Difficile da debuggare. Kernel usa locking per prevenire. Comune in file access concurrente. Vulnerabilità logica concorrenza.

Privilege Escalation

Ottenere privilegi superiori (user to root). Sfrutta bug kernel o config errata. Critico per sicurezza. Patch immediate richieste. Kernel hardening limita escalation. Obiettivo principale attacker.

Rootkits

Malware nasconde presenza nel kernel. Hooka system call. Difficile da rilevare. Accesso kernel richiesto. Compromette integrità OS. Kernel signing previene caricamento non autorizzato. Minaccia avanzata persistente.

Sandbox e Container

Ambienti ristretti per eseguire codice non trustato. Limita syscall e risorse. Seccomp, AppArmor, SELinux. Contiene danni exploit. Usato per browser, container. Kernel enforce policy. Riduce superficie attacco. Essenziale per sicurezza moderna.

Seccomp Profiles

Filtra system call permesse. Blocca call non necessarie. Riduce superficie attacco kernel. Usato da Docker/Chrome. Kernel filtera syscall. Configura via BPF. Limita danni exploit app.

AppArmor

Security module Linux. Profile per programma (path based). Limita accesso file, rete, capabilities. Facile da configurare. Kernel enforce profile. Contiene app compromesse. Alternativa a SELinux.

SELinux

Mandatory Access Control (MAC). Policy complesse (label based). Più granulare di AppArmor. Curva apprendimento ripida. Kernel hooka decisioni accesso. Standard enterprise/governo. Protezione forte.

Resource Limits

Limiti ulimit (file, processi, memoria). Previene fork bomb o esaurimento risorse. Utente o sistema. Kernel enforce limiti. Semplice ma efficace. Protegge da DoS locale. Base isolamento risorse.

Altre mappe mentali su Tecnologia