Didattica

Didattica di Tutanum

Logica e architettura

Memoria, bus e ingresso/uscita

Nella pagina dedicata all'architettura del set di istruzioni e all'assembler abbiamo imparato a parlare con il processore, seguendo il ciclo fetch-decode-execute con cui preleva, decodifica ed esegue le istruzioni, e abbiamo scoperto come quel dialogo diventi possibile quando le parole della macchina vengono trascritte in mnemonici gentili come MOV e ADD. Restava però sospesa una domanda che questa pagina raccoglie con pazienza, e cioè dove quelle istruzioni dimorino mentre aspettano di essere eseguite, come viaggino fino al cuore della CPU e come il calcolatore, chiuso nel suo guscio di metallo, riesca a comunicare con tastiere, dischi e schermi. Scendiamo dunque nel piano sotterrano della costruzione, quello della memoria, dei bus e dell'ingresso/uscita, con la promessa che al termine riconoscerai ogni muro che tiene in piedi la casa quando sollevi il coperchio del tuo computer.

Se ripeniamo per un attimo a quanto abbiamo visto finora, noteremo che la memoria era già ovunque anche quando sembrava assente. Il program counter punta a un indirizzo perché l'istruzione successiva vive in una cella numerata, le istruzioni load e store lette nell'assembler trasportano parole fra registri e memoria come corriere fra banco e magazzino, e persino il programma, una volta assemblato, viene deposto in memoria come puro numero prima di cominciare a respirare. Tre sono i protagonisti di questo piano nascosto, e li presentiamo con le metafore che ti accompagneranno per tutta la pagina, la memoria come il magazzino a ripiani numerati, i bus come le strade fra gli ambienti della bottega, e l'ingresso/uscita, che gli addetti chiamano I/O con una sigla che useremo spesso, come porte, finestre e campanelli verso il mondo esterno.

Cominciamo dal magazzino, con una verità che stupisce chi si avvicina per la prima volta alla macchina, e cioè che esiste una gerarchia di memorie sovrapposte anziché una memoria unica, gerarchia che va dai registri della CPU, accessibili alla velocità del processore e capaci di custodire appena un centinaio di byte, alle cache di pochi kilobyte o megabyte, alla memoria centrale in gigabyte, ai dischi che trattano i terabyte come faccende domestiche, e infine a nastri e dischi ottici talvolta riposti in un cassetto lontano. Scendendo lungo questa scala si muovono insieme tre parametri che reggono ogni decisione di progetto, il tempo di accesso, che dai pochi nanosecondi dei registri sale ai dieci millisecondi del disco e a secondi interi se il supporto va preso e infilato in un lettore, la capacità, che cresce in proporzione opposta, e il costo per byte, che crolla allontanandosi dalla CPU, tanto che il gigabyte su disco costa una frazione di quello in memoria centrale.

Perché accettare una scala così scomoda anziché dotare la macchina di una memoria unica e velocissima? La risposta sta nel principio di località, l'osservazione che i programmi reali toccano la memoria in modo tutt'altro che casuale, e immaginarla è facile pensando a un artigiano al suo banco, con gli attrezzi del giorno sulla scrivania, quelli di rado usati sullo scaffale alle spalle e il magazzino al piano di sotto. Quando la CPU tocca l'indirizzo A è quasi certo che tocchi subito dopo gli indirizzi vicini, perché le istruzioni vengono prelevate in sequenza salvo i salti e i cicli rieseguono poche istruzioni sullo stesso appezzamento di dati. Quando una parola viene letta, essa e le sue vicine passano dalla memoria lenta alla cache, la scrivania della costruzione, e se fra k accessi uno solo raggiunge lo scaffale il tempo medio si schiaccia verso quello della scrivania, come insegna la formula del libro, tempo medio uguale a c più (1 meno h) per m, con c tempo della cache, m tempo della memoria e h frequenza di successi detta hit ratio, mentre uno meno h prende il nome di miss ratio.

Guardiamo com'è fatta la scrivania, perché la cache è una memoria speciale, organizzata a righe dette linee di cache, ciascuna delle quali copia un blocco da 4 a 64 byte consecutivi, così che con linee da 32 byte la linea zero custodisca i byte da 0 a 31 e la linea uno quelli da 32 a 63. Quando la CPU chiede una parola e il circuito che governa la cache la trova, la consegna subito, e l'evento si chiama hit, colpo riuscito, mentre quando la parola manca, evento chiamato miss, viene caricata un'intera linea anziché la sola parola, perché le vicine verranno presto richieste dalla località spaziale e prelevare k parole in un viaggio solo costa meno. Ogni elemento della cache è fatto di tre parti, un bit che dice se il contenuto è valido, perché all'accensione tutta la cache è spazzatura, un'etichetta, il tag, che ricorda da quale indirizzo proviene la copia, e il campo dati, e nella cache più semplice, detta a corrispondenza diretta, l'indirizzo si spezza nei campi etichetta, linea, parola e byte, dove la linea dice in quale unico posto cercare e il tag conferma la provenienza.

La progettazione di una cache è un mestiere di compromessi, e il libro elenca cinque decisioni da prendere prima di progettare. La prima è la grandezza, più la cache è grande e meglio funziona a caro prezzo, la seconda è la dimensione della linea, dato che una cache da 16 kilobyte può dividersi in milleventiquattro linee da 16 byte o in duemilaquarantotto da 8, la terza è l'organizzazione interna, cioè come tener traccia delle linee presenti, la quarta è la scelta fra una cache unificata a spazio condiviso fra istruzioni e dati, e due cache separate, organizzazione chiamata architettura Harvard in onore del Mark III di Howard Aiken, preferita nelle CPU con pipeline, dove il prelievo dell'istruzione e quello dell'operando avvengono nello stesso istante e le istruzioni, intoccate durante l'esecuzione, lasciano quella delle istruzioni sempre pulita. Resta la quinta decisione, il numero delle cache, perché una sola scrivania raramente basta.

Nelle macchine attuali la risposta è un impegno su più piani. Sul chip del processore vivono due cache di primo livello, una per le istruzioni e una per i dati, piccole fra i 16 e i 64 kilobyte e aperte in un lampo perché distano pochi micrometri dal cuore. Fuori dal chip, nello stesso involucro, sta la cache di secondo livello, unificata, fra 512 kilobyte e un megabyte, e su schede generose si aggiunge una terza da alcuni megabyte di SRAM, la memoria statica che fra poco incontreremo. Le cache sono annidate, il contenuto della prima sta dentro la seconda e quello della seconda dentro la terza, così quando la prima manca il bersaglio la seconda ha buone probabilità di averlo in casa e il viaggio allo scaffale diventa un'eccezione sempre più rara.

Per costruire questi ripiani servono due materiali diversi, e capirne la differenza illumina mezza architettura. La RAM statica, detta SRAM, è fatta di circuiti simili ai flip-flop incontrati nella pagina sul sequenziale, sei transistor per bit che tengono fermo il valore finché arriva corrente, velocissimi, dell'ordine dei nanosecondi, ma costosi e ingombranti come un banco in falegnameria, per cui vivono nei piani alti della gerarchia. La RAM dinamica, detta DRAM, ragiona come un setaccio più economico, un transistor e un minuscolo condensatore per ogni bit, densa e a buon mercato, ma la carica cola via come acqua da un secchio bucato e va rabboccata, operazione detta refresh, ogni pochi millisecondi, pena la perdita dei dati, con tempi più lunghi e un'interfaccia più complessa. Per questo la memoria centrale è quasi sempre DRAM, capiente ed economica, mentre le cache sono SRAM, costose e fulminee, e l'accoppiata dei due materiali è il trucco che rende sopportabile il divario fra processore e magazzino.

I chip DRAM di grandi dimensioni nascondono un artificio, perché organizzano le celle come una scacchiera e, per risparmiare piedini, forniscono l'indirizzo in due tempi, prima il numero di riga asserendo il segnale RAS, Row Address Strobe, poi quello di colonna asserendo il CAS, Column Address Strobe, a prezzo di due cicli invece di uno. La stirpe si è evoluta sotto la pressione del processore. Le prime FPM lavoravano in modo asincrono, con segnali espliciti che dicevano al chip quando rispondere. Le EDO avviavano un riferimento prima del completamento del precedente, una pipeline modesta che raddoppia la portata lasciando il singolo accesso al suo passo. Le SDRAM entrarono in sintonia col clock del sistema, così la CPU dice per quanti cicli lavorare e i segnali di controllo spariscono, e le DDR, a doppio tasso, trasmettono sul fronte di salita e su quello di discesa dell'orologio, raddoppiando la banda a parità di frequenza, tanto che un chip largo 8 bit a 200 megahertz sfiora i 3,2 gigabit al secondo. Per comodo i chip vengono saldati su schede vendute come moduli, le SIMM e le DIMM, con contatti su uno o due lati e, nei portatili, le sorelle compatte SO-DIMM.

C'è una famiglia di memorie che rifiuta di dimenticare quando la corrente manca, ed è quella delle memorie di sola lettura, nata per elettrodomestici, giocattoli e automobili, dove programma e dati devono sopravvivere allo spegnimento senza che nessuno li riscriva mai. La ROM viene scritta in fabbrica con una maschera incisa nel silicio, economica a grandi volumi ma rigida come ceramica, perché cambiare un bit significa buttare il chip. La PROM è programmabile una volta sola sul posto e custodisce minuscoli fusibili che un'alta tensione fa saltare selezionando righe e colonne. La EPROM compie il passo successivo, perché la sua finestrella di quarzo lascia entrare luce ultravioletta che in quindici minuti cancella tutti i bit riportandoli a uno, e per questo è la regina delle prove ripetute. La EEPROM cancella per via elettrica, byte per byte e senza togliere il chip dal circuito, pagando lo scambio con capacità sessantaquattro volte minore e velocità dimezzata, per cui resta di nicchia. Ultima nata, la memoria flash cancella a blocchi, equipaggia schede da un gigabyte e raggiunge i 50 nanosecondi, e i progettisti la guardano come la candidata a sostituire i dischi, con un avvertimento, perché dopo circa centomila cancellazioni i suoi blocchi si esauriscono, mentre i dischi durano anni a prescindere da quanto li riempi.

Lasciamo i magazzini e percorriamo le strade, perché ogni pezzo della costruzione comunica attraverso i bus, collegamenti elettrici condivisi fra più dispositivi, alcuni interni alla CPU, altri esterni per legare il processore alla memoria e alle periferiche. I primi calcolatori domestici avevano un unico bus di sistema, un mazzo di cinquanta o cento fili paralleli piantati nella scheda madre con connettori dove infilare memorie e schede, mentre i computer moderni separano almeno un bus fra CPU e memoria da uno o più bus per le periferiche. Perché schede di aziende diverse funzionino insieme, il bus obbedisce a un protocollo, un regolamento preciso con specifiche meccaniche ed elettriche su tensioni, tempi e connettori, e la storia è costellata di bus celebri, dall'Omnibus del PDP-8 all'Unibus del PDP-11, dal PCI alle sue discendenze, tanti che Tanenbaum osserva che il mondo sarebbe un posto migliore se quasi tutti sparissero lasciandone uno o due.

Su ogni bus gli abitanti si dividono in due mestieri, i master, attivi e capaci di iniziare un trasferimento, e gli slave, passivi e in attesa di essere interrogati, e il ruolo dipende dal momento, perché nella lettura di un blocco ordinata al controllore del disco la CPU fa la master e il controllore lo slave, mentre un istante dopo, quando lo stesso controllore chiede alla memoria di accettare le parole lette, i ruoli si scambiano, con una regola fissa, e cioè che la memoria non può mai fungere da master. I segnali si ripartiscono in tre corsie, il bus di indirizzi che dice quale cella o dispositivo interessa, il bus di dati che trasporta il valore, e il bus di controllo che comunica se si tratta di lettura o scrittura e con quali tempi. L'ampiezza del bus di indirizzi decide quanta memoria la CPU tocca direttamente, dato che con n fili si raggiungono due alla n locazioni, e qui bisogna scegliere fra costo e futuro, come mostra la storia dei PC IBM, partiti con venti linee e un megabyte e cresciuti a sedici e poi oltre aggiungendo quattro e otto linee, una crescita a scarponi lontana dall'ordine di trentadue linee progettate da zero.

Anche il bus di dati deve crescere per saziare la sete di banda, e qui affronta un nemico subdolo chiamato disallineamento del bus, cioè segnali lanciati insieme che arrivano con ritardi leggermente diversi, trascurabile a basse frequenze e muro di mattoni quando il clock accelera, tanto che la via pratica diventa aggiungere linee, da otto a sedici e poi a trentadue bit. Chi risparmia fili adotta il bus multiplexato, dove le stesse linee portano prima l'indirizzo e poi il dato, a prezzo di operazioni più lente. Quanto al ritmo, i bus si dividono in sincroni, guidati da un oscillatore a cristalli fra i 5 e i 100 megahertz, e asincroni, senza orologio, con un dialogo di disponibilità chiamato handshaking, stretta di mano fra master e slave. Quando più master desiderano la strada insieme entra in scena l'arbitro, che assegna il bus alla richiesta più urgente. E le operazioni superano la semplice lettura e scrittura, perché esistono cicli a blocchi, con cui una cache intera si carica in un colpo solo, cicli leggi-modifica-scrivi per agganciare variabili condivise senza mai mollare la strada, e cicli dedicati agli interrupt, il campanello della bottega di cui fra poco raccontiamo.

La filosofia dei bus paralleli, tanti fili che trasportano tanti bit a ogni colpo di clock, ha regnato per decenni. Sotto la pressione delle frequenze i suoi difetti si sono fatti sentire, disallineamento, interferenze fra cavi, capacità parassite, e i progettisti hanno capovolto la prospettiva scegliendo la via seriale, pochi fili sparati a velocità folli, il cui risultato più importante si chiama PCI Express, che gli stessi progettisti ammettono essere diverso da un vero bus, un commutatore, un piccolo centralino che collega ogni chip con una linea seriale dedicata punto a punto, come nelle reti Ethernet. Ogni collegamento è una coppia di canali unidirezionali, uno per senso di marcia, di due fili ciascuno, segnale e terra contro il rumore, e le coppie possono essere una, due, quattro, otto, sedici o trentadue, con la prima generazione obbligata a 2,5 gigabit al secondo per direzione. Al posto dei segnali di controllo viaggiano pacchetti con intestazione e campo dati, e poiché manca un clock comune i bit vengono codificati otto bit su dieci per tenere sincronizzati mittente e destinatario. Sopra il livello fisico, un livello di trasmissione aggiunge numero di sequenza e rilevazione degli errori, con conferme e ritrasmissioni, e un livello di transazione gestisce quattro spazi d'indirizzi, memoria, I/O, configurazione e messaggi, il che permette l'inserimento a caldo dei dispositivi.

Alle periferiche lente e quotidiane, tastiere, mouse, stampanti, macchine fotografiche, PCI Express sarebbe sprecato, e per loro nel 1993 sette aziende progettarono lo standard USB, rilasciato nel 1998, con obiettivi che suonano come i desideri di chi è stanco di smontare il case, niente ponticelli, un solo cavo che porti anche l'alimentazione, fino a centoventisette dispositivi per macchina, installabili a caldo e senza riavvio. La topologia è un albero con un hub radice e cavi di quattro fili, due per i dati, uno per i cinque volt e uno per la terra, e lo zero viaggia come una transizione e l'uno come assenza di transizione, così anche una fila di zeri mantiene il ritmo. Quando infili una periferica, l'hub se ne accorge, il sistema operativo la interroga, le assegna un indirizzo fra uno e centoventisette e ne scarica la configurazione, e da quel momento i dati viaggiano in cornici ogni millisecondo, di quattro tipi a seconda che servano controllo, tempo reale o trasferimenti corposi. La stessa filosofia seriale governa i dischi, dove lo standard SATA ha sostituito il vecchio nastro parallelo con una coppia di fili sottili, e ogni cavo dentro un case è oggi una conversazione punto a punto, lontana dai giorni in cui trenta fili condividevano ogni dubbio.

Torniamo al magazzino per osservare il gesto più frequente di tutta la macchina, il ciclo di lettura e scrittura della memoria, che da vicino è una cerimonia di segnali paziente come un passaggio di consegne fra artigiani. Un chip di memoria porta piedini per indirizzo, piedini per dati e tre inviti di controllo, il segnale CS che lo seleziona, il segnale RD, detto anche OE, che dichiara una lettura e abilita le uscite, e il segnale WE che dichiara una scrittura. In lettura la CPU pone l'indirizzo e asserisce CS e RD, il decodificatore interno seleziona la parola giusta, e i buffer a tre stati sulle uscite, interruttori elettronici che per pochi nanosecondi lasciano passare la parola come una pinza tagliafili che si richiude da sola. In scrittura i ruoli si scambiano, perché la CPU pone indirizzo e dato e asserisce CS lasciando spento il segnale di lettura, il che carica il dato nei flip-flop della parola selezionata mentre i buffer di uscita restano scollegati, così il chip evita di combattere contro i dati in arrivo. Nei chip DRAM la cerimonia raddoppia, con l'indirizzo di riga sotto il colpo di RAS e quello di colonna sotto il CAS, a prezzo di due tempi, e chi progetta memorie parla di pagine, dato che restare nella stessa riga accede a tutta la pagina senza nuova cerimonia.

Se la memoria è il magazzino ben educato, le periferiche sono ospiti complicati, e per conversare con loro la CPU si affida a un ceto di interpreti chiamati controllori, chip che traducono gli ordini del bus nei linguaggi dei singoli apparecchi. Fra loro spiccano gli UART, ricetrasmettitori asincroni universali che prendono un byte dal bus di dati e lo sputano un bit alla volta su una linea seriale verso un terminale, con velocità e formato dei caratteri configurabili via software. L'esempio da manuale è il chip parallelo 8255A, con ventiquattro linee di I/O a cui si agganciano tastiere, interruttori e luci, organizzate in tre porte da otto bit, A, B e C, ognuna con il suo registro. Scrivere un numero nel registro della porta A fa apparire quel numero sulle linee, e lì resta fermo finché non lo riscrivi, leggere il registro campiona lo stato delle linee in ingresso, e una modalità con handshaking aspetta il cenno del dispositivo prima del byte successivo. Il chip si aggancia al bus con otto linee dati, i segnali di lettura e scrittura, la selezione del chip e due linee d'indirizzo per le tre porte e il registro di controllo, il che significa che, visto dall'esterno, il controllore è un pezzo di memoria con indirizzi tutti suoi.

Per capire come la CPU tratta con questi interpreti conviene osservare una tastiera, la periferica più umile e la più chiara. Il suo controllore custodisce almeno due registri, un registro dati che tiene il carattere appena battuto e un registro di stato il cui bit più significativo l'hardware alza a ogni carattere che arriva, così il software sa se c'è novità. La maniera più antica di gestirla si chiama I/O programmato, ed è un ciclo serrato in cui la CPU legge il registro di stato e continua a leggerlo finché il bit non sale a uno, per poi pescare il carattere dal registro dati, operazione che azzera il bit e fa ricominciare il giro. L'attesa attiva, chiamata busy waiting, è tollerabile per un piccolo controllore dedicato e diventa uno spreco quando sul computer girano altri programmi in attesa della CPU. La via d'uscita è farsi avvisare invece di controllare, ed è la storia degli interrupt che stiamo per raccontare.

Un interrupt è un cambiamento del flusso di esecuzione provocato dall'hardware anziché dal programma che sta girando, e la distinzione conta, perché il programma causa le trap, imprevisti sincroni che si ripetono puntuali a ogni riesecuzione con gli stessi dati, mentre gli interrupt sono asincroni, imprevedibili nel momento, e un programma rieseguito un milione di volte li vedrà spostarsi a seconda del momento in cui viene premuto invio. Ricostruiamo la scena con la riga di caratteri da mandare allo schermo incontrata poco fa, per cui il software riempie un buffer, prepara puntatore e contatore, spedisce il primo carattere al terminale e poi dimentica la faccenda, libero di far girare un altro programma, mentre il controllore dello schermo, appena mostrato il carattere, suona il campanello. Il requisito che rende civile il meccanismo si chiama trasparenza, e significa che, a racconto finito, la macchina riparte esattamente dallo stato di prima come se l'intermezzo non fosse mai accaduto.

Il campanello ha una coreografia precisa, ed è uno dei meccanismi più eleganti della costruzione. Quando il dispositivo è pronto, il suo controllore alza una linea di interrupt sul bus, e la CPU, quando può occuparsene, risponde con un segnale di conferma, allorché il controllore depone sulle linee dati un piccolo intero che lo identifica, il vettore di interrupt. La CPU salva allora il program counter e la parola di stato, il registro PSW, sulla pila del sistema, usa il vettore come indice in una tabella di puntatori per trovare l'indirizzo della routine di servizio, e salta lì. La routine salva i registri che userà, perché il programma interrotto li rivuole intatti, scopre quale dispositivo esatto l'ha chiamata, dato che il vettore è condiviso dai dispositivi dello stesso tipo, legge lo stato, aggiorna puntatore e contatore, spedisce il carattere successivo, rimette a posto i registri e conclude con l'istruzione di ritorno da interrupt, che ripristina program counter e stato, così il programma interrotto riprende il filo del suo discorso.

Con un solo dispositivo la storia finirebbe qui, ma un computer ospita decine di campanelli, e a volte uno suona mentre l'artigiano serve un altro cliente. La prima strategia fa chiudere la porta a ogni routine prima ancora di salvare i registri, così gli interrupt vengono serviti in fila rigorosa, a rischio però di far perdere dati ai dispositivi dal tempo critico, come una linea seriale che riceve caratteri a ritmo serrato. La strategia più raffinata assegna a ogni dispositivo una priorità e fa girare la routine al livello del dispositivo, con la CPU che registra il proprio livello nella PSW, così gli interrupt di priorità inferiore restano in attesa pazienti mentre quelli superiori interrompono le routine di servizio. L'esempio del libro dà alla stampante priorità due, alla linea seriale cinque e al disco quattro, e in trenta secondi di giornata tipo si vede la linea interrompere la stampante, il disco attendere il suo turno e il programma dell'utente riprendere convinto di non essersi mai fermato.

A partire dall'8088 le CPU Intel dividono i campanelli in due ordini di grandezza, gli interrupt mascherabili, usati da tutti i dispositivi di I/O e temporaneamente ignorabili, e gli interrupt non mascherabili, riservati agli eventi quasi catastrofici come l'errore di parità della memoria, che la CPU è obbligata a ascoltare subito. La mascherabilità si regola con un bit della PSW, che permette al software di imporsi silenzio, e sul Pentium il vettore indicizza una tabella di duecentocinquantasei elementi da otto byte. Resta il problema che un solo livello mascherabile non basta a esprimere le priorità, e qui entra in scena il controllore di interrupt 8259A, discendente di quello del PC IBM. Il chip raccoglie otto ingressi di richiesta, e quando uno si alza lui alza il pin di interrupt della CPU, che alla conferma risponde con un impulso di riconoscimento, e allora il controllore depone il numero del dispositivo sul bus dati in un ciclo speciale. Dentro custodisce registri per mascherare singole linee o cambiarne le regole, e a fine servizio il software deve inviare un comando esplicito, altrimenti il controllore tiene il telefono occupato, e in cascata otto chip servono sessantaquattro dispositivi.

Anche servito dagli interrupt, però, ogni carattere costa una cerimonia completa, e quando i dispositivi diventano veloci come i dischi la CPU passerebbe le giornate a fare da postino fra memoria e periferica. La soluzione consiste nel tornare all'I/O programmato affidandolo a qualcun altro, e quel qualcuno è il controllore DMA, Direct Memory Access, accesso diretto alla memoria, che prende il posto della CPU nei trasferimenti corposi. Il controllore custodisce almeno quattro registri, l'indirizzo di partenza in memoria, il numero di byte da trasferire, il numero del dispositivo e la direzione del viaggio, e per spedire trentadue byte dalla cella cento al terminale numero quattro la CPU scrive quei quattro numeri. Da quel momento il controllore chiede il bus, legge un byte dalla memoria, lo scrive nel dispositivo, sposta l'indirizzo di uno, abbassa il contatore di uno, e ripete il giro finché il contatore non si azzera, e alla fine suona il campanello. Il prezzo d'esercizio si chiama furto di cicli, perché il DMA ha priorità di bus superiore a quella della CPU, dato che i dispositivi tollerano male l'attesa, e durante i suoi trasferimenti il processore resta in coda, ma risparmiare un interrupt a ogni byte ripaga il pedaggio molte volte.

Resta la domanda da manuale d'officina su come la CPU indirizzi i suoi interpreti, e la storia offre due filosofie. La prima, chiamata I/O mappato in memoria, tratta i registri dei dispositivi come celle della memoria, assegnando loro indirizzi veri, così le normali istruzioni di lettura e scrittura raggiungono le porte del controllore senza vocabolario aggiuntivo, e la seconda, detta a porte isolate, riserva allo spazio di I/O un indirizzamento separato con istruzioni dedicate, come IN e OUT, e linee di controllo che dicono se si parla alla memoria o a una periferica. Il libro costruisce per esercizio un calcolatore minuscolo a sedici bit con una EPROM da 2 kilobyte per il programma in fondo allo spazio degli indirizzi, una RAM a metà strada e il chip PIO negli ultimi quattro byte, e osserva che dal punto di vista del programmatore la scelta è quasi arbitraria, mentre dal punto di vista dell'interfaccia cambia tutto, perché usare il PIO con porte isolate richiede uno spazio separato e segnali extra sul bus, mentre mapparlo in memoria gli regala quattro indirizzi a costo zero. Le due scuole convivono ancora oggi nei processori delle nostre macchine.

Chi costruisce quel calcolatore deve risolvere un problema elettrico garbato, la decodifica dell'indirizzo, perché ogni chip sul bus deve sapere quando si sta parlando a lui. La EPROM risponde a ogni indirizzo i cui cinque bit più significativi sono zero, e il segnale di selezione può nascere da un comparatore o da una porta NOR a otto ingressi. La RAM richiede il primo bit a uno, e il PIO chiede di riconoscere quattro indirizzi soltanto, quelli che terminano con undici uno. Se il sistema è composto da soli tre chip, entra in gioco la decodifica parziale, che sfrutta il fatto che ogni indirizzo che comincia con uno zero sta nella EPROM, ogni indirizzo che comincia con zero e uno sta nella RAM, e ogni indirizzo che comincia con due uno sta nel PIO, così bastano due porte NAND e un invertitore per governare tutto il sistema. Il risparmio ha un prezzo scritto nel contratto, perché la decodifica parziale risponde a indirizzi multipli, fatto innocuo finché restano inutilizzati e vincolante il giorno in cui vorrai espandere la macchina, e per i sistemi che devono crescere conviene un decodificatore vero che spartisca lo spazio in otto regioni da 8 kilobyte.

La teoria, per quanto raccontata con calore, resta nebbia finché non la tocchi, quindi adesso appoggia il racconto sul banco e vieni con noi davanti al tuo computer per sei esperimenti che chiedono solo un terminale e un po' di curiosità, ordinati dal più semplice al più articolato, e ciascuno ti farà vedere con gli occhi un pezzo della bottega descritta. Se usi Linux o un altro sistema della famiglia Unix troverai tutto pronto, e se il tuo sistema è diverso ti basterà cercare strumenti equivalenti, perché i concetti appresi viaggiano intatti da una macchina all'altra come buoni ricettari di famiglia.

Per il primo esperimento apri il terminale e scrivi lscpu, il comando che presenta il processore come una scheda d'identità, e leggi con occhi nuovi le righe delle cache, L1d per i dati e L1i per le istruzioni del primo livello, L2 e L3 per i piani più alti, con le dimensioni che dovrebbero richiamarti la scala della gerarchia. Nota con piacere che L1d e L1i compaiono separate, perché hai appena visto l'architettura Harvard senza smontare nulla, poi confronta i numeri con quanto raccontato e verifica che la realtà rispetti il disegno. Per salutare la memoria centrale scrivi free -h e guarda quanti gigabyte di DRAM la tua macchina tiene accesi per te, pensando a tutti i condensatori rabboccati ogni pochi millisecondi pur di non dimenticare nulla.

Per il secondo esperimento, se hai i permessi di amministratore, scrivi sudo dmidecode --type memory e lasciati presentare la famiglia DRAM completa, con ogni modulo DIMM o SO-DIMM raccontato nel dettaglio, la generazione DDR, la velocità di trasferimento, il produttore e persino il numero di serie. Confronta quello che leggi con la storia delle RAM raccontata sopra e cerca il segno della doppia cresta di clock della DDR, riflettendo sul fatto che quella schedina, che sembra un cioccolatino di plastica, discende direttamente dalla scacchiera di condensatori con indirizzi di riga e colonna. Se il comando ti viene negato, prova con lsmem per avere almeno il riepilogo delle dimensioni, e ricorda che anche questa piccola umiliazione amministrativa fa parte del mestiere.

Per il terzo esperimento tocca alle strade, quindi scrivi lspci per vedere i dispositivi appesi ai collegamenti PCI Express della tua macchina, schede video, controller di rete, dischi veloci, e osserva che ognuno occupa una corsia dedicata verso il commutatore, esattamente come nel centralino descritto. Con lspci -vv puoi addirittura leggere la larghezza del collegamento, x1, x4, x16, e la velocità negoziata per direzione, mentre lsusb e lsusb -t mostrano dall'altra parte la radice USB con il suo albero di hub, i dispositivi configurati con i loro indirizzi e la storia di ogni porta, così da rendere concreta la differenza fra il centralino seriale veloce e il giardino delle periferiche lente.

Per il quarto esperimento misura il divario fra scrivania e scaffale misurando il tuo disco, e scrivi dd if=/dev/zero of=/tmp/prova.bin bs=1M count=1024 conv=fdatasync per creare un file di un gigabyte e osservare la velocità di scrittura stampata a fine corsa, un numero in megabyte al secondo che dovrebbe sembrare lento persino alla tua memoria più paziente, il che dimostra con una prova il salto enorme fra il mondo elettronico, quello meccanico e quello flash. Con lsblk -d -o NAME,ROTA,SIZE,MODEL scopri quali dischi sono ancora magnetici rotanti, con ROTA che vale uno, e quali sono già memoria flash con ROTA a zero, pensando alla promessa e alla stanchezza dei blocchi raccontate sopra, e ricordati di liberare lo spazio con rm /tmp/prova.bin quando hai finito, perché la bottega va lasciata pulita.

Per il quinto esperimento osserva i campanelli suonare in diretta con cat /proc/interrupts, il file che il kernel espone come un registro del portiere, con una riga per ogni linea di interrupt e una colonna per ogni core, e i contatori che crescono sotto i tuoi occhi. Premi qualche tasto, muovi il mouse, scarica qualcosa dalla rete e guarda quali righe si animano, riconoscendo la linea della tastiera, quella della scheda di rete, i timer che battono il ritmo e persino la riga NMI che vigila sui guasti, e prova a immaginare il vettore, la tabella, la pila e il ritorno da interrupt che avvengono a ogni incremento. Quando avrai visto salire i contatori saprai che la coreografia descritta sopra si ripete nel tuo computer centinaia di volte al secondo, ognuna invisibile e perfettamente trasparente.

Per il sesto e ultimo esperimento apri cat /proc/iomem e osserva la mappa degli indirizzi fisici della tua macchina, con le regioni riservate ai dispositivi, alle finestre di configurazione e alla memoria vera, una fotografia dello spazio di indirizzamento che il libro costruisce per esercizio e che qui ti si svela intero, poi confrontala con cat /proc/ioports sulle macchine che ancora lo espongono, dove vive lo spazio delle porte isolate dell'architettura Intel, e nota come i due mondi, quello mappato in memoria e quello a porte separate, convivano pacificamente nello stesso computer da quarant'anni. Se vuoi il quadro completo, sudo lspci -vv mostra per ogni dispositivo le regioni di memoria assegnate, con dimensioni e flag, e ti restituirà la sensazione giusta, quella di chi, avendo capito come sono fatti muri e strade, entra in una casa e riconosce ogni vano senza bisogno di pianta.

Questa pagina attinge a Architettura dei calcolatori di Andrew S. Tanenbaum, e in modo specifico al capitolo 2, che presenta l'organizzazione dei sistemi di calcolo con la gerarchia di memoria e i dischi, al capitolo 3, con i paragrafi sulla memoria, sui chip della CPU e sui bus, sugli esempi di bus come PCI Express e USB e sulle interfacce con la decodifica degli indirizzi, al capitolo 4, dove il paragrafo sul miglioramento delle prestazioni tratta le cache a più livelli, e al capitolo 5, dove i paragrafi sui tipi di istruzioni e sul controllo del flusso raccontano l'ingresso/uscita programmato, gli interrupt con i loro vettori e le priorità, e l'accesso diretto alla memoria.

Con la memoria, le strade, i portieri e i corrieri diretti, la macchina che hai davanti dovrebbe aver smesso di essere una scatola magica ed essere diventata quello che è sempre stata, una bottega ordinata dove ogni gesto ha un mestiere e ogni mestiere ha un motivo che puoi raccontare a tua volta. Resta però la domanda più alta, quella che chiede perché questa architettura e non un'altra, chi l'ha sognata per primo e quali altri cervelli, lontani dai flip-flop e dai campanelli, cercano ancora di imitarla con idee come l'apprendimento, ed è la domanda che ti aspetta proseguendo verso la pagina successiva, dove Neumann e Hebb ti racconteranno come si disegna una mente e come una mente si disegna da sola.