Sistemi Informatici: Una Prospettiva per Programmatore (Edizione Globale)
Un'approfondita analisi su come i sistemi informatici eseguono programmi e memorizzano informazioni. Questo corso colma il divario tra la programmazione a alto livello e l'hardware sottostante, coprendo la rappresentazione a livello macchina, l'architettura del processore, la gerarchia della memoria e la programmazione concorrente.
Panoramica del corso
📚 Riepilogo del contenuto
Un'approfondita analisi su come i sistemi informatici eseguono programmi e memorizzano informazioni. Questo corso colma il divario tra la programmazione ad alto livello e l'hardware sottostante, coprendo la rappresentazione a livello macchina, l'architettura del processore, la gerarchia della memoria e la programmazione concorrente.
Padroneggia l'arte della programmazione di sistema comprendendo l'interfaccia hardware-software.
Autore: Randal E. Bryant, David R. O'Hallaron
Ringraziamenti: Supportato dagli studenti e dai docenti del corso 15-213 presso la Carnegie Mellon University. I ringraziamenti includono contributi di Manasa S. e Mohit Tahiliani.
🎯 Obiettivi di apprendimento
- Identificare come le informazioni sono rappresentate tramite bit e contesto all'interno di un sistema.
- Tracciare le quattro fasi del sistema di compilazione dal codice sorgente all'eseguibile.
- Descrivere la struttura organizzativa dell'hardware e la natura gerarchica dei dispositivi di archiviazione.
- Convertire tra notazioni decimale, binaria e esadecimale e spiegare l'indirizzamento a livello macchina (Endianness).
- Eseguire operazioni a livello di bit e logiche in C e prevedere i risultati degli shift aritmetici.
- Analizzare gli encoding degli interi per identificare vulnerabilità potenziali legate al sovrapposizione e errori di casting.
- Analizzare la mappatura tra costrutti C (cicli, ramificazioni, procedure) e istruzioni assembly x86-64.
- Scomporre lo stack di runtime per spiegare come vengano passati i parametri, memorizzate le variabili locali e gestiti i richiami ricorsivi.
- Valutare i layout di memoria per strutture dati eterogenee e applicare le regole di allineamento per calcolare i requisiti totali di archiviazione.
- Definire lo stato visibile al programmatore Y86-64 e codificare/decodificare istruzioni in sequenze di byte.
🔹 Lezione 1: Un tour dei sistemi informatici
Panoramica: Questa lezione fornisce una panoramica completa su come i sistemi informatici rappresentano le informazioni, traducono i programmi ed eseguono istruzioni attraverso complesse interazioni hardware-software. Esplora il viaggio di un programma dal codice sorgente fino all'esecuzione, il ruolo cruciale della gerarchia della memoria nel superare il divario tra processore e memoria, le astrazioni fornite dal sistema operativo e le leggi matematiche che governano le prestazioni e la parallelizzazione del sistema.
Risultati dell'apprendimento:
- Identificare come le informazioni sono rappresentate tramite bit e contesto all'interno di un sistema.
- Tracciare le quattro fasi del sistema di compilazione dal codice sorgente all'eseguibile.
- Descrivere la struttura organizzativa dell'hardware e la natura gerarchica dei dispositivi di archiviazione.
🔹 Lezione 2: Rappresentazione e manipolazione delle informazioni
Panoramica: Questa lezione esplora come i computer digitali rappresentano e manipolano le informazioni a livello di bit. Copre la transizione dalle notazioni esadecimali e dalle dimensioni delle parole a livello macchina agli encodings complessi degli interi (senza segno e complemento a due) e dei numeri in virgola mobile (IEEE 754). Gli studenti analizzeranno le proprietà matematiche dell'aritmetica informatica, inclusi gli impatti sulla sicurezza derivanti dal sovrapposizione e le sfumature dell'arrotondamento nei sistemi con precisione finita.
Risultati dell'apprendimento:
- Convertire tra notazioni decimale, binaria ed esadecimale e spiegare l'indirizzamento a livello macchina (Endianness).
- Eseguire operazioni a livello di bit e logiche in C e prevedere i risultati degli shift aritmetici.
- Analizzare gli encoding degli interi per identificare vulnerabilità potenziali legate al sovrapposizione e errori di casting.
🔹 Lezione 3: Rappresentazione a livello macchina dei programmi
Panoramica: Questa lezione offre un'analisi approfondita su come i programmi C ad alto livello vengano trasformati in codice macchina x86-64. Copre l'architettura fondamentale del processore, inclusi registri e stack, l'implementazione del flusso di controllo (condizioni, cicli e switch), il funzionamento delle chiamate di procedura e della ricorsione, e la rappresentazione a livello macchina di strutture dati complesse come array, struct e unioni. Inoltre affronta la sicurezza del sistema tramite analisi di buffer overflow e le istruzioni specializzate utilizzate per l'aritmetica in virgola mobile.
Risultati dell'apprendimento:
- Analizzare la mappatura tra costrutti C (cicli, ramificazioni, procedure) e istruzioni assembly x86-64.
- Scomporre lo stack di runtime per spiegare come vengano passati i parametri, memorizzate le variabili locali e gestiti i richiami ricorsivi.
- Valutare i layout di memoria per strutture dati eterogenee e applicare le regole di allineamento per calcolare i requisiti totali di archiviazione.
🔹 Lezione 4: Architettura del processore
Panoramica: Questa lezione esplora l'architettura fondamentale di un processore, concentrandosi sulla transizione da un'implementazione sequenziale (SEQ) a un'implementazione ad alte prestazioni in pipeline (PIPE) utilizzando l'Architettura dell'Instruzione Y86-64 (ISA). Gli studenti analizzeranno come le istruzioni siano codificate, elaborate attraverso fasi discrete (Prelevamento, Decodifica, Esecuzione, Memoria, Scrittura-Ritorno), e come gli hazard hardware siano gestiti tramite logica di controllo, attese e forwarding per massimizzare il throughput.
Risultati dell'apprendimento:
- Definire lo stato visibile al programmatore Y86-64 e codificare/decodificare istruzioni in sequenze di byte.
- Implementare la logica di controllo hardware usando HCL (Hardware Control Language) per circuiti combinatori e sequenziali.
- Tracciare il flusso delle istruzioni attraverso le sei fasi di un processore sequenziale e identificare l'impatto del clocking.
🔹 Lezione 5: Ottimizzazione delle prestazioni del programma
Panoramica: Questa lezione esplora l'approccio sistematico per migliorare le prestazioni del programma comprendendo l'interazione tra codice ad alto livello, compilatori ottimizzanti e architetture dei microprocessori moderni. Gli studenti impareranno a identificare "blocchi di ottimizzazione" come l'aliasing della memoria, applicare trasformazioni a basso livello come il loop unrolling e la riassociazione, e utilizzare strumenti di profiling come GPROF per individuare efficacemente i colli di bottiglia delle prestazioni.
Risultati dell'apprendimento:
- Identificare e mitigare blocchi di ottimizzazione, inclusi aliasing della memoria e overhead delle chiamate di procedura.
- Quantificare le prestazioni del programma utilizzando il metrica Cicli Per Elemento (CPE).
- Applicare trasformazioni come loop unrolling, accumulatore multiplo e riassociazione per sfruttare il parallelismo a livello di istruzione.
🔹 Lezione 6: Gerarchia della memoria
Panoramica: Questa lezione esplora la progettazione strutturale e funzionale della gerarchia della memoria, concentrandosi sui compromessi tra velocità, costo e capacità di archiviazione. Dettaglia le tecnologie che alimentano i sistemi moderni — dalla SRAM e DRAM ai dischi e SSD — e spiega come il Principio di Località (temporale e spaziale) permetta a piccole memorie cache veloci di migliorare significativamente le prestazioni del programma. Gli studenti impareranno ad analizzare la mappatura della cache (Direct-Mapped, Set Associative, Fully Associative) e ad applicare tecniche di ottimizzazione come il riordino dei cicli e il blocco per scrivere codice amico della cache.
Risultati dell'apprendimento:
- Distinguere tra tecnologie di memoria SRAM, DRAM, ROM e Flash e il loro ruolo nella gerarchia.
- Calcolare la capacità di archiviazione del disco e il tempo totale di accesso basandosi sulla geometria e sui componenti operativi.
- Analizzare gli indirizzi di memoria per determinare gli indici di set della cache, i tag e gli offset del blocco in diverse strategie di mappatura.
🔹 Lezione 7: Collegamento
Panoramica: Questa lezione esplora il processo fondamentale a livello di sistema del collegamento, che aggrega codice e dati in un singolo file caricabile in memoria ed eseguibile. Gli studenti passeranno dal codice sorgente all'eseguibile finale, comprendendo come i linkers risolvano i riferimenti simbolici, uniscano sezioni tramite ridirezionamento e gestiscano librerie statiche e dinamiche. La lezione si conclude con tecniche avanzate come l'interposizione di librerie e il codice indipendente dal posizionamento (PIC) utilizzato nelle moderne librerie condivise.
Risultati dell'apprendimento:
- Tracciare la trasformazione dei file sorgente attraverso il driver del compilatore fino all'eseguibile finale.
- Analizzare i file oggetto ELF per identificare tipi di simboli e organizzazione delle sezioni.
- Applicare le regole di risoluzione dei simboli per gestire nomi duplicati e dipendenze al momento del collegamento.
🔹 Lezione 8: Flusso di controllo eccezionale
Panoramica: Questa lezione esplora il Flusso di Controllo Eccezionale (ECF), il meccanismo con cui un sistema informatico reagisce ai cambiamenti nello stato del sistema. Esaminiamo come l'ECF sia implementato a tutti i livelli del sistema, dal software hardware-triggered Eccezioni e Switching di contesto e Controllo di processo a livello di sistema operativo (fork, wait, execve) alle Segnali software e ai Salti non locali. Gli studenti impareranno a gestire la concorrenza, trattare gli errori di sistema e scrivere codice robusto e sicuro per i segnali.
Risultati dell'apprendimento:
- Distinguere tra le quattro categorie di eccezioni a livello hardware (Interruzioni, Interruzioni trap, Faults, Aborts) e i loro meccanismi di gestione.
- Gestire i cicli di vita dei processi tramite chiamate di sistema per creazione (
fork), raccolta (waitpid) ed esecuzione (execve). - Implementare gestori di segnali sicuri che tengano conto della concorrenza, dei segnali non in coda e della sicurezza asincrona.
🔹 Lezione 9: Memoria virtuale
Panoramica: Questa lezione esplora la Memoria Virtuale (VM) come astrazione fondamentale che fornisce a ogni processo uno spazio di indirizzamento grande, continuo e privato. Copre i suoi tre ruoli principali: strumento per una caching efficiente nella DRAM, meccanismo per la gestione e la protezione della memoria, e fondamento per la mappatura della memoria. Inoltre, la lezione approfondisce i meccanismi di traduzione degli indirizzi (TLB), l'allocazione dinamica della memoria (gestione dell'heap) e i principi della raccolta automatica dei rifiuti, concludendo con critiche fallacie relative alla memoria nel linguaggio C.
Risultati dell'apprendimento:
- Distinguere tra indirizzamento fisico e virtuale e descrivere il ruolo dell'Unità di Gestione della Memoria (MMU).
- Eseguire la traduzione da indirizzo virtuale a fisico utilizzando Tabelle delle Pagine e Buffer di Traduzione (TLB).
- Analizzare e implementare strategie di allocazione dinamica della memoria, incluse liste esplicite/implicithe e coalescenza.
🔹 Lezione 10: I/O a livello di sistema
Panoramica: Questa lezione esplora l'interfaccia fondamentale tra il sistema operativo Linux e i programmi applicativi per eseguire input e output. Copre le chiamate di sistema Unix di base, i diversi tipi di file incontrati nel filesystem Linux e le strutture di dati a livello kernel usate per gestirli. Introduce inoltre il pacchetto Robust I/O (RIO) per gestire i "short counts" e fornisce linee guida per scegliere tra I/O standard e I/O a livello di sistema in diversi contesti di programmazione, come la programmazione di rete.
Risultati dell'apprendimento:
- Implementare operazioni di file di base utilizzando l'interfaccia Unix I/O (
open,close,read,write). - Differenziare tra file regolari, directory e collegamenti mentre si interrogano i metadati dei file tramite
stat. - Utilizzare il pacchetto RIO per eseguire operazioni di I/O robuste, bufferizzate e non bufferizzate.
🔹 Lezione 11: Programmazione di rete
Panoramica: Questa lezione esplora l'architettura fondamentale delle applicazioni basate su rete, centrata sul modello Client-Server e Internet Globale IP. Gli studenti impareranno a navigare nell'interfaccia Socket — l'API principale per comunicazioni di rete a livello di sistema — e progrediranno verso l'implementazione di un server web funzionale (TINY) in grado di fornire sia file statici che contenuti dinamici tramite l'Interfaccia Comune Gateway (CGI).
Risultati dell'apprendimento:
- Comprendere il ciclo richiesta-risposta del modello client-server e l'architettura hardware/software dell'Internet Globale IP.
- Manipolare e convertire indirizzi IP, nomi di dominio e strutture socket utilizzando funzioni indipendenti dal protocollo come
getaddrinfo. - Implementare un server web iterativo robusto e programmi CGI che utilizzino il controllo dei processi e la redirezione I/O per servire contenuti dinamici.
🔹 Lezione 12: Programmazione concorrente
Panoramica: Questa lezione esplora i modelli fondamentali di concorrenza: processi, multiplexing I/O e thread. Fornisce un'analisi approfondita della sincronizzazione tramite semafori per risolvere le condizioni di competizione, modelli architettonici comuni come produttore-consumatore e server prethreaded, e le metriche utilizzate per valutare le prestazioni parallele. Infine affronta questioni critiche di affidabilità come la sicurezza dei thread, la rientranza e la prevenzione dei deadlock.
Risultati dell'apprendimento:
- Distinguere tra modelli di concorrenza basati su processi, I/O multiplexed e thread.
- Applicare operazioni di semaforo (P e V) per garantire l'esclusione mutua e risolvere schemi di sincronizzazione.
- Calcolare metriche di prestazioni parallele come il guadagno e l'efficienza sotto diverse leggi di scalabilità.