Architettura dei calcolatori/Architettura dei processori

Wikibooks, manuali e libri di testo liberi.
Jump to navigation Jump to search

Architettura dei processori[modifica]

Searchtool.svg Per approfondire, vedi Architetture dei processori.

A fronte dei comuni processori CISC (complete instruction set computer) che forniscono la possibilità di eseguire un gran numero di operazioni macchina diverse (anche 500 o più) ciascuna con proprie caratteristiche e modalità di accesso agli operandi e scrittura dei risultati; ci sono i processori RISC (reduced instruction set computer) in cui si diminuisce il numero di istruzioni disponibili cercando di aumentare il grado di parallelismo dell'esecuzione delle istruzioni stesse consentendo una maggiore efficienza a parità di frequenza di clock.

Si definisce \emph{MIPS} (millions instruction per second) un indice della potenza di calcolo di un computer che dipende dalla frequenza di calcolo e da come è microprogrammato (non è molto indicativo); \emph{benchmark} sono programmi che valutano le prestazioni di un microprocessore; \emph{throughput} il numero di istruzioni eseguite al secondo.

Per migliorare le prestazioni di un computer si può incrementare la frequenza di clock, ma non è possibile farlo arbitrariamente perché ad ogni ciclo di clock si caricano le capacità parassite dei componenti elettronici causando dissipazione di potenza che produce calore.

L'ideale sarebbe poter eseguire una istruzione per ogni ciclo di clock (throughput $\frac{1}{T}$) ma la maggior parte delle istruzioni richiede più cicli di clock; è possibile però iniziare una nuova istruzione anche se la precedente non è ancora terminata, organizzando le istruzioni in una pipeline.

L'esecuzione di una istruzione comprende varie fasi: \begin{itemize} \item \emph{fetch} (F): accesso alla memoria tramite il bus per ricevere il codice operativo (o \emph{opcode}), dura circa 3 cicli e richiede l'uso dei registri OPCODE e MJR oltre al bus; \item \emph{decode} (D): analizzare il codice dell'istruzione per stabilire le modalità di prelievo degli operandi, richiede l'accesso ai registri OPCODE e MJR; \item \emph{read} (R): accesso al bus per il prelievo degli operandi e traduzione degli operandi immediati, occupa un numero variabile di cicli di clock dipendente dall'istruzione e occupa il bus ed i registri per gli operandi; \item \emph{execute} (X): operazioni interne al processore di durata variabile, occupa le unità aritmetiche e logiche del processore (ALU e FPU) e i registri operandi; \item \emph{write} (W): scrittura dei risultati, può richiedere o meno l'accesso alla memoria, occupa il bus ed i registri degli operandi. \end{itemize} Queste fasi in sequenza comportano che una istruzione necessiti di circa 10 cicli di clock per il suo completamento.

Per rendere parallela l'esecuzione delle istruzioni si divide il calcolatore in più unità indipendenti con la possibilità di comunicare tra di loro; la risorsa più frequentemente utilizzata è il bus, quindi si identifica un'unità specifica del processore deputata alla gestione degli accessi al bus: l'\emph{unità di accesso al bus} (UAB); le altre operazioni si dividono in due unità: l'\emph{unità di fetch} (FETCH), contenente di registri OPCODE e MJR e deputata allo svolgimento delle fasi di fetch e di decode, e l'\emph{unità aritmetico-logica} (ALU} contenente i registri operandi e deputata all'esecuzione delle istruzioni (questa è l'unità più complessa).

Con queste modifiche è possibile sovrapporre la fase di fetch di una istruzione con la fase di esecuzione della precedente, a condizione che la durata della fase di fetch sia minore della durata della fase di esecuzione (e questo non è sempre vero, visto che la fase di fetch occupa circa tre cicli di clock e che istruzioni molto semplici possono richiedere uno o due cicli di clock)

\begin{tabular}{cccccccc} F & D & R & X & W & & & \\

 &   &   & F & D & R & X & W \\

\end{tabular}

Una sovrapposizione ulteriore non è possibile perché si avrebbero contemporaneamente read e write e anche read e fetch; per risolvere questo problema si cambia la struttura.

\subsection{Architettura Harward} Si usano due bus distinti per svolgere contemporaneamente le fasi fetch e read, con due banchi di memoria separati: uno che contiene le istruzioni ed uno che contiene i dati; sono quindi necessarie due unita di accesso al bus, la UAB dati e la UAB istruzioni, ciascuna con il proprio bus e la propria memoria.

Tuttavia in questo modo non è possibile ancora svolgere contemporaneamente lettura e scrittura dei dati; la soluzione è eliminare la fase di scrittura, aumentando il numero di registri per gli operanti e sostituendo le istruzioni che scrivono il risultato in memoria con istruzioni che scrivono sui registri operandi; le scritture in memoria saranno fatte da istruzioni specifiche che scrivono in memoria durante la fase di esecuzione, la fase di write è così sovrapposta alla fase di decode, che non interessa il bus.

\begin{tabular}{cccccccc}

F & D & R & X &   &   &   &   \\
  &   & F & D & R & W &   &   \\
  &   &   &   & F & D & R & X \\

\end{tabular}

Questa soluzione aumenta il costo del processore, che necessita di due bus e di più registri.

\subsection{Coda di prefetch} Una soluzione differente deriva dall'osservazione che durante la fase di esecuzione sono si utilizza il bus, e quindi questo può essere utilizzato per anticipare il caricamento di istruzioni che verranno eseguite successivamente.

Si aggiunge un'\emph{unità di prefetch} che condivide l'uso del bus con l'unità di accesso al bus e che quando il bus è libero carica in un suo buffer interno le istruzioni successive a quella in esecuzione, in questo modo la fase di fetch non richiede l'accesso al bus ma solo il prelievo delle istruzioni dalla coda di prefetch.

L'unità di prefetch opera prima della fase di fetch nella fase di prefetch (P) anche in questa soluzione si elimina la fase di scrittura ametnando il numero di registri ed introducendo istruzioni di scrittura in memoria specializzate.

\begin{tabular}{ccccccccc}

P & F & D & R & X &   &   &   &   \\
  &   & P & F & D & R & W &   &   \\
  &   &   &   & P & F & D & R & X \\

\end{tabular}

Spesso nella coda di prefetch si caricano anche gli operandi che le istruzioni prevedono, anche perché la fase di prefetch si sovrappone a quella di esecuzione che spesso è più lunga e quindi si possono caricare più istruzioni e dati; la coda di prefetch diventa quindi un buffer che si interpone tra la memoria e gli altri moduli.

Perché questa ottimizzazione sia possibile è necessario che la coda di prefetch non sia mai vuota, il che è difficile in corrispondenza delle istruzioni di salto, quando non si può stabilire esattamente a priori la prossima istruzione; inoltre le varie fasi devono essere di durata costante, il che non è spesso vero per le fasi di esecuzione e lettura, la cui durata dipende dalla complessità dell'istruzione, dal numero e tipo di operandi e anche dalla configurazione complessiva del sistema (numero di dispositivi connessi al bus).

È necessario sincronizzare le varie fasi, ritardando l'esecuzione quando una fase dura più del normale, si introducono dei tempi morti o \emph{bubbles} (possono esere pensati come istruzioni NOP); comunque è meglio che le varie istruzioni abbiano la stessa durata.

\section{Organizzazione della memoria per l'accesso a burst} Avendo a disposizione pi\`u moduli di memoria, per rendere pi\`u rapida la memorianel caso di trasferimenti a burst è conveniente usare i bit meno significativi (invece dei pi\`u significativi) dell'indirizzo per selezionare il modulo, in questo modo indirizzi consecutivi stanno su moduli diversi ed il multiplexer che seleziona il modulo, visto che in un accesso a burst i bit pi\`u significativi sono stabili per un tempo maggiore, si trova ad avere tutti gli ingressi stabili e l'indirizzo può variare pi\`u rapidamente.

Il difetto di questa soluzione è che la memoria è meno espandibile ed affidabile, se un modulo si guasta l'intera memoria non funziona pi\`u.

\chapter{Processori RISC} \section{Caratteristiche generali} L'opera di progettazione volta a migliorare ancora il grado di parallelismo ha portato allo sviluppo dei \emph{processori RISC} (reduced instruction set computer), in grado di eseguire un piccolo numero di istruzioni macchina; questo è possibile considerando che gran parte delle istruzioni di un processore CISC spesso non vengono usate o possono essere eseguite componendone altre. Lo scopo è ottenere istruzioni con fasi di durata costante, eliminare la fase di decodifica, limitare gli accessi in memoria. \section{Istruzioni macchina} Si impone un unico formato per le istruzioni, in maniera da rendere costante la durata della fase di fetch: ad esempio supponiamo di dedicare 11 bit al codice operativo, 6 al tipo, 16 agli operandi.

Si elimina la fase di lettura stabilendo che tutte le istruzioni operino sui registri; potrebbe essere necessario aumentare il numero di bit associati agli operandi, visto che è necessario indirizzare i vari registri.

La fase di decodifica non è più necessaria perché le istruzioni hanno un formato standardizzato.

È possibile ridurre la frequenza a cui opera il bus, per evitare problemi elettrici dovuti al fatto che il fronte di salita del clock non può essere troppo ripido, se si usa un bus a 32 bit e si prelevano due istruzioni per volta.

Sono necessari molti registri, inoltre bisogna aggiungere due istruzioni specializzate per leggere e scrivere in memoria (che chiamiamo LOAD e STORE) che avranno durata maggior perché accedono alla memoria.

Le uniche fasi rimaste sono quindi la fase di fetch e quella di esecuzione, che tuttavia continuano ad avere una durata variabile ed a necessitare di sincronizzazione.

\section{Fase di Fetch} Per standardizzare il più possibile la durata della fase di fetch e ridurla ad un ciclo di clock si aggiunge una memoria cache apposita per le istruzioni, o \emph{instruction cache}; si tratta di un'estensione del concetto di coda di prefetch.

La I-cache memorizza le ultime istruzioni eseguite in modo che quando si ha un ciclo non è necessario accedere alla memoria per prelevare le istruzioni rimaste nella cache.

Le I-cache sono piuttosto piccole (ad esempio $d=8K$) e sono divise in celle con tre campi, un bit di validità, un campo etichetta MSB (contenente i bit più significativi dell'indirizzo dell'istruzione) ed un campo contenente l'istruzione (ISTR).

La cache rimane coerente anche se si effettuano trasferimenti a burst, si può continuare a sfruttare il bus quando è libero per trasferire istruzioni nella I-cache; è anche possibile usare anche una cache specializzata per i dati, o D-cache.

\section{Fase di esecuzione} La fase di esecuzione di alcune istruzioni può durare più di un ciclo di clock, per ottenere un throughput di un'istruzione per ciclo di clock si possono sovrapporre fasi di esecuzione diverse modularizzando la ALU.

Si divide la ALU in una successione di reti combinatorie separate da registri (per renderle non trasparenti); in questo modo il tempo di esecuzione di una singola istruzione si allunga ma si riesce comunque ad avere un migliore throughput.

\subsection{Sincronizzazione dei registri} Quando due istruzioni le cui fasi di esecuzione sono in parallelo accedono agli stessi registri si possono avere dei conflitti, che possono essere risolti in due modi: \begin{itemize} \item imponendo ai programmi di non avere istruzioni che creno conflitti, può farlo automaticamente il compilatore o assemblatore scegliendo i registri acceduti accuratamente, inserendo delle istruzioni NOP o variando l'ordine delle istruzioni quando possibile; \item si associa un bit di validità ad ogni registro, quando un'istruzione accede ad esso si annulla il bit fino a che l'istruzione non è terminata, se un'altra istruzione tenta di accedere ad un registro non valido si blocca in attesa \end{itemize} Entrambe le soluzioni rallentano l'esecuzione.

\subsection{Istruzioni di salto} Normalmente un programma non contiene solo istruzioni che devono essere eseguite in sequenza, ma anche istruzioni di salto. In questo caso le istruzioni presenti nella pipeline potrebbero non dover essere eseguite.

Nel caso di una istruzione di salto incondizionato è sicuro che le istruzioni successive non debbano essere eseguite, mentre nel caso di salto condizionato è necessario attendere la fine dell'esecuzione dell'istruzione per sapere se saltare o meno.

\subsubsection{Bubble} Alla fine della fase di fetch di una istruzione il processo può capire se questa è una istruzione di salto e aggiungere un certo numero di istruzione NOP nella pipeline (dipendentemente dalla profondità della pipeline stessa), oppure le NOP possono essere messe direttamente nel codice; alternativamente si può disabilitare la ALU disabilitando il clock dei registri.

\subsubsection{Pipeline flushing} Non appena si esegue una istruzione di salto incondizionato, od una istruzine di salto condizionato con condizione verificata, si cancella la pipeline. Questo è possibile solo se tutte le scritture delle istruzioni avvengono nell'ultima fase, ma nel caso di salti condizionati permette di eseguire istruzioni se la condizione non è verificata.

\subsubsection{Dual pipeline} Se si hanno a disposizione due ALU in caso di istruzione di salto condizionato è possibile caricare in una le istruzioni sequenzialmente successive e nell'altra quelle da eseguire in caso di salto; alla fine dell'istruzione si sceglie la pipeline valida e si cancella l'altra.

Oltre alla scrittura nell'ultima fase delle istruzioni questa tecnica richiede anche che l'indirizzo di salto possa essere calcolato in fase di fetch dell'istruzione, e quindi deve essere un operando immediato o relativo.

\subsubsection{Delayed branch} Si anticipa la fase di esecuzione dell'istruzione di salto spostandola prima di un numero di istruzioni sufficienti a fare si che il momento del salto cada al termine della fase di fetch dell'istruzione precedente; lo spostamento dell'istruzione deve essere fatto dal compilatore e può essere fatto solo se non ci sono conflitti nell'accesso ai registri; l'istruzione di salto non deve fare riferimento a registri o flag modificati dalle istruzioni scavalcate.

\subsubsection{Branch prediction} Per migliorare la tecnica del pipeline flushing si considera che spesso i salti condizionati hanno un'evoluzione più probabile: se il salto è all'indietro è probabile che la condizione sia verificata a causa del modo con cui i compilatori per linguaggi ad alto livello traducono i cicli in linguaggio macchina, se il salto è in avanti è probabile che la condizione non sia verificata.

È necessario distinguere in fase di fetch se l'istruzione è di salto condizionato e l'indirizzo di salto, quindi si inserisce in PC l'indirizzo più probabile secondo le considerazioni precedenti, se poi la previsione è errata si cancella la pipeline.

Questa tecnica non può essere usata nel caso di salti remoti o di salti con operandi non immediati o relativi.

\section{Pipeline} Si dividono le istruzioni in cinque fasi ottenendo una pipeline profonda cinque livelli: le fasi sono la fase di fetch (IF), di decode (ID), di esecuzione (EX), di accesso alla memoria (ME) e di scrittura (WR) e sono separate da quettro registri: IF/ID, ID/EX, EX/ME, ME/WR.

Ogni fase ha delle reti combinatorie e circuiti dedicati a processare i dati dal registro di input e portarli nel registro di output in un ciclo di clock, il circuito per la fase di fetch acquisisce l'istruzione dall'unità di accesso al bus.

\subsection{Fase di fetch} In questa fase il codice operativo di una istruzione (supponiamo a 32 bit) viene caricato nel registro IF/ID; esso è composto da 6 bit che identificano il tipo di istruzione ed il tipo di operazione da eseguire (OPCODE) e da altri argomenti che dipendono dal tipo.

Il circuito di fetch contiene il registro \emph{program counter} (PC) che riferisce la locazione di memoria dove deve essere prelevata la prossima istruzione e la cui uscita è connessa all'unità di accesso al bus; il dato in ingresso è scelto da un multiplexer pilotato da un indice (NEXT) che selezione normalmente il valore di PC incrementato di 4 da un apposito sommatore, oppure seleziona il campo IMM del registro ID/EX nel caso di istruzioni di salto eseguite con successo.

\subsubsection{Istruzioni aritmetiche} Il codice operativo specifica l'indirizzo di due registri sorgenti (RS1 e RS2, supponiamo su 5 bit per un processore con 32 registri operativi), il registro destinazione (RD1, sempre su 5 bit) ed un ulteriore argomento da passare all'unità aritmetico-logica (FALU).

\subsubsection{Istruzioni di accesso alla memoria} Nel caso di una istruzione di load LD, questa specifica un registro base RB, un registro destinazione RD ed un campo OFFSET, nel caso di una istruzione di store ST il registro base è sostituito dal registro sorgente RS il cui contenuto andrà scritto in memoria.

\subsubsection{Istruzioni di salto} Il campo OPCODE specifica di quale istruzione di salto si tratta, gli operandi variano per ognuna: \begin{itemize} \item JE e JS (jump if equal e jump if sign): specificano due registri RS1 e RS2 ed un offset (campo OFFSET); \item JMP e JAL (jump incodizionata e jump and link): specificano un indirizzo completo (IND); \item JR (jump return): specifica un registro sorgente RS1; \end{itemize} In un ulteriore campo PCI di IF/ID viene scritto il contenuto del registro PC (già incrementato) che può essere usato per calcolare l'indirizzo di destinazione.

\subsection{Fase di decode} Il campo OPCODE viene espanso da una rete combinatoria detta \emph{unità di controllo} (UC) che ha in uscita i comandi da inviare alle fasi successive (CEX, CME, CWR) che sono scritti nel registro di uscita ID/EX.

Il circuito di questa fase contiene il \emph{register file} (RF), ovvero una rete che da l'accesso al contenuto dei registri operativi e che ha in ingresso due indirizzi di registri per lettura, un indirizzo di registro per la scrittura, un blocco di dati da scrivere (Data) ed un ingresso (Rwrite) che indica quando scrivere; in uscita fornisce il contenuto dei registri di ingresso selezionati.

\subsubsection{Istruzioni aritmetiche} Gli indici dei registri sorgenti sono in ingresso al register file, il contenuto in uscita a RF (operandi A e B) passa al registro di uscita ID/EX, l'indice del registro di uscita viene propagato e il campo FALU viene esteso su 32 bit e rappresenta l'operando immediato (IMM). Non ci sono comandi per la fase di memoria.

\subsubsection{Istruzioni di accesso alla memoria} Il registro RB (o RS) è in ingresso al register file, il suo contenuto (operando A) viene scritto in ID/EX, l'offset viene esteso su 32 bit e propagato così come il campo IMM delle istruzioni aritmetiche; nel caso di una istruzione LD il campo RD è il registro di uscita come per le istruzioni aritmetiche, nel caso di ST il campo RS è in ingresso al register file come secondo registro di lettura ed il suo contenuto (operando B) viene scritto in ID/EX.

\subsubsection{Istruzioni di salto} \begin{itemize} \item istruzioni di salto condizionato JS e JE: RS1 e RS2 sono in ingresso al register file, il loro contenuto (campi A e B) viene scritto in ID/EX, OFFSET viene moltiplicato per 4, esteso a 32 bit e sommato a PCI, il risultato viene scritto nel campo IMM; \item istruzioni di salto incondizionato JMP e JAL: l'indirizzo IND viene scritto in IMM moltiplicato per 4, nel caso di istruzione JAL il campo PCI viene scritto nel campo B del registro ID/EX e nel campo RD viene scritto il registro in cui questa istruzione scrive (supponiamo 31); \item istruzione di ritorno JR: il registro RS1 va in ingresso al register file, il contenuto viene scritto in IMM. \end{itemize} Non sono presenti comandi per l'accesso alla memoria e solo per l'istruzione JAL sono presenti comandi per la fase di scrittura.

\subsection{Fase di esecuzione} In questa fase opera la ALU vera e propria che riceve in ingresso parte del contenuto di CEX che indica quale operazione deve eseguire; il risultato dell'operazione (ALUout) viene memorizzato in EX/ME.

I comandi per le fasi di memoria e di scrittura (CME e CWR) e il registro di destinazione sono propagati inalterati al registro di uscita.

\subsubsection{Istruzioni aritmetiche} La ALU riceve in ingresso gli operandi A e B e l'operando immediato, eseguendo l'operazione aritmetico-logica selezionata e ritorna il risultato.

\subsubsection{Istruzioni di accesso alla memoria} La ALU riceve in ingresso i campi A e OFFSET ed esegue la somma ottenendo un indirizzo di memoria; nel caso di istruzione ST un bit di CEX specifica che l'operando B sia propagato al registro EX/ME.

\subsubsection{Istruzioni di salto} \begin{itemize} \item istruzioni di salto condizionato JS e JE: gli operandi A e B sono in ingresso alla ALU, il bit Sign o Zero in uscita dalla ALU è selezionato dai comandi CEX (Sign nel caso di JS e Zero nel caso di JE) e va in ingresso NEXT al multiplexer della fase di fetch che implica la selezione di IMM come prossimo indirizzo in PC se la condizione è verificata; \item istruzioni di salto incondizionato JMP, JR e JAL: i comandi CEX settano il bit NEXT del multiplexer della fase di fetch ed il campo IMM viene scritto in PC, nel caso di JAL il campo B viene propagato inalterato attraverso la ALU e scritto nel campo ALUout; \end{itemize} Solo l'esecuzione dell'istruzione JAL prosegue oltre questa fase, per le altre non è prevista nessuna operazione ed EX/ME è nullo.

\subsection{Fase di accesso alla memoria} I comandi CME specificano la selezione della memoria e l'azione (lettura o scrittura) da eseguire, il risultato è inserito nel campo MEMout del registro ME/WR.

Vengono propagati i comandi per la fase di scrittura CWR ed il campo registro destinazione RD.

\subsubsection{Istruzioni aritmetiche} Queste istruzioni non prevedono accesso alla memoria, quindi il contenuto del registro EX/ME viene propagato inalterato in ME/WR e nel campo MEMout viene scritto il contenuto di ALUout.

\subsubsection{Istruzioni di accesso alla memoria} Il campo ALUout specifica l'indirizzo di memoria da accedere, i comandi CME selezionano la memoria e specificano se debba essere eseguita un'operazione di lettura (per LD) o scrittura (per ST), nel caso dell'istruzione ST il campo B specifica i dati che devono essere scritti e il campo MEMout non è significativo.

\subsubsection{Istruzioni di salto} L'istruzione JAL attraversa questa fase come una istruzione aritmetica, con i campi ALUout, CWR e RD propagati inalterati, le altre istruzioni non prevedono questa fase.

\subsection{Fase di scrittura} In questa fase si accede al register file RF presente nel circuito della fase di lettura; i comandi CWR specificano se il campo MEMout debba essere scritto su registro: il campo RD è in ingresso al register file come indice di registro per la scrittura, il campo MEMout è in ingresso a RF come campo DATA ed un bit dei comandi CWR specifica il campo Rwrite determinando la scrittura.

\subsubsection{Istruzioni aritmetiche} Il campo MEMout contiene ALUout che viene scritto sul registro indicato.

\subsubsection{Istruzioni di accesso alla memoria} Nel caso di istruzione LD il campo MEMout viene scritto nel registro indicato, l'istruzione ST non ha questa fase.

\subsubsection{Istruzioni di salto} L'istruzione JAL si comporta come una LD determinando la scrittura di PCI nel registro 31, le altre istruzioni di salto non prevedono questa fase.

\section{Implementazione della Pila} Molti programmi si avvalgono di una struttura a pila, per evitare di accedere ogni volta alla memoria si implementa una piccola pila con un blocco di registri, quando il blocco è pieno o vuoto si salva o ripristina con un accesso a burst alla memoria, risparmiando tempo.

Al posto delle istruzioni CALL e RET si usano le istruzioni JAL (\emph{jump and link}) e JR: la prima determina il salto alla locazione di memoria specificata e scrive in un registro determinato RJ (supponiamo il 31) l'indirizzo successivo, la seconda scrive in PC il contenuto di RJ; nel caso di un ulteriore salto si copia il contenuto di RJ in un altro registro.

Con una pila implementata in questo modo non è possibile usare efficientemente programmi ricorsivi.

\subsection{Register windows} Per passare i parametri ad un sottoprogramma, invece di avvalersi della pila, si usano i registri.

\section{Cache}

\subsection{Cache write-through} Ogni volta che un dato è modificato viene immediatemente aggiornata la modifica nella memoria da parte dell'UAB, in questo modo la d-cache non porta nessun vantaggio per la scrittura, ma comunque le scritture sono molto meno frequenti delle letture.

\subsection{Cache write-back} La memoria viene aggiornata successivamente alla scrittura nella cache, magari quando il processore non utilizza il bus.

Alcuni eventi obbligano a scrivere immediatemente in memoria le celle di cache che sono state modificate, identificate da un bit dirty/modified: \begin{itemize} \item in caso di rimpiazzamento della cella nella cache; \item durante un cambio di contesto, l'intero stato del processore deve essere salvato, e quindi anche la d-cache; \item se la memoria è condivisa tra più processori; \item se il dato è stato scritto in un dispositivo di I/O mappato nello spazio di memoria, tali aree di memoria non possono essere sottoposte a cache. \end{itemize}

La cache gestita write-back è più efficiente al costo di logica aggiuntiva.

\subsection{Writebuffer} Supponendo di utilizzare una cache gestita write-back con bit dirty ed un'unità di accesso al bus che, quando il bus è libero, aggiorna la memoria, si inserisce tra d-cache e memoria un \emph{write buffer}. Si tratta di una struttura dati gestita a coda fifo composta da blocchi di memoria che contengono il dato da scrivere e l'indirizzo completo.