Appunti presi durante le lezioni di Architettura degli Elaboratori II, nell’anno accademico 2001/2002. Attenzione: non mi assumo nessuna responsabilità per informazioni errate o imprecise eventualmente presenti.
Sito del corso: http://twiki.dsi.uniroma1.it/twiki/view/Architetture2/WebHome
Sito di questi appunti: http://twiki.dsi.uniroma1.it/twiki/view/Users/AlbertoRocca
E-mail docente: debiase@dsi.uniroma1.it .
Buono studio.
Per velocità di una porta si intende il tempo che questa impiega per adattare il suo output agli input che riceve.
La porta più veloce è la NAND.
Se assumiamo che essa inpiega un tempo T per adattarsi agli input, si può tracciare il seguente schema:
Porta |
Tempo |
Nand |
T |
Le stringhe binarie su cui i calcolatori lavorano devono avere lunghezza finita e fissata.
Tali stringhe sono dette parole o word.
La loro lunghezza condiziona tutta l'architettura del calcolatore: ogni circuito sarà in grado di manipolare (addizionare, memorizzare, decodificare, ecc.) solo stringhe di quella lunghezza.
B - BLOCCHI BASE
Utilizzeremo blocchi (insiemi) di porte logiche già studiati ad Architetture I.
Di ciascuno di questi blocchi non ci interessa più lo schema circuitale, ma la sua funzione logica (deducibile dalla tabella di verità).
Blocchi di porte logiche connesse in modo aciclico t.c. i segnali in uscita in un dato istante dipendono solo dai segnali in ingresso in quello stesso istante.
2.1.1 Codificatore
Input |
Output |
0 0 0 |
1 0 0 0 0 0 0 0 |
L'uno è sull'uscita indicata dal numero binario in input.
2.1.3 Transcodificatore
Input |
Output |
0 0 0 |
valori stabiliti dal costruttore |
Input |
Output |
x y z w 0 0 |
x |
Le linee di indirizzamento i0 ed i1 specificano quale entrata connettere all'uscita.
Input |
Output |
0 0 |
0 0 |
r rappresenta il riporto.
Input |
Output |
0 0 0 |
0 0 |
Blocchi di porte logiche t.c. i segnali in uscita in un dato istante NON dipendono solo dai segnali in ingresso in quello stesso istante (c'è una memoria finita degli input passati, che può influenzare gli output attuali).
Input in t |
Output in t |
0 0 | |
Q(t-1) |
La maggior parte dei calcolatori attualmente in uso sono macchine di Von Neumann, dal nome dell'ideatore dell'architettura (struttura logica) con cui sono costruiti.
Una macchina di Von Neumann è costituita da un sistema di interconnessione tramite cui vengono collegati 4 tipi di unità logiche (ognuna con un certo compito logico):
Queste unità logiche sono ottenute combinando i blocchi di cui si è parlato nel capitolo precedente, a creare circuiti più complessi.
CU e ALU sono quasi sempre integrate su un unico chip che prende il nome di CPU (Central Processing Unit).
Per questo, CU e ALU sono in genere connessi tramite multiplexer. CPU, memoria centrale, I/O, invece, sono in genere connessi tramite bus.
Per spostare una stringa da un punto ad un altro in un calcolatore esistono due metodi di trasmissione: seriale e parallelo.
Per realizzare questi due metodi di trasferimento esistono tre modi.
Si supponga ad esempio che A debba comunicare con B. A, tramite le linee di indirizzamento, attiva l'uscita B del suo demultiplexer. Manda quindi serialmente all'ingresso del suo demultiplexer la stringa binaria da inviare. Questa viene ricevuta dal multiplexer di B, insieme alle linee di indirizzamento che A ha mandato al suo demultiplexer. In questo modo, l'ingresso A del multiplexer di B viene attivano. La stringa quindi passa attraverso il multiplexer di B e raggiunge B.
Possono comunicare fra loro più di due registri alla volta, ma se si vuole cambiare il numero di elementi che compongono la rete (nell'esempio sono tre, A,B,C), va rifatto tutto da capo (struttura rigida), nel senso che bisogna usare multiplexer e demultiplexer con un numero di ingressi o uscite adeguato.
Affiancando più linee si può effettuare un trasferimento parallelo.
Input |
Output |
x 0 |
disconnesso |
Dunque, quando ricevono un segnale basso su c, qualunque sia il valore di a, esse introducono una resistenza tale che non c'è alcun segnale sull'uscita b. Questo significa disconnesso. Quando c vale 1, invece, mandano in output lo stesso segnale che ricevono in input.
Inizialmente tutte le unità sono disconnesse; quando due di esse vogliono colloquiare, un opportuno circuito di controllo connette le loro porte tristate. I segnali del mittente transitano sul bus e, pur raggiungendo tutte le unità, vengono captati solo dal ricevente.
Il circuito di controllo è detto arbitratore del bus e cede il bus a chi lo richiede solo quando questo è libero, utilizzando una certa gerarchia di priorità (ad es. la CPU ha precedenza sugli altri componenti).
La capacità di trasferimento del bus è misurata in byte al secondo e viene detta troughput.
NON possono comunicare fra loro più di due registri alla volta, ma se si vuole cambiare il numero di elementi che compongono la rete (nell'esempio sono tre, A,B,C), non è necessario rifare tutto da capo.
Affiancando più BUS si può effettuare un trasferimento parallelo.
Schema a blocchi:
Funzione logica: operazioni logiche (not, and, or, ..., comparazione logica, ...) e operazioni aritmetiche (somma, differenza, shift, complementazione, opposto, comparazione aritmetica,...).
La ALU non è in grado di eseguire direttamente nè moltiplicazioni, nè divisioni. Inoltre può lavorare direttamente solo su numeri naturali o interi e su stringhe con significato logico.
Operandi: ingressi ai quali mandare le stringhe binarie su cui compiere l'operazione scelta.
Funzione: ingressi su cui mandare il codice che identifica l'operazione da compiere. Ad esempio:
Codice |
Operazione |
0 0 0 |
AND |
Risultato: uscite su cui si ottiene la stringa risultante, in caso di operazioni aritmetiche.
Flag: uscite che segnalano determinati eventi logici.
Flag |
Evento |
Z |
segnala se il risultato dell'operazione logica o aritmetica compiuta ha come risultato la stringa nulla |
N |
segnala se il risultato dell'operazione compiuta è un numero negativo; |
C |
segnala il trabocco di un uno sul MSB o LSB |
V |
segnala un overflow o underflow. |
Il trabocco segnala che una stringa rappresentante un numero naturale o un valore logico (cioè in base due "standard" - senza bit segno e NON in complemento a due) ha oltrepassato le dimensioni della word e quindi non rappresenta più il valore voluto. Per fare ciò la ALU controlla che l'uscita di trabocco dei vari circuiti da cui proviene la stringa sia attiva.
L'overflow segnala invece che una stringa rappresentante un numero intero (cioè in complemento a due) ha oltrepassato la dimensione della word e quindi non rappresenta più il valore voluto. Per fare ciò la ALU controlla il MSB degli operandi: se entrambe gli operandi hanno MSB pari ad uno, c'è stato overflow e il flag V viene attivato.
Il trabocco non ha senso per numeri rappresentati con stringhe in complemento a due
(ad es. 1100Ca2+1010Ca2 da un risultato errato 0110Ca2, mentre 1100Ca2+1100Ca2 da un risultato giusto 1000Ca2, dunque il trabocco non dà alcuna indicazione sulla correttezza del risultato),
così come l'overflow non ha senso per numeri naturali e valori logici rappresentati in base due con metodo "standard".
La ALU, tuttavia, segnala entrambe le condizioni senza occuparsi del significato delle stringhe che elabora.
Schema a blocchi:
Insieme di registri di memoria di capienza pari alla lunghezza della word; ogni registro prende nome di cella di memoria.
Funzione logica: scrittura di stringhe in memoria e lettura di stringhe precedentemente memorizzate.
A: ingressi a cui va mandata la stringa da memorizzare
B: ingressi a cui va mandato l'indirizzo del registro su cui si vuole memorizzare A / da cui si vuole leggere C. Per semplicità progettuale e per comodità, anche questo ingresso accetta solo stringhe lunghe una word
C: uscita su cui si può leggere la stringa in memoria
Funzione: specifica se si vuole leggere o scrivere sulla memoria
Il programmatore assembly vede la memoria centrale come vettore unidimensionale il cui indice, detto indirizzo, identifica univocamente ogni cella e permette di accedervi sia in lettura che in scrittura.
L'entrata B teoricamente dovrebbe essere gestita da un decodificatore che riceve in input l'indirizzo del registro e con tante uscite quante sono le celle di memoria, così da poter mandare ad ognuna di esse un opportuno segnale. Si vedrà che in pratica ciò non è possibile. Ad ogni modo si può ragionare come se ciò fosse fattibile, poiché vengono usati trucchi trasparenti al programmatore assembly.
Dunque, se ad es. la memoria avesse 8 registri e lavorasse con parole da 4 bit, il decodificatore che gestisce B dovrebbe avere 8 uscite e quindi 4 ingressi, di cui soltanto tre realmente utilizzati:
ingressi = log28
2ingressi = 2log28
2ingressi = 8
2ingressi = 23
ingressi = 3
I 4 ingressi sono necessari perché, come già specificato, per semplicità progettuale e per comodità, B accetta solo stringhe lunghe una word.
La memoria centrale è una memoria ad accesso casuale: per raggiungere un registro non è necessario scorrere tutte le celle precedenti, come succede invece per le memorie dette ad accesso sequenziale. Per questo la memoria centrale viene spesso chiamata RAM (Random Access Memory).
Caratteristica delle macchine di Von Neumann è che la memoria centrale contenga sia istruzioni che dati. Questo aumenta le possibilità di commettere errori. Per proteggersi da ciò, la RAM viene spesso organizzata in "compartimenti stagni", ognuno atto a contenere solo un tipo di stringa. In genere esiste una parte dedicata alle istruzioni del programma, un'altra atta a contenere i dati, una parte riservata al sistema operativo [ad es. per il vettore delle interruzioni e per tutto il codice del S.O.] ed infine una riservata allo stack. Si noti che questa non è una suddivisione hardware, ma logica.
È da notare, infine, che prima che una operazione di lettura o scrittura venga completata, trascorre un certo tempo detto tempo di accesso, dovuto alla lentezza delle porte che costituiscono registri e decodificatori.
L'entrata B della memoria dovrebbe essere gestita da un decodificatore con input l'indirizzo del registro e con tante uscite quante sono le celle di memoria, così da poter mandare ad ognuna di esse gli opportuni segnali. Se si considera che B, sugli attuali computer, è in genere di 32 bit, si capisce che il decoder dovrebbe avere 4 miliardi di piedini di uscita (232). Attualmente, però, il massimo numero di piedini che si riesce ad inserire in un chip è dell'ordine di qualche centinaio.
Diventa quindi necessario usare un "trucco" a livello hardware (invisibile al programmatore assembly, che continua a vedere la memoria come un vettore unidimensionale) per poter indirizzare un tale quantitativo di celle.
Il trucco consiste nel frazionare l'indirizzo B.
Si prenda, ad esempio, il caso di indirizzi di 32 bit. B viene diviso in pezzi da 8 bit ciascuno. Ogni pezzo è considerato idealmente come una coordinata: x, y, z, w. In questo modo, la memoria non viene più pensata come un vettore unidimensionale, ma viene pensata scomposta come in figura.
Ogni pezzo da 8 bit è gestito da un decodificatore con otto ingressi e quindi 256 uscite (che è un numero di piedini accettabile). Le uscite dei 4 decodificatori vengono poi messe insieme per ottenere l’indirizzo finale.
Si ricorda che tutto ciò avviene a livello hardware e non logico e pertanto risulta trasparente al programmatore assembly.
In commercio si trovano chip di memoria costruiti come visto con registri, input, input per indirizzo, uscite. Essi hanno capacità prestabilita, nonché ingressi e uscite in grado di accogliere stringhe di lunghezza fissata.
Assemblando tali circuiti si ottengono i banchi di memoria veri e propri, in grado di gestire stringhe dalla lunghezza e dalla capienza che si vuole.
Si consideri il caso in cui si abbiano a disposizione chip in grado di contenere 4 parole da 4 bit e quindi con 4 linee di indirizzamento di cui solo 2 relamente utilizzate, 4 linee di input, 4 linee di output e 4 celle di memoria:
Come prima eventualità, si supponga di voler costruire con essi una memoria con capacità identica a quella dei chip disponibili, ma che possa lavorare su parole lunghe il doppio. Ecco come effettuare le connessioni:
Aggiungendo altri chip si possono gestire parole più lunghe.
Si supponga ora di voler costruire con essi una memoria che lavori con parole di lunghezza identica a quella dei chip disponibili, ma con capacità doppia (otto parole invece di quattro). Ecco come effettuare le connessioni:
Per gestire 8 parole servono 4 linee di indirizzamento, di cui solo 3 reamente utilizzate. Di queste, due vanno connesse a quelle dei chip, una viene usata come segnale abilitatore degli input dei chip (stabilisce quale dei due chip utilizzare).
Le uscite dei chip vengono gestite da porte OR: basta che da parte di un chip arrivi un uno ad una porta OR perchè l'output sia uno.
Aggiungendo altri chip si può ulteriormente aumentare la capacità.
Si possono combinare i due precedenti metodi per ottenere banchi di memoria di capacità doppia ed in grado di gestire parole lunghe il doppio.
L'ingresso B è lungo una parola. Questa è una limitazione alla quantità di memoria indirizzabile dalla macchina. Se ad es. la parola è di 16 bit, si possono indirizzare al massimo 216 (circa 65000) celle: come si usa dire, il campo di indirizzamento va da 0 a 216-1.
Può essere necessario disporre di più indirizzi, ovvero può essere necessario avere una memoria centrale di dimensioni maggiori rispetto a questo limite.
A tal proposito si usa un registro di memoria speciale detto registro di estensione, che si trova in una zona della RAM di nome MM (Memory Managment). Tale registro contiene i bit in più rispetto alla lunghezza della parola che bisogna utilizzare per indirizzare la memoria.
Si può pensare in questi termini: la memoria viene divisa in pagine e ogni pagina è direttamente indirizzabile tramite la word che arriva all'ingresso B. Nel registro, invece, viene memorizzato il numero di pagina che si stà considerando.
Per quanto illustrato, la memoria centrale viene divisa in memoria fisica - quella realmente presente "on board" - e memoria indirizzabile - quella parte di memoria fisica direttamente indirizzabile tramite una parola.
Introdurre una struttura a pagine ha i seguenti svantaggi: occorrono nuove istruzione per gestire il registro di estensione (ad es l'istruzione "salto di pagina"); si ha un aumento del tempo di accesso; un programma non può occupare più di una pagina e i salti fra moduli di programma in pagine diverse sono più complicati [le istruzioni di salto hanno uno spazio per l'offset o indirizzo che non può gestire il registro di estensione, dunque occorre usare anche istruzioni speciali].
La Control Unit è costituita dai seguenti elementi di base:
e dai seguenti elementi non di base:
Il PC è un contatore preselezionabile.
Il suo compito è di gestire la memoria centrale: le sue uscite sono collegate agli ingressi di indirizzamento della memoria centrale.
Essendo un contatore, scandisce la memoria in modo sequenziale. Se, però, è necessario interrompere la sequenzialità del conteggio per saltare ad un certo indirizzo, si può usare l'ingresso preselezionabile, collegato al codificatore di comandi.
Caratteristica delle macchine di Von Neumann è di avere un solo PC. Ciò è equivalente ad affermare che le macchine di Von Neumann sono macchine sequenziali: possono eseguire una sola istruzione alla volta.
Dall'unicità del PC si deduce anche l'unicità della memoria centrale: solo il PC ha accesso alla memoria e se esso è unico, anche la memoria lo deve essere.
La stringa letta dalla memoria tramite il PC viene inviata al decodificatore di istruzioni.
Questo decodifica i vari campi dell'istruzione e li manda al codificatore di comandi.
Questo circuito riceve le istruzioni decodificate e fa tutto il necessario per eseguirle.
Ad es. si connette all'ingresso "funzione" della ALU e le comunica che operazione fare; connette quindi la memoria centrale o i registri interni alle entrate della ALU; connette poi l'uscita della ALU con la memoria centrale o i registri interni per memorizzare il risultato.
Si fa presente che non tutte le istruzioni richiedono l'uso della ALU.
Si noti la differenza fra istruzioni e comandi: le istruzioni sono comandi codificati, i comandi sono istruzioni decodificate.
A onor del vero, si potrebbero costruire macchine che abbiano solo comandi e non istruzioni. Così facendo non sarebbe più necessaria le fase di decodifica, aumentando velocità, economicità ed affidabilità dei calcolatori. Questo però non avviene perchè i comandi occuperebbero troppa memoria (esistono molti comandi, ognuno può avere molti modi di indirizzamento degli operandi). La codifica è una sorta di compressione e per questo in memoria si trovano soltanto comandi codificati - ovvero istruzioni - e non comandi veri e propri.
Visto il gran numero di istruzioni e indirizzamenti possibili, il decodificatore di istruzioni risulta essere un circuito combinatori estremamente complesso e quindi anche costoso da progettare e costruire.
Talvolta, per risparmiare, esso viene sostituito da una micromacchina, ovvero una macchina di Von Neuman "in miniatura", con micro-PC, micro-CPU e ROM al posto della RAM.
L'istruzione da decodificare arriva al micro-PC che controlla la ROM. Nella ROM, al contrario delle macchine di Von Neumann vere e proprie, ci sono sia istruzioni che comandi. Nel caso che il micro-PC trovi direttamente il comando corrispondente all'istruzione ricevuta, il comando viene subito inviato in output. Se, invece, l'istruzione non ha un comando associato direttamente, significa che le è associato un micro-programma , anch’esso presente nella ROM, che,una volta eseguito dalla micro-CPU, da in output il comando corrispondente all'istruzione.
Ovviamente ci sarà un registro di memorizzazione sull’uscita della micromacchina che accumula i vari pezzi del comando via via ottenuti dall’elaborazione dalla micro-CPU. Solo quando tutta l’istruzione è decodificata, viene abilitata l’uscita ed il comando ottenuto arriva al codificatore di comandi.
L'insieme dei microprogrammi viene detto firmware. Le microistruzioni con cui scrivere questi programmi sono molto poche.
Tutti questi passaggi sono trasparenti al programmatore assembly, tranne per il fatto che introducono un rallentamento nell'esecuzione dei programmi, a fronte, però, di una maggiore economicità.
Questo rallentamento può essere più o meno marcato a seconda del numero di microistruzioni e del numero di comandi direttamente presenti nella ROM. È possibile espandere il set di microistruzioni e comandi semplicemente cambiando la ROM (o utilizzando una EPROM - tipo di memoria di cui non parleremo).
Perché una macchina di Von Neumann possa fare quello che le è richiesto tramite un’istruzione sono necessarie le seguenti fasi:
Il generatore di fase conosce la fase nella quale ci si trova e la segnala al decodificatore di comandi.
Vedremo che sapere in quale delle tre fasi si trova la macchina, in alcuni casi, si rivela molto utile (ad es. canalizzazione).
Per ora basta sapere che se ci si trova nella prima o nella seconda fase il calcolatore deve fare le stesse operazioni qualsiasi sia l'istruzione; se, però, ci si trova nell'ultima fase, i compiti che il decodificatore di comandi deve svolgere variano da istruzione ad istruzione. Utilizzando una micromacchina, sia nella seconda che nella terza fase i compiti variano da istruzione ad istruzione. Appare evidente, quindi, che serve sapere in quale delle tre fasi il sistema si trova.
Sono normali registri di memorizzazione lunghi una parola.
Il loro compito è identico a quello delle celle di memoria centrale: memorizzare i dati su cui si sta operando.
Da un punto di vista logico, quindi, risultano superflui, ma la pratica mostra che è molto comodo averli.
Rispetto alla memoria centrale, infatti, essi sono più vicini e meno numerosi. Questo significa che sono più velocemente accedibili e più facilmente indirizzabili della memoria centrale.
Si usa dividere tali registri in due gruppi:
Fra quelli ad uso speciali ci sono:
Questa unità logica permette al calcolatore di comunicare con l’uomo e con l’ambiente esterno.
La comunicazione avviene tramite opportuni dispositivi detti periferiche.
Nella comunicazione con l’ambiente esterno, tramite opportune periferiche, il calcolatore riceve segnali che misurano grandezze fisiche e manda segnali per modificare tali grandezze.
Nella comunicazione con l’uomo, tramite opportune periferiche, l’operatore può inserire dati ed istruzioni nel calcolatore, mentre il calcolatore può mostrare all’operatore il risultato delle sue elaborazioni.
Si può avere, infine, anche una comunicazione fra calcolatore e calcolatore.
Ci sono due metodi per implementare l'unità di I\O.
Il problema principale nel colloquio fra periferiche e CPU risiede nella differenza di velocità fra questi componenti.
Per riuscire a farle comunicare, quindi, bisogna usare vari accorgimenti.
Innanzitutto è necessario che le periferiche bidirezionali (periferiche su cui si può leggere e scrivere) abbiano almeno i seguenti tre registri:
La comunicazione consiste nel trasferimento di dati fra memoria centrale o registri interni e registri di dato in ingresso o di dato in uscita della periferica.
Esistono i seguenti tre metodi per realizzare lo scambio di dati:
1: istruzioni del programma che non richiedono I\O
2: inizializzazione:
caricamento in una variabile dell'indirizzo di memoria centrale da cui scrivere i dati trasferiti;
caricamento in una variabile della condizione di fine: o il numero di word da trasferire o la particolare word che appena letta fa terminare il trasferimento;
3: test:
se il bit di pronto in lettura del registro di stato della periferica è basso, ripete questo test finchè non diventa alto, altrimenti continua
4: istruzione che trasferisce la word dal registro di dato in uscita della periferica alla RAM o ad un registro interno alla macchina;
5: test:
se la condizione di fine non è verificata ripete da 3, altrimenti continua
6: parte restante del programma
Riassumendo: la CPU cicla finchè la periferica non è pronta; appena la periferica è pronta, la CPU cicla leggendo un dato alla volta, finchè non li legge tutti.
Rispetto agli altri metodi, il programmato è molto inefficiente perchè tiene occupata la CPU a ciclare per trasferire una word alla volta, pertanto è ormai abbandonato
1: abilitazione di quella periferica a lanciare interruzioni, alzando l'apposito bit del suo registro di stato
2: dichiarazione della posizione di memoria in cui si trova il programma di servizio di quella interruzione, inserendo l'indirizzo nel vettore delle interruzioni
3: istruzioni del programma che non richiedono I\O
4: inizializzazione:
caricamento dell'indirizzo di memoria centrale da cui scrivere i dati trasferiti (qui o nel programma di servizio?)
caricamento della condizione di fine: o il numero di word da trasferire o la particolare word che appena letta fa terminare il trasferimento (qui o nel programma di servizio?)
6: istruzione di lettura da periferiferica:
o il programma ha un'istruzione di attesa - il calcolatore si ferma ad aspettare che la periferiferica sia pronta in lettura senza poter fare niente - o ha un'istruzione di trap verso il sistema operativo - il sistema operativo cede il controllo della CPU ad altri programmi finchè non arriva il segnale di pronto -
quando il bit di pronto in and con il bit di abilitazione delle interruzioni della periferica è alto, la periferica lancia una interruzione e viene attivato il programma di servizio, che trasferisce la word dal registro di dato in uscita della periferica alla RAM o ad un registro interno al calcolatore; ciò si ripete finchè non viene raggiunta la condizione di fine; a questo punto, il programma di servizio ritorna e cancella l'interruzione
7: parte restante del programma
Riassumendo: la CPU aspetta l’interruzione di periferica pronta; il programma di servizio legge un dato e ritorna; questo avviene finchè tutti i dati non sono esauriti.
Il metodo ad interruzioni è utilizzato per le periferiche che non trasferiscono molti dati (ad es. la tastiera)
1: istruzioni del programma che non richiedono I\O
2: caricamento nel registro di indirizzo del controllore di DMA dell'indirizzo di memoria centrale da cui scrivere i dati trasferiti
3: caricamento nel registro di dato del controllore di DMA della condizione di fine: o il numero di word da trasferire o la particolare word che appena letta fa terminare il trasferimento
4: abilitazione di quella periferica a lanciare interruzioni, alzando l'apposito bit del suo registro di stato
5: dichiarazione della posizione di memoria in cui si trova il programma di servizio di quella interruzione, inserendo l'indirizzo nel vettore delle interruzioni
6: istruzione di avvio del trasferimento:
o il programma ha un'istruzione di attesa - il calcolatore si ferma ad aspettare che la periferiferica abbia trasferito tutti i dati, senza poter fare niente - o ha un'istruzione di trap verso il sistema operativo - il sistema operativo cede il controllo della CPU ad altri programmi finchè non arriva l'interruzione di fine trasferimento -
quando l'interruzione di fine trasferimento arriva, o finisce l'attesa o arriva un'istruzione di trap al sistema operativo che decide se far ripartire il programma
7: parte restante del programma
E’ un metodo pensato per liberare la CPU dall'incombenza di gestire lo scambio di dati fra periferiche e calcolatore: la differenza con gli altri due metodi, infatti, sta nell'inviare una interruzione non per ogni word trasferita, ma per ogni blocco di word trasferito; in questo modo la CPU non deve eseguire un'istruzione di move da un indirizzo ad un altro per ogni dato da scambiare.
Questo è possibile perchè alle periferiche viene permesso di accedere alla memoria centrale direttamente, senza l'intervento della CPU. Ciò avviene tramite un opportuno circuito detto controllore di DMA, unico e a cui si possono collegare più feriferiche e che è a sua volta collegato al bus. Questo componente ha (almeno) tre registri, che vengono usati al posto dei registri delle periferiche: un registro di stato, un registro contatore di indirizzo, un registro contatore di dato. Il programmatore assembly specifica, tramite un opportuno bit del registro di stato, se la periferica deve leggere o scrivere sulla memoria centrale. Specifica quindi nel registro contatore di indirizzo l'indirizzo di partenza della memoria centrale, su cui leggere o scrivere. Nel registro contatore di dato mette il numero delle informazioni da leggere o scrivere, ovvero il numero di celle di memoria da usare. Avvia quindi il trasferimento alzando un opportuno bit nel registro di stato. Eseguite queste poche istruzioni, la CPU non viene più chiamata in causa e può svolgere altri compiti. Durante il trasferimento, il registro contatore di indirizzo viene incrementato ed il registro contatore di dato decrementato. Quando il registro contatore di dato arriva a zero, la periferica segnala la fine del trasferimento alzando un opportuno bit del registro di stato per mandare un'interruzione alla CPU.
Alla luce di quanto esposto sinora, appare evidente che il metodo del DMA non è trasparente al programmatore assembly: egli deve fare in modo che il sistema operativo imposti correttamente il controllore di DMA.
Un'altra importante cosa da notare è che il registro contatore d'indirizzo svolge le veci del program counter della CPU. L'unica differenza è che il PC è un contatore preselezionabile, mentre il registro del controllore di DMA non lo è.
Ci si potrebbe poi chiedere come facia il controllore di DMA a colloquiare con la RAM se in quel momento il BUS è già occupato dalla CPU. Ebbene, esso sfrutta i buchi di tempo in cui la CPU non utilizza il bus. Come già visto, infatti, l'esecuzione di istruzioni da parte della CPU implica tre fasi distinte: caricamento, decodifica o elaborazione della micromacchina ed esecuzione. Durante la decodifica, la CPU sicuramente non usa il BUS. Questo potrebbe accadere anche durante l'esecuzione, dipende dal tipo di istruzione. E' in queste fasi che il controllore di DMA può usare il bus. Il tutto è ovviamente gestito dall'arbitratore del BUS.
Il metodo del DMA viene usato solo per le periferiche che scambiano grandi quantità di dati col calcolatore. Ciò avviene a causa del suo costo sia hardware, che software (impostazione dei suoi tre registri). Inoltre, se le periferiche che utilizzano un controllore di DMA fossero troppe, si avrebbe la saturazione del bus, ovvero gli spazi lasciati liberi dalla CPU non sarebbero sufficienti per accontentare tutte le richieste di trasferimento, ottenendo un'esplosione dei tempi di attesa.
ALU, CU, memoria centrale e I\O sono le unità logicamente essenziali per le macchine di Von Neumann.
Tramite queste, un calcolatore può svolgere tutte le operazioni che normalmente i calcolatori sanno fare.
Spesso, però, a queste unità logiche essenziali, vengono affiancate altre unità, specializzate in vari compiti.
Tali unità non sono logicamente essenziali, dato che i compiti che esse svolgono potrebbero venire svolti anche soltanto da ALU, CU, memoria centrale e I\O via software (ovvero combinando varie istruzioni).
Ad es.: la ALU non sa calcolare direttamente il prodotto di due numeri, ma per fare un prodotto basta fare una serie di somme, cosa che la ALU sa fare. Basta quindi scrivere un programma che faccia ciò. Ad es.: per operare con numeri di lunghezza doppia al normale basta spezzare manualmente questi numeri su due word.
Vengono tuttavia aggiunte perchè velocizzano queste operazioni, dato che sanno eseguirle direttamente, con una sola istruzione - via hardware, come si dice - perchè hanno circuiti in grado di farlo. In questo modo si risparmiano tempo e fatica.
Di queste unità aggiuntive vedremo soltanto l'unità aritmetica.
L'unità aritmetica (altrimenti detta coprocessore aritmetico o unità a virgola mobile) si occupa di moltiplicare e dividere numeri interi, nonchè di operare sui numeri reali rappresentati in virgola mobile e sui numeri in lunghezza doppia (lunghi cioè due word anzichè una).
Le stringhe binarie che i calcolatori usano si possono dividere in due grandi classi, in base a ciò che esse rappresentano:
Le istruzioni dicono al calcolatore cosa fare. I dati sono le entità su cui tali istruzioni possono agire.
Si noti che istruzioni e dati, in una macchina di Von Neumann, convivono nello stesso luogo: la memoria centrale. Ciò rappresenta una debolezza di questi calcolatori, poichè non c'è alcuna protezione fra i due tipi (stringhe di dati possono essere scambiate per istruzioni e viceversa). Sta ai programmatori scrivere programmi sufficientemente corretti da non creare problemi in questo senso. La sintassi dei linguaggi di alto livello (controlli sui tipi, ecc.) serve proprio a questo.
[da migliorare]Un programma scritto in linguaggio macchina (cioè direttamente comprensibile dal calcolatore) è un insieme di istruzioni binarie. Un programmatore è la persona che scrive tali stringhe. In realtà i programmatori scrivono programmi in linguaggi che usano codici mnemonici al posto di sequenze binarie. Queste sequenze vengono poi tradotte da appositi programmi in stringhe binarie.
Esistono 4 classi di istruzioni:
Le stringhe binarie rappresentanti istruzioni sono divise idealmente in due campi logici: (esempio fittizio a 32 bit)
16 bit: codice istruzione |
16 bit: referenziamento operandi |
Il primo campo contiene il codice dell'istruzione: ogni istruzione è identificata univocamente tramite un numero binario di massimo 16 bit (verrà interpretato dal decodificatore di istruzione).
Il secondo campo contiene il referenziamento degli operandi: ogni istruzione agisce su un certo numero di dati (eventualmente zero); la loro posizione nel calcolatore viene indicata in questa parte dell'istruzione (verranno recuperati dal PC tramite preselezione inviata dal codificatore dei comandi).
Il referenziamento degli operandi può essere di due tipi: esplicito ed implicito.
Nel caso di referenziamento esplicito, nel campo "referenziamento degli operandi" viene effettivamente indicato dove si trovano gli operandi (memoria centrale o registri di memorizzazione interni alla CPU) oppure viene messo direttamente il loro valore.
Nel caso di referenziamento implicito, il campo "referenziamento degli operandi" non esiste. In questo caso, infatti, ci si aspetta di trovare gli operandi in posizioni fisse, prestabilite dal progettista della macchina (determinati registri della CU detti accumulatori).
Parlando del referenziamento esplicito, sorge un altro problema: se si decide di usare operandi che si trovano nella memoria centrale, lo spazio di 16 bit non basta neanche per indirizzarne uno (servono 32 bit per ogni indirizzo).
Esistono due soluzioni.
La prima consiste nell'utilizzare istruzioni più lunghe di una parola, dette istruzioni a lunghezza variabile poichè si possono avere istruzioni di 1, 2, 3, ecc. parole; ogni parola in più contiene l'indirizzo di un operando oppure l’operando stesso.
La seconda consiste nell'usare i registri di memorizzazione interni alla CU. Questi, essendo in numero esiguo, sono indirizzabili tramite stringhe molto corte, che riescono quindi a stare nei 16 bit disponibili. In questo caso si possono usare istruzioni a lunghezza fissa.
Sempre riguardo l'indirizzamento esplicito, esistono le seguenti possibilità di referenziamento degli operandi (vengono evidenziati i principali):
16 bit: codice istruzione |
16 bit: operando |
16 bit: codice istruzione |
16 bit: indirizzo |
Indirizzo della memoria centrale in cui si trova l'operando.
16 bit: codice istruzione |
16 bit: registro |
Numero del registro di memorizzazione interno alla CU in cui si trova l'operando.
16 bit: codice istruzione |
16 bit: registro |
Numero del registro di memorizzazione interno alla CU in cui si trova l'indirizzo della memoria centrale in cui si trova l'operando.
16 bit: codice istruzione |
16 bit: indirizzo |
Indirizzo della memoria centrale in cui si trova l'indirizzo della memoria centrale in cui si trova l'operando.
16 bit: codice istruzione |
16 bit: registro |
Numero del registro di memorizzazione interno alla CU in cui si trova l'indirizzo della memoria centrale in cui si trova l'indirizzo della memoria centrale in cui si trova l'operando.
16 bit: codice istruzione |
16 bit: offset |
Numero che sommato al contenuto del PC, dà l'indirizzo della memoria centrale in cui si trova l'operando (di quanto bisogna spostarsi dalla posizione corrente in memoria centrale).
16 bit: codice istruzione |
16 bit: offset |
Numero che sommato al contenuto di un opportuno registro di memorizzazione interno alla CU che sia indice (cioè che contenga un indirizzo di memoria centrale), dà l'indirizzo della memoria centrale in cui si trova l'operando (di quanto bisogna spostarsi, non dalla posizione corrente in memoria centrale, ma da una certa posizione memorizzata).
16 bit: codice istruzione |
16 bit: registro |
Numero del registro di memorizzazione interno alla CU in cui si trova l'indirizzo della memoria centrale in cui si trova l'operando.
16 bit: codice istruzione |
16 bit: indirizzo |
Indirizzo della memoria centrale in cui si trova l'indirizzo di un registro locale alla CU che contiene l'offset che sommato al PC da l'operando.
16 bit: codice istruzione |
16 bit: indirizzo |
Indirizzo della memoria centrale in cui si trova l'indirizzo di un registro locale alla CU che contiene l'offset che sommato al PC da l'indirizzo di memoria centrale in cui si trova l'operando.
16 bit: codice istruzione |
16 bit: |
...
16 bit: codice istruzione |
16 bit: |
...
16 bit: codice istruzione |
16 bit: |
...
16 bit: codice istruzione |
16 bit: |
...
16 bit: codice istruzione |
16 bit: |
Come il terzo caso, è differito a registro, ma ogni volta che si passa per il registro, il suo contenuto viene incrementato di uno.
16 bit: codice istruzione |
16 bit: |
Come il terzo caso, è differito a registro, ma ogni volta che si passa per il registro, il suo contenuto viene decrementato di uno.
Utilizzando il referenziamento implicito, le istruzioni sono poco potenti: ne servono molte per fare anche le operazioni più semplici. Si consideri ad esempio una istruzione che somma due interi. Bisogna prima portare i due operandi dalla memoria o dai registri in cui sono ai registri predefiniti; bisogna quindi richiamare l'istruzione ed infine spostare il risultato dal registro in cui viene di default messo, alla memoria o al registro in cui si intende conservarlo.
Le istruzioni che utilizzano l'indirizzamento esplicito, invece, sono più potenti proprio perchè più complesse. Utilizzando ancora l'esempio della somma di due interi, basta una sola istruzione, nella quale vengono contestualmente indicate le posizioni degli operandi e del risultato. Si veda a tal proposito il capitolo sulle macchine CISC e sulle macchine CISC.
Riassumendo, la potenza di una istruzione indica "quante cose riesce a fare quella istruzione tutte in una volta". Il livello di complessità è legato al modo di indirizzamento degli operandi e a tutto ciò che è di contorno all'istruzione. Non dipende, invece, dall’istruzione di persè, che è sempre la stessa (nell’esempio è "somma di interi").
Perchè un'istruzione sia eseguita da una macchina di Von Neumann sono necessarie le seguenti fasi:
Nel caricamento, il PC e la memoria centrale si mettono in comunicazione su richiesta del PC; l'indirizzo che PC punta raggiunge l'ingresso "Indirizzi" della memoria, la quale manda in output la stringa trovata; memoria centrale e decodificatore di istruzione si connettono su richiesta del PC; la stringa trovata in memoria raggiunge il decodificatore di istruzione.
Nella decodifica (riconoscimento), agisce il decodificatore di istruzione oppure la micromacchina, che decodifica il campo "codice istruzione".
Nella esecuzione, agisce il codificatore dei comandi, che manda istruzioni a tutte le unità che servono (ad esempio fa saltare il PC alla locazione di memoria dove si trova un certo operando, ecc.).
Vedremo che sapere in quale delle tre fasi si trova la macchina, in alcuni casi, si rivela molto utile (ad es. canalizzazione).
Nei calcolatori tutti i tipi di dato vengono rappresentati tramite stringhe di bit di lunghezza fissa.
Ad ognuna di queste stringhe è associato un significato, che è proprio il dato che essa rappresenta.
Il significato che più di frequente si usa è quello numerico.
In questo caso, ogni stringa binaria rappresenta un numero.
Per rappresentare i numeri naturali si usa la rappresentazione binaria standard.
Per rappresentare i numeri interi si usa la rappresentazione in complemento a 2.
Per rappresentare i numeri reali si usa la rappresentazione in virgola mobile.
Viene utilizzata la rappresentazione in complemento alla base (che nel caso binario è, appunto, 2). Questa è identica alla rappresentazione dei numeri naturali, tranne per il fatto che il bit più significativo viene interpretato in due modi: come indicatore del segno (1 per il meno, 0 per il più) e come cifra vera e propria. In questa rappresentazione, infatti, esiste la proprietà che se un numero è negativo inizia con uno e se è positivo inizia con zero.
Lo zero, per convenzione, viene rappresentato con una sequenza di zero ed è dunque considerato positivo (nonchè pari).
Esempio (per stringhe di 4 bit):
Stringa binaria |
Interpretazione binaria standard |
Interpretazione complemento a due |
0 0 0 0 |
0 |
0 |
Il -8 non viene usato perchè se si calcolasse il suo complemento si otterrebbe zero invece che +8 (lo zero "ruba" uno spazio ai positivi). Si preferisce, dunque, non usarlo.
Le operazioni si fanno come per i numeri naturali, senza considerare il MSB come bit di segno.
Una proprietà di questa rappresentazione è che per ottenere l'opposto di un numero basta complementarne tutti i bit e sommare uno alla cifra meno significativa.
Questa caratteristica è molto utile perchè permette di usare un circuito addizionatore sia per sommare che per sottrarre due numeri. Per sottrarli, infatti, basta sommare al primo l'opposto del secondo e l'opposto del secondo si ottiene con un invertitore (porte not) ed un sommatore.
Viene utilizzata la rappresentazione in virgola mobile. Ogni numero reale r viene espresso nella forma seguente:
r = s * m * be
dove
srappresenta il bit di segno del numero
Nella cella di memoria che dovrà contenere un numero reale così rappresentato vengono memorizzate soltanto le seguenti informazioni:
bit di segno |
parte frazionaria della mantissa |
esponente |
Ognuna di queste 3 informazioni occupa un ben determinato numero di bit, che ora non ricordo, e tutte insieme sono sufficienti per ricostruire il numero di partenza.
Addizioni e sottrazioni: Moltiplicazioni: Divisioni:
Analizzando la logica di funzionamento di un calcolatore, appare evidente che esistono due metodi per tenere in memoria (ad es. in una word) una stringa binaria rappresentante un dato:
MSB |
LSB |
LSB |
MSB |
Il metodo usato viene stabilito dal costruttore e, ovviamente, condiziona tutta l’architettura del calcolatore. Nelle macchine RISC si usa il metodo big endian, nelle CISC il metodo little endian.
La memoria centrale è organizzata in word. Questo significa che gli indirizzi di memoria puntano ognuno all’inzio di una serie di celle di memoria in grado di contenere una word di bit.
Talvolta, però, può essere più comodo utilizzare tipi di dato più corti di una word.
Se ad esempio la word è di 4 byte, può essere comodo lavorare su un tipo di dato lungo un solo byte, pensando la memoria organizzata in byte.
Pensare la memoria in byte, però, non significa che al posto delle parole si indirizzano i singoli byte - ciò non è possibile se non esplicitamente previsto in fase di progettazione -, ma significa considerare solo il primo byte di ogni parola (oppure l’ultimo, a seconda della scelta progettuale). Tutto il resto della parola può contenere una sequenza di zero oppure l'estensione del segno se vi era memorizzato un numero in complemento a due.
Tutto ciò, quindi, risulta essere necessario perché solo le parole sono direttamente indirizzabili.
Per un calcolatore lo scorrere del tempo è legato al cambiamento di stato del segnale di clock. Quando il segnale emesso dal circuito temporizzatore passa da alto a basso o viceversa, per il computer è passata un'unità di tempo.
Alla luce di ciò, se si interrompe il clock, è possibile fermare il "tempo macchina", così che il calcolatore non si accorga più del passare del tempo.
Partendo da questa idea è nato il concetto di interruzione ed eccezione. Al presentarsi di un apposito segnale che arriva alla CPU dall'hardware o dal software, il normale flusso del programma in esecuzione viene interrotto.
Sarà possibile interrompere il programma in esecuzione "non facendo più arrivare il segnale di clock a quel programma", ovvero interrompendo lo scandire delle sue istruzioni da parte del PC. Questo, però, non è sufficiente: soltanto salvando nello stack tutti i registri utilizzati dal programma (sia quelli interni alla CPU che le celle di memoria della RAM), nonchè il valore puntato dal program counter e il registro di stato della macchina, qualsiasi cosa succeda, quando si ritorna ad eseguire il programma sospeso, esso non si accorgerà di essere stato interrotto.
Il salvataggio e ripristino di tutti questi registri viene detto rispettivamente salvataggio e ripristino dello stato volatile della macchina.
Viene utilizzato lo stack proprio per la sua filosofia di accesso LIFO, ideale per questi casi di salvataggio e successivo ripristino, nonchè per l’annidamento di più eccezioni e/o interruzioni.
Ci si potrebbe chiedere il motivo della necessità di salvare anche il registro di stato della macchina. Questo è necessario perché, mentre il programma viene sospeso, potrebbe succedere qualsiasi cosa. Ad esempio la macchina potrebbe essere spenta ed il contenuto del registro di stato andrebbe perso. Potrebbe anche succedere che i flag logici vengano modificati, eccetera.
Un'interruzione o eccezione viene richiesta alla macchina tramite un opportuno segnale che arriva alla Control Unit.
Questo segnale può avere tre provenienze, generando tre tipi di interruzioni
Questa suddivisione è puramente concettuale, dato che la macchina tratta tutti e tre i tipi di interruzione allo stesso modo.
Ricapitolando, le interruzioni esterne ed interne vengono generate dalla macchina stessa, mentre quelle software, dette eccezioni, vengono generate dal programmatore assembly.
Alla luce di quanto sinora esposto, appare evidente che le interruzioni (e non le eccezioni) sono eventi asincroni, ovvero possono accadere in qualsiasi momento, senza possibilità di prevedere quando.
Di seguito, con il termine interruzione verranno indicate sia le interruzioni vere e proprie che le eccezioni.
Quando la CPU riceve una richiesta di interruzione, sia essa interna, esterna o software, esegue i seguenti passi:
Il programmatore assembly che scrive i programmi di servizio come prima cosa deve provvedere a salvare nello stack tutti i registri che modificherà. Di conseguenza, al termine del programma di servizio dovrà ripristinare tali registri. Questi due passaggi sono assolutamente necessari perchè il programma interrotto non si accorga di nulla.
Il salto al sottoprogramma di servizio è per molti versi simile ad un salto a subroutine. Esistono tuttavia alcune differenze, che comportano l'esistenza di istruzioni diverse per il salto ed il ritorno da subroutine e per il salto ed il ritorno da sottoprogrammi di servizio. La prima differenza è che nel salto a sottoprogramma di servizio viene salvato, oltre al contenuto del PC, anche il contenuto del registro di stato della CPU, cosa che non accade nel salto a sottoprocedura. Un'altra differenza sta nel fatto che il salto a sottoprogramma di servizio ha un tramite: il vettore delle interruzioni.
Per multiprogrammazione si intende la possibilità di far girare più programmi contemporaneamente su una stessa macchina. Si badi bene al significato del termine contemporaneamente, che non indica la contemporaneità di esecuzione delle istruzioni dei due programmi - cosa questa che contrasterebbe con la definizione di macchina di Von Newman -, ma indica che più programmi si "contendono" l’uso della CPU, interrompendosi a vicenda (le macchine di Von Neumann hanno un solo PC, quindi per eseguire più programmi contemporaneamente non possono far altro che "zompettare" da uno all'altro).
E’ possibile quindi eseguire un'altro programma mentre quello precedentemente in esecuzione rimane in sospeso, "non facendo più arrivare il segnale di clock a quel programma", ovvero interrompendo lo scandire delle sue istruzioni da parte del PC, che passerà a scandire le istruzioni del secondo programma. Questo, però, non è sufficiente: soltanto salvando nello stack lo stato volatile della macchina, quando si ritorna ad eseguire il programma sospeso, questo non si accorgerà di essere stato interrotto.
La multiprogrammazione ha rivoluzionato il modo di utilizzare i calcolatori. In sua assenza, l’unico modo per eseguire un programma in contemporanea ad un altro sarebbe quello di linkarli (si veda il capitolo seguente per una trattazione del linker, strumento che comunque dovrebbe essere già noto sia dal corso di Programmazione, che dal corso di Laboratorio di Architetture).
Si pensi che ai nostri giorni ci sono sempre almeno due programmi in esecuzione contemporaneamente in un calcolatore: il sistema operativo e il programma che si sta utilizzando. Senza la multiprogrammazione, i sistemi operativi non potrebbero esistere, o meglio, potrebbero esistere, ma ogni volta che si avesse la necessità di aggiungere un nuovo programma, bisognerebbe linkarlo col resto dei programmi, ricompilando il tutto.
In genere, il colloquio fra CPU e periferiche esterne al calcolatore avviene tramite interruzioni. Basti pensare alla tastiera: ogni volta che si batte un carattere, viene generata un'interruzione in attesa dell'immissione del carattere successivo. In questo modo, fra l'immissione di un carrattere e quello successivo, il calcolatore può eseguire altri programmi.
Le interruzioni interne vengono quasi sempre utilizzate per gestire errori. Errori tipici che generano interruzioni interne sono: istruzione inesistente, cella di memoria inesistente, violazione dello stack (stack overflow), errori aritmetici, ecc.
Ciò non toglie che anche le interruzioni esterne e software possano essere utilizzate per gestire errori.
A tal proposito, un secondo tipo di classificazione delle interruzioni è il seguente:
La gestione degli errori segnalati tramite interruzioni spetta poi al sottoprogramma di servizio.
Le interruzioni verranno trattate approfonditamente anche nel corso di "Sistemi Operativi". Si vedrà, ad esempio, che per evitare che i programmi che non generano mai interruzioni monopolizzino l'uso della CPU finchè non terminano, il S.O. genera interruzioni fasulle a cadenza fissa (sistemi operativi a time sharing che usano il real-time clock).
Interruzioni ed Eccezioni nel MIPS[Lezione di Laboratorio di Architetture; ho deciso di trattare questo argomento, vista la sua difficoltà] Interruzioni ed eccezioni sono eventi che alterano il normale svolgimento del programma.
0->interruzione: interruzione da periferica In genere: se è zero è una interruzione, altrimenti è una eccezione. E' necessario distinguere i due casi, poichè vengono trattati in modo leggermente diverso fra loro.
A questi registri si accede tramite le istruzioni:
: (move from coprocessor 0) copia il contenuto del registro rsrc del coprocessore 0 al registro rdest del processore principale
: (move to coprocessor 0) viceversa
: analogo a lw
: analogo a sw L'istruzione
Per gestire le interruzioni:
SPIM simula due periferiche che utilizzano l'I\O con interruzioni: tastiera (input) e schermo (output).
Il monitor lancia una interruzione quando l'operazione di scrittura su schermo è terminata.
|
Le cose che ho scritto di seguito sono scritte molto meglio nel materiale del corso "Laboratorio di Architetture I", disponibile sul sito http://twiki.dsi.uniroma1.it/twiki/view (Twiki).
L'assembly è IL linguaggio di programmazione a basso livello (più vicino alla macchina).
Viene "costruito" sull'architettura della macchina e per questo ne sfrutta tutte le caratteristiche.
Questo è il motivo per cui viene studiato nel corso di Architettura degli Elaboratori e questo è il motivo per cui, in questo corso, ogni volta che si parla di "programmatore", si intende "programmatore assembly", ovvero che scrive in assembly i suoi programmi.
L’assembly è composto da:
Quando un file o modulo sorgente scritto in assembly viene tradotto in modulo oggetto, l'assemblatore trasforma il codice mnemonico in stringhe binarie e risolve soltanto i salti interni al modulo.
Il modulo oggetto sarebbe già perfettamente eseguibile se non ci fossero salti e riferimenti irrisolti ad altri moduli oggetto.
Un programma di nome linker si occupa di fondere in modo corretto i moduli oggetto che servono. I moduli oggetto fusi, possono derivare anche da altri linguaggi di programmazione, oltre all'assembly. Viene creato così il modulo eseguibile.
In particolare, nei moduli oggetto: gli indirizzi assoluti vengono fatti partire da zero; i salti ed i riferimenti ad etichette esterne non sono risolti.
Se non esistessero, per far funzionare più programmi contemporaneamente bisognerebbe linkarli. Le istruzioni di trap, in fondo, eseguono una specie di link tramite gli indirizzi assoluti del vettore delle interruzioni: la stessa cosa che fa il linker quando cambia gli indirizzi assoluti dei moduli che unisce.
Se non esistessero, più programmi che usano una stessa libreria dovrebbero essere linkati ognuno alla sua copia della libreria. Tramite trap, invece, viene caricata una sola copia della libreria a cui i vari programmi accedono a turni.
Se non esistessero, non si potrebbe avere la struttura di sistema operativo che si ha attualmente: un nucleo gestisce il colloquio tra applicazioni e periferiche interne ed esterne. Questi colloqui, infatti, nel caso nucleo-applicazioni vengono gestiti tramite trap.
Il software che deve essere fornito dal produttore della macchina.
Senza questi programmi, la macchina sarebbe inutilizzabile.
Ciò che assolutamente non può mancare nel software di base:
Per avere una macchina efficiente, sarebbe opportuno scrivere almeno il software di base in assembly.
La velocità dei circuiti elettronici dipende dalla velocità di commutazione delle loro porte logiche. Purtroppo, maggiore è la velocità di una porta, più elevato è il suo prezzo.
La memoria centrale è composta da moltissime porte, almeno 4 per ogni bit che può contenere. La CPU è composta da molte meno porte. Questo comporta che la CPU possa essere costruita con porte più veloci di quelle della memoria centrale. Avere la RAM più lenta, però, rallenta di conseguenza la CPU quando questa ha bisogno di recuperare stringhe dalla memoria (molto spesso), vanificando la sua maggiore velocità.
Per evitare tutto ciò è stata introdotta la gerarchia di memoria, un accorgimento esclusivamente pratico.
Fra CPU e RAM viene interposta una memoria costruita con la stessa tecnologia della CPU, detta cache memory o memoria nascosta. Essendo realizzata con porte costose (le stesse della CPU), deve essere in quantità molto minore rispetto alla RAM, per limitare i costi; ciononostante, essa può contenere centinaia di word.
La CPU non colloquia direttamente con la RAM, ma con la cache. Quando la control unit richiede un nuovo indirizzo alla cache, se questa ha la stringa relativa a quell'indirizzo, la manda alla CPU direttamente, altrimenti carica dalla memoria centrale - passando attraverso il bus - non solo quella stringa,
ma tutto un blocco di stringhe limitrofe. Questo trasferimento da memoria centrale a cache avviene in modo parallelo: in una sola volta vengono trasferite più stringhe. Tramite questo accorgimento, la gerarchia di memoria riesce ad essere molto efficiente e permette alla CPU di lavorare quasi sempre alla sua massima velocità, colloquiando solo con la cache e minimizzando gli accessi alla memoria centrale.
Perchè tutto funzioni correttamente, è necessario che la cache lavori con una logica diversa da quella della memoria centrale. La cache è, infatti, una memoria di tipo associativo.
Questi tipi di memoria ricevono una maschera (una parte) del contenuto che si cerca e restituiscono tutto il contenuto associato a quella maschera. In particolare, nella cache ogni registro non contiene soltanto il dato vero e proprio, ma anche il suo indirizzo in memoria centrale (che è la maschera). Alla cache arriva una parte della stringa cercata - l'indirizzo - ed essa restituisce tutto il dato - indirizzo + dato vero e proprio.
Ciò è necessario perchè permette di memorizzare dati in qualsiasi ordine e non sequenzialmente (dopo il dato di indirizzo 0000, nella RAM ci deve essere il dato di indirizzo 0001, mentre nella cache ci può essere qualsiasi dato), fattore, questo, che risulta indispensabile proprio per come funziona la cache.
Ovviamente, perché ogni registro della cache contenga indirizzo + dato, è necessario utilizzare registri più capienti di quelli della RAM, aumentando ulteriormente il prezzo.
Ultime osservazioni:
questo meccanismo gerarchico risulta trasparente al programmatore assembly;
programmi con molte istruzioni di salto e in cui tali salti sono particolarmente ampi sono poco efficienti perchè richiedono dati non contenuti nella cache; per questo alcuni costruttori forniscono istruzioni assembly di salto con un offset limitato;
in questo corso non si tratteranno i criteri per la sostituzione dei dati all'interno della cache.
Ci possono essere diversi livelli di cache, via via più lenti, ma anche più capienti e meno costosi. In genere, la cache di primo livello (L1) si trova integrata nel chip della CPU (il core), la cache di secondo livello (L2) si trova sulla scheda della CPU ed una eventuale cache di terzo livello (L3) viene posta nei pressi della CPU.
Come visto in precedenza, le macchine di Von Neumann hanno bisogno di tre fasi per arrivare a fare quello che è richiesto da una istruzione. Queste fasi sono: caricamento, decodifica, esecuzione.
Ognuna di esse richiede un certo tempo: il caricamento richiede un tempo Δt
1 fisso, la decodifica richiede un tempo Δt2 fisso se la macchina non è microprogrammata o variabile altrimenti, l'esecuzione richiede un tempo Δt3 variabile.Progettando il calcolatore in modo tale che le varie fasi non interferiscano l'una con l'altra, è possibile iniziare il caricamento dell'istruzione successiva mentre si stà ancora decodificando l'istruzione corrente.
Ciò è possibile progettando macchine in cui le tre fasi richiedano lo stesso tempo, altrimenti i tempi non si incastrerebbero bene [si vedano le macchine RISC].
Grazie alla canalizzazione, al tendere ad infinito delle istruzioni eseguite, si riduce il tempo di un terzo (tre volte più veloce).
Ultime osservazioni:
in realtà il tempo diminuisce di uno fratto il numero di fasi, quindi di 1\3 nel nostro caso; esistono tuttavia calcolatori progettati per avere molte più fasi, così da ottenere una riduzione più marcata di tempo (computer vettoriali, studiati ad Architetture III);
il meccanismo di pipelining è trasparente al programmatore assembly;
quando si hanno istruzioni di salto il metodo perde la sua efficacia perchè l'istruzione successiva già acquisita non serve più; questo problema si avverte ancora di più sui calcolatori con molte fasi (bisogna svuotare la pipeline di tutte le istruzioni che ci si stanno trattando in anticipo); per ovviare a ciò sono stati introdotti algoritmi di branch prediction, ovvero di previsione dei salti, in grado di aumentare di molto l’efficienza del pipelining.
Può capitare di dover eseguire un programma più grande della memoria centrale disponibile. Quando ciò avviene, il programma non può essere caricato nella RAM e quindi non può essere eseguito. In realtà esiste un metodo per risolvere tale problema: la memoria virtuale.
L'idea di base è la seguente: innanzitutto serve una memoria diversa dalla RAM per contenere quella parte di programma che non vi entra - in genere viene utilizzata una periferica, come ad esempio una unità disco; quando il programma deve essere eseguito, si carica nella RAM solo la parte che entra, mantenendo il resto sulla periferica.
Questo principio deve essere implementato in due modi diversi, a seconda che la memoria indirizzabile sia minore o maggiore della memoria fisica. Come già visto, infatti, per indirizzare la memoria si usano stringhe numeriche di n bit, dove in genere n coincide con la dimensione di una word. In questo modo si possono indirizzare 2n celle. Esistono tre eventualità: la memoria fisica ha N=2n celle; la memoria fisica ha N>2n celle (più celle di quante se ne possono indirizzare e quindi è divisa in pagine); la memoria fisica ha N<2n celle (meno celle di quante se ne possono indirizzare). Tenendo presente che il primo caso si può trattare sia come il secondo che come il terzo, rimane da vedere come realizzare una memoria virtuale negli ultimi due casi.
Si supponga che N>2n .
In questo caso, il programma più grande della memoria deve essere spezzato in un main che richiami subroutines.
In memoria viene caricato un ramo alla volta, ad esempio il ramo Main-S1-S2-S4.
Finchè si lavora su quel ramo non si hanno problemi. Se però da quel ramo bisogna passare a S5, per arrivarci occorre togliere dalla RAM S4 e metterci S5. Se invece bisogna andare ad S7, è necessario togliere S4 ed S2 per mettere S3 ed S7. Per andare infine ad S15, sarebbe stato necessario togliere S4, S2, S1 per mettere S12, S13 ed S15.
In questo caso, con togliere e mettere si possono intendere due azioni distinte: togliere e mettere in un'altra pagina o, se tutte le pagine sono occupate, togliere e mettere sulla periferica utilizzata.
Perchè tutto funzioni occorre che:
Per questo, la memoria virtuale ad overlay (che significa sovrapposizione) permette di eseguire programmi lunghi a piacere ma non è trasparente al programmatore assembly, perchè questo deve scrivere un opportuno linker e deve spezzare opportunamente i programmi in subroutines.
Si supponga che N<2n.
In questo caso il programmatore assembly non si preoccupa che N<2n, ma sfrutta tutti i 2n indirizzi possibili, anche se questi non corrispondono a nessuna cella. Questi indirizzi scritti dal programmatore assembly prendono nome di indirizzi virtuali.
Si supponga, ad esempio, di trovarsi nel caso in cui un programma di 2MB debba essere caricato in una RAM da 1 MB per essere eseguito.
Come prima cosa, la macchina carica quel che può: il primo MB.
I problemi nascono quando il programmatore effettua un salto ad un'istruzione presente nel secondo MB, oppure quando si arriva all'ultima istruzione del primo MB. Quando ciò avviene, si ha una interruzione interna perchè ci si è riferiti ad un indirizzo inesistente. Si ha quindi un salto al programma di servizio che toglie il MB già caricato, salvandolo su disco, e carica il rimanente MB al suo posto.
Ciò comporta due problemi. Il primo è che gli indirizzi del secondo MB risultano tutti sfasati di un MB. Il secondo è che scaricate tutto quello che c'era in memoria e caricare tutto quello che non entrava è molto inefficiente.
Riguardo alla prima questione, basta una minima modifica harware: bisogna inserire un registro di offset che agisca sul P.C. .
Si ha quindi che:
indirizzo virtuale =
indirizzo fisico (valore del P.C.) +
offset (numero nel registro di offset)
L'offset è di 0 MB quando ci si trova nel primo MB ed è di 1 MB quando viene caricato il secondo pezzo.
Riguardo alla seconda questione, in realtà non viene scaricato e poi caricato tutto il contenuto della RAM, ma "pezzi" di RAM detti pagine. Si dice quindi che la memoria è paginata e l'operazione che carica e scarica pagine fra disco e memoria centrale è detta swapping. dire che una memoria ha una struttura a pagine è completamente diverso dal dire che è paginata; sono due concetti differenti ed indipendenti l'uno dall'altro.
La tecnica dello swapping non è trasparente al programmatore assembly, perchè questo deve scrivere un opportuno programma di servizio dell’interruzione.
H - MACCHINE CISC E MACCHINE RISC
RISC: reduced instruction set computer.
CISC: complex instruction set computer.
Le macchine RISC hanno istruzioni più semplici, cioè meno potenti.
Le macchine CISC hanno istruzioni più complesse, cioè più potenti.
La potenza di una istruzione indica "quante cose riesce a fare quella istruzione tutte in una volta".
La complessità ovvero potenza di una istruzione è legata al modo di indirizzamento degli operandi e più in generale a tutto ciò che è di contorno all'istruzione.
Utilizzando l'indirizzamento implicito, le istruzioni sono poco potenti: ne servono molte per fare anche le operazioni più semplici.
Una istruzione di somma, ad esempio, è sempre una istruzione che attiva l'opportuno circuito della CPU, ma se ci si riferisce agli operandi implicitamente, allora è meno potente e più semplice, se al contrario gli operandi sono indicati espicitamente, è più potente e complessa. Nel caso dell'istruzione semplice, bisogna prima portare i due operandi dalla memoria o dai registri in cui sono ai registri predefiniti; bisogna quindi chiamare l'istruzione; bisogna infine spostare il risultato dal registro in cui viene di default messo, alla memoria o al registro in cui si intende conservarlo. Per la somma con indirizzamento esplicito, invece, basta una sola istruzione, nella quale vengono contestualmente indicate le posizioni degli operandi e del risultato.
Le istruzioni delle macchine RISC, avendo una carenza di modi di indirizzamento proprio perché sono poco potenti, operano direttamente sui registri della CPU e non sulla memoria centrale. Vengono a tal proposito fornite solo due istruzioni che accedono alla RAM: load e store. Con queste si caricano le celle di memoria su cui si vuole operare nei registri della CPU e viceversa. L’istruzione move, al contrario, può agire solo sui registri della CPU.
Un'altro degli elementi di contorno che determina la maggiore o minore complessità è il formato delle istruzioni: nelle macchine CISC può essere variabile (come già visto, questo significa che la lunghezza delle istruzione può variare di multipli di word), nelle RISC è fisso ed in genere pari ad una word.
Ovviamente esistono macchine intermedie fra RISC e CISC.
I motivi della distinzione fra macchine RISC e macchine CISC sono storici:
Vantaggi delle macchine RISC: non utilizzano la microprogrammazione (utilizzando istruzioni semplici, il decodificatore non è troppo complesso); permettono una più semplice ed efficace canalizzazione (le tre fasi di caricamento, decodifica, esecuzione richiedono lo stesso tempo, visto anche il fatto che la microprogrammazione non è necessaria e perchè è più facile fare in modo che le tre fasi non interferiscano fra loro); sono più facili da realizzare; spesso si tende a non utilizzare molte delle istruzioni complesse delle macchine CISC; sono più veloci; permettono una portabilità del codice più agevole (essendo più semplici delle CISC, permettono ai vari costruttori di realizzare macchine più simili e con set di istruzioni molto vicini fra loro).
Un esempio di macchina RISC si studia nel Laboratorio di questo corso: la macchina MIPS.
Come esempio verrà presa la famiglia di calcolatori PDP 11, prodotta negli anni settanta dalla Digital.
Per approfondire l’argomento si consigliano i seguenti siti:
http://simh.trailing-edge.com/pdp11.html
http://www.conknet.com/~w_kranz/pdp11/pdp11.htm
http://www.pdp11.org/ .
Questa famiglia di calcolatori aveva le seguenti caratteristiche:
- |
N Z V C |
TRAP (1 bit) |
PRIORITÀ (3 bit) |
MODO FUNZIONAMENTO (2 bit) |
- |
con N, Z, V e C risultati logici attuali, TRAP t.c. ogni volta che il programmatore lanciava una eccezione si alzava fino al ritorno dal programma di servizio (serviva per il debugging), PRIORITÀ del processo attuale (da 0 a 7 con 0 il massimo), scritto dal programma in esecuzione, che stabiliva da chi questo poteva accettare interruzioni\eccezioni, MODO DI FUNZIONAMENTO comprendente tre opzioni: sistema operativo, utente e intermedio; il set di istruzioni veniva modificato non permettendo l'accesso ad istruzioni pericolose (come lo spegnimento della macchina) all'utente
I modi di indarizzamento possibili erano (il numero che li identifica veniva messo nel campo "modo di indirizzamento" delle istruzioni):
Sul registro 7 della CPU (P.C.) si potevano utilizzare solo 2,3,6,7. Nel paragrafo 3.8 si vedrà come fosse possibile utilizzare anche gli indirizzamenti immediato ed assoluto grazie a questa caratteristica del registro 7.
Il formato di queste istruzioni era il seguente:
1 bit |
3 bit |
6 bit |
6 bit |
Se il primo bit era 1, segnalava che gli operandi erano mezze parole, se era zero segnalava che erano word: poiché era possibile indirizzare sia mezze parole che parole, tutte le istruzioni potevano lavorare su entrambe i formati.
I 3 bit seguenti indicavano l'operazione che si voleva eseguire (codice dell'istruzione). Il massimo numero di istruzioni a due operandi d’ingresso possibile era quindi di otto (23).
Nei successivi 6 bit si trovava il primo operando. Questi 6 bit venivano divisi in due parti da 3 bit ognuna: nei primi veniva indicato il modo di indizzamento (infatti c'erano otto modi - 23) mentre negli ultimi veniva indicato il registro a cui si riferiva il modo di indirizzamento (infatti c'erano otto registri nella CPU - 23).
Gli ultimi 6 bit erano analoghi ai precedenti, ma contenevano il secondo operando.
Nel caso di operazioni con un valore di ritorno, il risultato veniva messo in questi ultimi 6 bit, sovrascrivendo il secondo operando.
Le istruzioni avevano i seguenti codici mnemonici:
MOV: spostava una word da un posto ad un'altro
CMP: comparava i due operandi, mettendo il risultato nei bit logici di PSW
BIT: and logico
BIC: mascheramento - azzerava tutti i bit del I operando che corrispondevano agli uno del II operando (che era la maschera)
BIS: or esclusivo (xor)
ADD: somma e sottrazione
Tutte queste istruzioni operavano su parole.
Aggiungendo una B alla fine del nome si ottenevano istruzioni operanti su mezze parole.
Il formato di queste istruzioni era il seguente:
1 bit |
9 bit |
6 bit |
Se il primo bit era 1, segnalava che l'operando era una mezza parola, se era zero segnalava che era di una word: poiché era possibile indirizzare sia mezze parole che parole, tutte le istruzioni potevano lavorare su entrambe i formati.
I 9 bit seguenti indicavano l'operazione che si voleva eseguire (codice dell'istruzione), che quindi erano molte di più delle precedenti (29).
Negli ultimi 6 bit si trovava l'operando. Questi 6 bit venivano divisi in due parti da 3 bit ognuna: nei primi veniva indicato il modo di indizzamento (infatti c'erano otto modi - 23) mentre negli ultimi veniva indicato il registro a cui si riferiva il modo di indirizzamento (infatti c'erano otto registri nella CPU - 23).
Nel caso di operazioni con un valore di ritorno, il risultato veniva messo in questi ultimi 6 bit, sovrascrivendo l'operando.
Le istruzioni avevano i seguenti codici mnemonici:
JMP: salto incodizionato con un offset di 6 bit
SWAB: scambiava le due mezze parole di una parola fra loro
CLR: azzerava l'operando
COM: ???
INC: incrementava di uno l'operando
DEC: decrementava di uno l'operando
NEG: negava l'operando complementando i suoi bit
ADC: addizionava il riporto di una somma precedente, contenuto nei bit dei risultati logici di PSW; serviva per addizionare stringhe più lunghe di una word
SBC: sottraeva il prestito di una differenza precedente, contenuto nei bit dei risultati logici di PSW; serviva per sottrarre stringhe più lunghe di una word
TST: testava la positività o negatività dell'operando rappresentato in Ca2, controllando il MSB; il risultato veniva scritto nei risultati logici in PSW
ROR: shift logico a destra di una posizione (i bit che uscivano da un lato rientravano dal lato opposto: serviva per fare operazioni bit a bit)
ROL: shift logico a sinistra di una posizione (i bit che uscivano da un lato rientravano dal lato opposto: serviva per fare operazioni bit a bit)
ASR: shift aritmetico a destra di una posizione (i bit che escivano da dx NON rientravano da sn, perchè a sn entrava lo stesso bit che c'era per primo, per mantenere il segno: serviva per dividere per multipli di due l'operando)
ASL: shift aritmetico a sinistra di una posizione (i bit che escivano da sn NON rientravano da dx, perchè a dx entravano zeri, per mantenere il numero: serviva per moltiplicare per multipli di due l'operando)
SXT: estendeva il segno per i numeri in Ca2 (ad es. passando da 8 bit a 16 bit, ripeteva a sinistra il bit più significativo fino a coprire tutti i 16 bit della word)
Aggiungendo una B alla fine del nome si ottenevano istruzioni operanti su mezze parole.
Il formato di queste istruzioni era il seguente:
8 bit |
8 bit |
I primi otto bit rappresentavano il codice dell'istruzione, gli ultimi otto bit rappresentavano l'offset (quindi indirizzamento relativo). Si potevano pertanto effettuare salti a +127 e –128 posizioni dal contenuto del P.C. .
Le istruzioni avevano i seguenti codici mnemonici:
BR: salto incodizionato con un offset di 8 bit
BNE: saltava se nel bit Z dei risultati logici di PSW c'era 0 (non uguale)
BEQ: saltava se nel bit Z dei risultati logici di PSW c'era 1 (uguale)
BGE: saltava se maggiore o uguale
BLT: saltava se minore
BGT: saltava se maggiore
BLE: saltava se minore o uguale
BLP: saltava se positivo
BMI: saltava se negativo
BHI: saltava se maggiore o uguale per naturali non in Ca2
BLOS: saltava se minore o uguale per naturali non in Ca2
???: saltava se overflow (Ca2)
???: saltava se trabocco (naturali non in Ca2)
JSR: saltava a subroutine (salvando P.C. nello stack)
RTS: ritornava da subroutine (ripristinando P.C. dallo stack)
Il formato di queste istruzioni era il seguente:
8 bit |
8 bit |
I primi otto bit rappresentavano il codice dell'istruzione, gli ultimi otto bit rappresentavano l'opportuna locazione del vettore delle interruzioni (che quindi aveva 28 posizioni).
Le istruzioni avevano i seguenti codici mnemonici:
EMT: saltava alla specificata locazione del vettore delle interruzioni (salvando registro di stato e P.C.) - veniva raccomandato di non utilizzare questa istruzione perchè riservata alla casa costruttrice per costruire il software di base
TRAP: come la precedente ma utilizzabile liberamente; separando le due istruzioni si evitavano conflitti fra software di base e programmi utente; nulla vietava cmq di utilizzare EMT
BPT: saltava alla posizione 14 del vettore delle interruzioni, in cui si trovava l'indirizzo del programma di servizio scritto dal produttore che dopo ogni istruzione eseguita da parte del programma utente interrompeva la macchina (usato per debugging)
IOT: saltava alla posizione 20 del vettore delle interruzioni, in cui si trovava l'indirizzo del programma di servizio scritto dal produttore che gestiva l'I\O con interruzioni
RTI: ritornava dal programma di servizio (ripristinando registro di stato e P.C.)
HALT: arrestava il calcolatore
HOP: perdeva un ciclo di clock senza far nulla (utile per sincronizzazione / temporizzazione)
WAIT: arrestava la macchina in attesa di una interruzione: appena veniva lanciata una qualsiasi interruzione, il calcolatore ripartiva
RESET: metteva a zero tutti i registri interni ed esterni (periferiche) del calcolatore
Non esistevano istruzioni "native" a formato variabile.
Si è detto tuttavia che il settimo registro della CPU conteneva l’indirizzo puntato dal P.C. .
Questo permetteva di creare istruzioni a formato variabile per realizzare gli indirizzamenti assoluto e immediato. Analizzando le istruzioni sinora trattate, infatti, si nota che questi due tipi di indirizzamento non erano possibili, poichè nel campo dell'istruzione contenente gli operandi non ci sarebbe stato sufficiente spazio per mettere direttamente il valore di questi (sarebbero serviti 16 bit per ogni operando).
Ad esempio scrivendo:
ADD (R1), (R2)
gli operandi non erano contenuti nella word di ADD, dove si trovavano, invece, soltanto i nomi dei registri che li contenevano.
Scrivendo:
ADD (R7)+, (R7)+
veniva comunicato alla macchina che gli operandi si trovavano nelle due word successive a quella dell'istruzione ADD. Si otteneva, in pratica, una istruzione di tre word di cui una contenente ADD e le altre due contenenti gli operandi.
Ovviamente si poteva utilizzare questo trucco anche su di un solo operando, così da ottenere una istruzione di 2 word:
ADD (R1), (R7)+
Sono dispositivi collegati al calcolatore tramite il canale di I\O.
Supporto magnetico in grado di conservare nel tempo stringhe binarie e restituirle su richiesta del calcolatore.
Caratteristiche di questa periferica sono l'elevata capienza, la possibilità di conservare informazioni anche a calcolatore spento e in assenza di corrente, nonchè la latenza molto maggiore rispetto a quella della RAM (per via dell'architettura costruttiva).
All'interno dei calcolatori, dunque, convivono vari tipi di memoria, che possono essere suddivisi in maniera generale in memoria volatile e memoria permanente. Questa suddivisione si riflette nello studio della psicologia del pensiero, dove viene teorizzata la presenza di due tipi di memoria anche all'interno del cervello umano. Una memoria a breve termine sarebbe, infatti, responsabile della capacità di tenere a mente un numero di telefono dopo una breve lettura per consentire di digitarlo sulla tastiera del telefono, mentre quella a lungo termine si manifesterebbe nei ricordi e nella capacità di trattenere una grande quantità di informazioni per un lungo periodo di tempo, tipica del cervello umano. Dalla collaborazione tra questi due tipi di memoria, sostengono gli studiosi, si realizzano la comprensione del discorso e molte altre facoltà complesse. Questo tipo di suddivisione funziona perfettamente anche in ambito informatico, dove esistono memorie di massa, che possono conservare le informazioni in modo permanente senza grossi problemi di spazio, e memorie volatili, come registri e RAM, che sono deputate alle funzioni operative ma a cui non è richiesto di conservare i dati anche dopo lo spegnimento del computer.
Questo tipo di unità viene realizzata tramite dischi concentrici, detti piatti, in grado di ruotare a velocità variabile attorno ad un asse centrale comune. I piatti vengono ricoperti con un particolare materiale magnetico. Su di esso sono presenti numerosi magneti disposti inizialmente in maniera casuale, ciascuno con un polo positivo "+" ed uno negativo "-". Accoppiando i magneti due a due, a seconda dell’orientamento dei poli è possibile rappresentare i bit 0 ed 1. In particolare con lo schema "+" "-","+" "-" si rappresenta uno zero, mentre con "+" "-", "-" "+" si rappresenta un uno.
Opportune testine (due per ogni disco: una per la faccia superiore, una per quella inferiore) sono in grado sia di allineare i microscopici magneti, che di leggere il loro campo.
Le unità disco sono così organizzate: ogni piatto ha due facce, ogni faccia è divisa in cerchi concentrici detti tracce o cilindrici e ogni traccia è divisa in settori. Dunque, per accedere ad una certa stringa memorizzata, si usano indirizzi che indicano la faccia, la traccia e quindi il settore dove esso si trova. Fatto ciò, il circuito che controlla il funzionamento del disco seleziona la testina, la sposta meccanicamente sulla traccia indicata e poi aspetta che il giusto settore passi sotto di essa per iniziare la lettura o scrittura. Per verificare di aver posizionato la testina nella giusta posizione, all'inizio di ogni settore è ripotato il suo indirizzo. Tale associazione settore - indirizzo avviene al momento della formattazione del disco.
Trasferire (scrivere o leggere) un dato alla volta, però, è poco efficiente, vista la lentezza di questo tipo di periferiche rispetto alla CPU. Per minimizzare gli accessi, quindi, si effettua il trasferimento di un settore alla volta. Il quantitativo di dati che occupano un settore viene detto record. Si può quindi dire che viene trasferito un record alla volta. Si noti che il quantitativo di dati che occupano un settore è costante su tutto il disco, nonostante i settori interni siano più corti di quelli esterni. Questo perchè all'interno i dati sono via via più "fitti".
I dati vengono scritti in modo non contiguo. Ciò serve per evitare di sprecare spazio inutilmente quando si libera un settore precedente all'ultimo, ma i dati che si devono scrivere sono più grandi del settore. Questa caratteristica pone un nuovo problema: ritrovare tutti i pezzi di dato che interessano. Vengono utilizzate varie tecniche. Una di queste consiste nell'avere memorizzata all'inizio del disco una tabella con l'indirizzo di tutte le parti di ogni dato. Una seconda tecnica consiste nel mettere alla fine di ogni settore l'indirizzo del settore in cui continua il dato. Di tutto questo si occupa il sistema operativo.
Dovendo ricercare i vari pezzi, comunque, si ha una maggiore perdita di tempo. Per questo esiste la possibilità di riservare zone di disco alla memorizzazione contigua. In queste zone andranno tutti quei dati che necessitano di un accesso particolarmente veloce, come ad esempio gli swap file per la memoria virtuale.
Poichè ogni settore contiene molte word, quando si trasferisce un settore viene generato un notevole traffico di stringhe, quindi le unità disco vengono in genere collegate al calcolatore tramite il metodo del DMA.
Memoria virtuale: quando il disco è utilizzato per lo swapping, le pagine sono grandi proprio un record, per comodità.
Esistono due classi di unità disco: i dischi morbidi, in cui le testine sono a contatto con i piatti, e quelli rigidi, in cui le testine volano a pochi micron di distanza dai piatti, sostenute dal cuscino d'aria che si crea grazie alla rotazione degli stessi piatti.
Fra le due tecnologie, la prima è più lenta a causa dell'attrito, ma più resistente, la seconda è più delicata (le testine possono precipitare sul disco - crash di testine), ma più veloce (i dischi possono ruotare molto più velocemente).
Supporto magnetico in grado di conservare nel tempo stringhe binarie e restituirle su richiesta del calcolatore.
Vengono realizzati tramite nastri magnetici avvolti in bobine.
Parallele al nastro, scorrono 9 tracce. Sulle prime otto vengono memorizzati i byte, perpendicolarmente al nastro. Sull'ultima, per ogni byte memorizzato, si trova un bit di parità trasversale. Dopo un certo numero di byte, per comodità in quantità pari ad un record, si trova un byte di parità longitudinale. In questo modo è possibile identificare, come in una matrice, i bit errati e correggerli (basta complementarli).
La parità è una tecnica di rilevazione degli errori in cui si stabilisce che il numero di bit di ogni stringa deve essere pari. Per fare ciò, si aggiunge un bit alla stringa; questo prende il valore zero se già c'è un numero pari di bit e prende il valore uno se ce n'è un numero dispari, così da renderli in numero pari. In questo modo, se un numero dispari di bit è errato a causa della smagnetizzazione del nastro, la parità non torna più e l'errore può essere rilevato e corretto. Se però è errato un numero pari di bit, non si riesce a rilevarlo. Per questo si dice che la parità è una tecnica di protezione leggera. Esistono, invece, tecniche di protezione dette forti.
C'è da dire, però, che in tutte le tecniche di rilevazione degli errori bisogna aggiungere informazioni ridondanti alle stringhe che si vogliono "proteggere". In genere, più una tecnica è forte e maggiore è l’aumento di informazioni ridondanti. Via via che la protezione aumenta, dunque, lo spazio occupato è sempre maggiore: questo è il prezzo da pagare.
Le tecniche di rilevamento degli errori sono molto usate anche nella trasmissione di dati digitali.
Mentre i dischi vengono utilizzati per l’achiviazione in linea, i nastri vengono utilizzati per l'archiviazione non in linea.
L'archiviazione non in linea consiste nel memorizzare grandi quantità di dati a cui si accede raramente, ma che interessa conservare per periodi molto lunghi. Vengono per questo utilizzati dispositivi magnetici come i nastri, che hanno il vantaggio di poter essere tolti dal calcolatore e conservati a parte quando non si deve scrivere o leggere da essi. Pur essendo dispositivi magnetici, e quindi soggetti a smagnetizzazione, i nastri dispongono di varie tecniche di rilevazione e correzione degli errori, anche molto più complesse della parità e per questo risultano molto indicati per l’archiviazione non in linea.
Tuttavia, per conservare un archivio non in linea memorizzato su nastro magnetico, bisogna periodicamente eseguire la copia del nastro vecchio su di uno nuovo, così da eliminare gli errori di smagnetizzazione prima che diventino troppi per essere corregibili.
Ulteriore vantaggio dei nastri magnetici è che la scrittura e lettura da questi dispositivi è standardizzata (al contrario delle unità a disco), cioè è indipendente dal calcolatore e dal sistema operativo utilizzato.
L'archiviazione in linea, invece, consiste nella memorizzazione di dati a cui si accede più spesso e che devono comunque essere conservati anche a computer spento.
Dispositivi in grado di trasmettere dati da un calcolatore ad un altro.
Attualmente i più diffusi sono i modem. Questi sfruttano le linee telefoniche per trasferire word di bit.
Le linee telefoniche sono pensate per trasmettere la voce umana (onde sonore), in particolare quella parte di frequenze che va da 10 a 3000 Hz circa.
Si è stabilito che ad una frequenza di circa 900 Hz corrispondesse il bit 1 e che ad una frequenza di circa 950 Hz corrispondesse il bit zero. L'orecchio umano percepisce i due suoni in questione come fischi, uno più alto e uno più basso.
Applicando due filtri che rilevano queste due frequenze entrando in risonanza con esse, è possibile riconvertire le due frequenze in bit. Questo metodo è detto a modulazione di frequenza e significa che le onde che sono trasmesse oscillano tra frequenze ben determinate (900 e 950 Hz in questo caso).
Un vantaggio di utilizzare le onde sonore è che la trasmissione risulta quasi del tutto indipendente dalla potenza del segnale, poichè i filtri comunque entrano in risonanza ed amplificano le due frequenze in questione, anche se esse arrivano molto deboli.
Come visto nel paragrafo precedente, anche per la trasmissione di dati digitali vengono utilizzate diverse tecniche per individuare e correggere errori che possono avvenire durante la comunicazione (ad es. disturbi sulla linea, ecc.).
-VARICELLA martedì 28 maggio 2002-
-VARICELLA martedì 28 maggio 2002-
-VARICELLA martedì 28 maggio 2002-
Terminali video: tastiera/monitor
L’utente inserisce tramite la tastiera dati che vengono trasmessi al calcolatore e possono essere visualizzati su monitor.
L’unità di scambio è il carattere, formato di solito da 7 (ASCII) o 8 bit.
Dato che l’interazione è con operatori umani non serve una grande velocità di trasmissione viene usato collegamento seriale.
Nel caso di ingresso da tastiera quando l’utente rilascia il tasto premuto viene generato un segnale elettrico interpretato dal trasduttore della tastiera e tradotto in una sequenza di bit di codice ASCII e trasmessa al modulo di I/O del calcolatore.
Dispositivo trasmittente e ricevente devono utilizzare la stessa informazione di temporizzazione (segnale di clock): trasmissione sincrona (segnale unico) o asincrona (due segnali di clock con frequenze simili e opportuni controlli).
Trasmissione asincrona con tecnica start-stop:
o trasmissione di caratteri alfanumerici codificati con 8 bit, preceduti da un carattere 0, start, e uno o più bit 1, stop.
o la linea di trasmissione è nello stato 1 quando inattiva (rimane a 1 dopo il bit stop).
A - RICHIAMI *
1. VELOCITA' DELLE PORTE *
2. LA WORD *
B - BLOCCHI BASE *
1. INTRODUZIONE *
2. BLOCCHI COMBINATORI *
2.1 Codificatori *
2.1.2 Decodificatore *
2.1.4 Rom *
2.2 Commutatori *
2.2.1 Multiplexer *
2.2.2 Demultiplexer *
2.2.3 Multiplexer + demultiplexer *
2.3 Sommatori aritmetici *
2.3.1 Half adder *
2.3.2 Full adder *
2.3.3 Addizionatore aritmetico *
2.4 Comparatori *
2.4.1 Comparatore aritmetico *
2.4.2 Comparatore logico *
3. BLOCCHI SEQUENZIALI *
3.1 Flip-Flop *
3.1.1 Tipo JK *
3.2 Contatori *
3.2.1 Contatore binario *
3.2.2 Contatore binario preselezionabile *
3.3 Registri o buffer *
3.3.1 Registro di memorizzazione *
3.3.2 Registro a scorrimento (shift register) *
C - MACCHINE DI VON NEUMANN *
1. Introduzione *
2. Sistemi di Interconnessione *
3. Unità Aritmetica e Logica *
3.1 Introduzione *
3.2 Funzionamento *
4. Memoria Centrale *
4.1 Introduzione *
4.2 Funzionamento *
4.4 Accorgimenti pratici *
4.4.1 Frazionamento degli Indirizzi *
4.4.2 Assemblamento di Chip *
4.4.3 Pagine di Memoria *
5. Control Unit *
5.1 Introduzione *
5.2 Program Counter *
5.3 Decodificatore di Istruzioni *
5.4 Codificatore di Comandi *
5.5 Microprogrammazione *
5.6 Generatore di Fase *
5.7 Registri Interni alla CPU *
6. Input \ Output *
6.1 Introduzione *
6.2 Realizzazione *
6.3 Funzionamento *
7. ALTRO *
7.1 Introduzione *
7.2 AU (Unità Aritmetica) *
D - DATI ED ISTRUZIONI *
1. Introduzione *
2. Istruzioni *
2.1 Introduzione *
2.2 Tipi di Istruzione *
2.3 Campi delle Istruzioni *
2.4 Fasi *
3. Dati *
3.1 Introduzione *
3.2 Dati Numerici *
3.2.1 Rappresentazione di Naturali *
3.2.2 Operazioni su Naturali *
3.2.3 Rappresentazione di Interi *
3.2.4 Operazioni su Interi *
3.2.5 Rappresentazione di Reali *
3.2.6 Operazioni su Reali *
3.3 Verso di Memorizzazione *
3.4 Allineamento della memoria *
E - INTERRUZIONI ED ECCEZIONI *
1. Introduzione *
2. Funzionamento *
3. Utilizzi *
3.1 Multiprogrammazione *
3.2 Altro *
Interruzioni ed Eccezioni nel MIPS *
F - SOFTWARE *
1. Assembly *
1.0 Premessa *
1.1 Il Linguaggio *
1.2 Funzionamento *
1.3 L'importanza delle interruzioni software *
2. Software di base *
G - ACCORGIMENTI E TRUCCHI *
1. Gerarchia di memoria *
1.1 Introduzione *
1.2 La memoria cache *
1.3 Livelli di cache *
2. Canalizzazione *
2.1 Introduzione *
2.2 Applicazione *
3. Memoria Virtuale *
3.1 Introduzione *
3.2 Overlay *
3.2 Swapping *
H - MACCHINE CISC E MACCHINE RISC *
1. Caratteristiche *
2. Macchina RISC di esempio *
3. Macchina CISC di esempio *
3.1 Introduzione *
3.2 Caratteristiche generali *
3.2 Modi di indirizzamento *
3.3 Istruzioni a due operandi d'ingresso *
3.4 Istruzioni ad un operando d'ingresso *
3.5 Istruzioni di salto *
3.6 Istruzioni di trap *
3.7 Istruzioni di controllo *
3.8 Istruzioni a formato variabile *
I - PERIFERICHE *
1. Introduzione *
2. Unità disco *
3. Unità nastro *
4. Interfacce di comunicazione *
5. Schermo *
6. Stampante *