Dispensa corposa di Assembler

Transcript

Dispensa corposa di Assembler
S3Abacus – Architetture/Asm
SISTEMI E INFORMAZIONE..............................3
Definizioni...............................................................................................3
Classificazione.......................................................................................4
AUTOMI A STATI FINITI .............................................................5
Reversibilità...........................................................................................................5
Raggiungibilità.......................................................................................................5
Indistinguibilità.......................................................................................................5
Stati d'equilibrio.....................................................................................................5
Uscite d'equilibrio..................................................................................................5
Minimizzazione......................................................................................................6
RAPPRESENTAZIONE E CODIFICA DELL’INFORMAZIONE...................10
Notazioni e rappresentazioni posizionali.............................................10
Sistema di numerazione in base Due (BIN).......................................................11
Sistema di numerazione in base Otto (OCT).....................................................12
Sistema di numerazione in base Sedici (HEX)..................................................12
Trasformazioni tra sistemi Posizionali.................................................12
Raggruppamento di Bit.......................................................................................14
Contenitori binari..................................................................................15
Operazioni Bitwise..............................................................................................15
Maschere di bit....................................................................................................16
Numeri con segno................................................................................16
Numeri con parte frazionaria................................................................17
Rappresentazioni in eccesso 2n-1.....................................................................17
Standard IEEE 754.............................................................................................18
CODICI, COMUNICAZIONE, INFORMAZIONE..................................20
Codifiche numeriche............................................................................21
Codifica Numerica BCD......................................................................................22
Codifica Numerica Eccesso 3.............................................................................22
Codifica Numerica Aiken....................................................................................22
Codifica Numerica Gray......................................................................................22
Codifiche Alfanumeriche......................................................................22
Codifica Alfanumerica BAUDOT.........................................................................22
Codifica Alfanumerica ASCII..............................................................................22
CODICI ED ERRORI................................................................24
Parità...................................................................................................................24
Parità incrociata..................................................................................................24
Codice di Hamming.............................................................................................25
Codici Ciclici - CRC.............................................................................................26
CheckSum...........................................................................................................27
ARCHITETTURE ..............................................28
ARCHITETTURA DI VON NEUMANN............................................29
Tassonomia di Flynn...........................................................................................29
Macchina di Von Neumann..................................................................29
Memoria...............................................................................................29
Tecnologie...........................................................................................................30
Bus.......................................................................................................30
Tecnologie...........................................................................................................31
Input/Output.........................................................................................31
Tecnologie...........................................................................................................32
Processore...........................................................................................32
Cisc, Risc, Crisc..................................................................................................33
Cache..................................................................................................................33
Prefetch, Pipeline, Superscalarità......................................................................33
Esecuzione Predicativa e Speculativa...............................................................34
Esecuzione Fuori ordine e VLIW........................................................................34
ARCHITETTURE INTEL............................................................35
x-86......................................................................................................35
Intel 8086.............................................................................................................35
Registri................................................................................................................35
Indirizzamento.....................................................................................................36
IA-32.....................................................................................................36
Netburst, EM64T.................................................................................................37
MultiCore ............................................................................................................37
IA-64.....................................................................................................37
Tabella riassuntiva architetture Intel (2008).........................................38
ASSEMBLY X-86..............................................39
Istruzioni x-86.......................................................................................39
Registro Flags (PSW).........................................................................................41
Sintassi e indirizzamenti.....................................................................................42
x-86 e MsDos.......................................................................................42
Memoria..............................................................................................................43
Boot.....................................................................................................................43
Formato degli eseguibili e Rilocazione: EXE e COM.........................................43
API , Interruzioni Sw e Servizi............................................................................44
Video e tastiera con le Interruzioni Sw del Bios...................................45
Video e tastiera con le Interruzioni Sw di MsDos................................45
Terminare i programmi in MsDos.........................................................46
ASSEMBLY CON DEBUG.........................................................47
Comandi...............................................................................................47
Esplorare la memoria...........................................................................47
Consultare I registri..............................................................................48
Scrivere un programma........................................................................48
.doc - 1684Kb - 12/set/2008 - 1 / 82
S3Abacus – Architetture/Asm
Il programma più corto del mondo......................................................................49
Video e tastiera...................................................................................................50
Strutture di controllo.............................................................................50
Editare un programma.........................................................................52
Area Dati e area Codice.......................................................................53
Allineamento........................................................................................54
ASSEMBLY CON TASM...........................................................57
Struttura dei programmi.......................................................................57
Ciclo di vita di un programma..............................................................57
Ciclo di vita di un programma TASM...................................................58
Scrivere un programma EXE...............................................................60
Modelli di memoria...............................................................................61
Scrivere un programma COM..............................................................62
ASSEMBLY X-86 AVANZATO.........................64
MACRO.............................................................................65
Macro costanti.....................................................................................................65
Macro di codice...................................................................................................66
Macro con parametri e etichette.........................................................................67
STACK..............................................................................68
PROCEDURE.......................................................................71
Definizione di procedura ....................................................................................71
Meccanismo di chiamata....................................................................................72
Preservare i registri.............................................................................................72
PASSAGGIO DI PARAMETRI......................................................74
VARIABILI LOCALI.................................................................76
Notazioni per il passaggio di parametri e le variabili locali................................77
DIRETTIVE PER LA PROGRAMMAZIONE E LIBRERIE.........................79
Salti lunghi, direttiva JUMPS..............................................................................79
Duplicazione di etichette, direttiva LOCALS......................................................79
Librerie, direttive INCLUDE, PUBLIC ed EXTRN..............................................80
Makefile...............................................................................................................82
.doc - 1684Kb - 12/set/2008 - 2 / 82
.doc - 1684Kb - 12/set/2008 - 3 / 82
S3Abacus – Architetture/Asm
SISTEMI E INFORMAZIONE
DEFINIZIONI
Per affrontare la teoria dei Sistemi sono necessarie alcune nozioni matematiche:
- riduzione di espressioni di n-mo grado
- uso di matrici e calcolo della priorità
- calcolo derivate e integrali
- risoluzione equazioni differenziali
In generale un S i s t e m a è una entità che riceve sollecitazioni e fornisce, come reazione ad esse, delle nuove condizioni.
In termini più formali si dicono i n g r e s s i le sollecitazioni e u s c i t e le reazioni agli ingressi.
Gli elementi tipici per l' individuazione di un sistema sono:
- T un insieme ordinato e crescente di tempi;
-I
insieme di n canali di ingresso;
- VI insieme dei valori per gli ingressi;
- U insieme di m uscite;
- VU insieme dei valori per le uscite;
- S insieme degli stati interni.
Servono poi due funzioni che possano descrivere l' andamento del sistema e, da questo, fornire l'andamento delle uscite:
s(t) = f(t0,t,s(t0),in(t)[t0,...,t[)
funzione di T r a n s i z i o n e :
La prima è detta
funzione di T r a s f o r m a z i o n e :
La seconda è detta
u (t) = g (t,s(t),in(t))
m
m


La situazione può essere rappresentata da un diagramma funzionale:
i (t)
s(t)
1
i 2(t)
u1 (t)
f( )
m
g()
in(t)
u2 (t)
um(t)
s(t0)
L'insieme T è ordinato e rappresenta tutti i possibili istanti in cui deve essere analizzato il sistema. Esso può essere finito o infinito ma sicuramente
è limitato inferiormente da un valore detto t0 (tempo iniziale).
L'insieme I descrive tutte le possibili sollecitazioni autonome a cui il sistema è soggetto; in altre parole potrebbe essere visto come l'insieme delle
variabili d'ingresso. E' opportuno, per ogni elemento di I (variabile) specificarne il campo di validità o l'insieme dei valori.
L'insieme VI è composto da tutti i valori che possono assumere contemporaneamente gli elementi di I, quindi se la cardinalità di I è n, l'insieme VI è
rappresentato - in teoria - da tutte le possibili n-ple che si possono comporre.
Analoghi discorsi si applicano a U e VU, mentre per S si intende l'insieme di tutte le configurazioni interne che il sistema può assumere durante
l'analisi.
Risulta particolarmente intuitivo che gli ingressi, gli stati e le uscite debbano dipendere dal tempo t, cioè: i=i(t), s=s(t), u=u(t).
In particolare risulta intuitivo che saputi gli ingressi al tempo iniziale t0, saputo lo stato iniziale al tempo t0 e sapute le relazioni tra ingressi e stati per
il calcolo delle uscite, si possa conoscere senza dubbio sia l'uscita successiva (al tempo t1) che lo stato successivo (al tempo t1); inoltre applicando
tali deduzioni, si può estendere il principio ad un qualsiasi tempo t diverso da t0.
E' per questo motivo che le due relazioni funzionali f e g assumono la forma vista poco sopra.
S3Abacus – Architetture/Asm
.doc - 1684Kb - 12/set/2008 - 4 / 82
E' da notare come t0 non sia espressamente solo il tempo iniziale dell'analisi, ma un qualsiasi tempo t per il quale siano noti i(t) e s(t).
Anche la notazione in(t), um(t) e gm(..) sono da intendersi come n-ple di valori di ingresso, m-ple di valori di uscita e m-ple di funzioni di
trasformazione, avendo generalizzato la numerosità di ingressi e uscite. Infatti se da n ingressi diversi il sistema offre m uscite diverse, la funzione
di traformazione in realtà deve descrivere tutti gli m modi in cui gli n ingressi si trasformano nelle m uscite. Pertanto questi m modi sono le gm
funzioni di trasformazione diverse.
Le due funzioni fondamentali  e  sono da interpretare in modo consequenziale l’una dall’altra e non contemporaneo, anche se la stessa
dipendenza da t di s(t) e di um(t) potrebbe far pensare diversamente.
Infatti la loro valutazione contemporanea induce ad uno sfasamento nell’analisi: la valutazione delle uscite attuali (al tempo t) dipende strettamente
dallo stato attuale (al tempo t) che pertanto deve essere già conosciuto. Anche il diagramma funzionale riportato contiene questa inesattezza, infatti
le n entrate dell’ingresso in f() non sono le stesse che entrano poi in gm(), ma dovrebbero essere gli ingressi al tempo t precedente.
La corretta relazione tra le due funzioni si ottiene integrandole matematicamente: um(t) = gm(t, f(t0,t,s(t0),in(t)[t0,...,t[),in(t))
Solo in base a questa considerazione l’interpretazione delle due funzioni diviene coerente:
- lo stato che desidero conoscere, cioè lo stato al tempo attuale t e quindi s(t) è calcolabile con la funzione f solo se è possibile conoscere lo stato
iniziale s(t0) e l’intera evoluzione degli stati per tutti i tempi successivi fino a t escluso; si tratterebbe quindi di determinare tutti i vari s(t1), s(t2), ...; ma
ciò è possibile solo se per ogni istante t0, t1, ..., conosco gli ingressi applicati, cioè tutti gli ingressi applicati da t0 fino a t escluso.
Pertanto la scrittura in(t)[0..t[ significa: tutti i valori degli n ingressi valutati a partire da t0 fino a t escluso.
Allora la formula  descrive esattamente questo appena affermato.
- l’uscita al tempo attuale t non è altro che una m-pla di valori, e quindi la si scrive: um(t); per poter calcolare ogni singolo valore della m-pla il
ragionamento è sempre identico: è necessario sapere quale è lo stato al tempo t, cioè s(t) e quale è l’ingresso al tempo t, cioè l’n-pla specifica: in(t).
Ma questo è ciò che rappresenta formalmente la formula .
In realtà la  potrebbe essere riscritta: sarebbe infatti sufficiente conoscere lo stato immediatamente precedente e l’ingresso (n-pla)
immediatamente precedente per conoscere lo stato attuale s(t); in altre parole la funzione di transizione potrebbe anche esprimersi:
s(t) = f(t0,t,s(tprec),in(tprecl))
’
Cionondimeno si preferisce la  per evidenti scopi didattici, dato che costringe a ripensare alla storia passata del sistema.
Le presenze di t e sono esplicitate nelle due funzioni per mettere in evidenza la dipendenza dalla variabile indipendente t connaturata alla
dinamicità dei sistemi e la dipendenza assoluta dall’istante iniziale di analisi - e quindi dallo stato iniziale del sistema - che così fortemente
condiziona l’evoluzione di ogni sistema.
CLASSIFICAZIONE
I sistemi in generale possono essere suddivisi in categorie in base alla loro natura; si possono quindi distinguere tipi contrapposti di sistemi:
- Varianti:
sono tali quei sistemi per cui, al trascorrere del tempo, uno o più parti costitutive del sistema si evolvono e quindi ne
modificano la struttura d'analisi. Se ad esempio in un sistema 'missile' la massa del missile viene considerata costante
durante l'analisi per esempio del calcolo della velocità e delle accelerazioni, che possono essere viste come funzioni di
trasformazione o transizione, tale assunzione risulta evidentemente falsa dato che la massa del missile diminuisce al
consumo di carburante.
In altre parole un parametro costitutivo del sistema dipende esplicitamente dal tempo e varia con esso.
- Invarianti:
in contrapposizione, se le caratteristiche intrinseche del sistema rimangono costanti nel tempo e variano solo ingressi, stati
e uscite.
- Deterministici:
sono tali quei sistemi in cui le grandezze d'analisi sono precisamente e univocamente determinate da relazioni
matematiche, grafiche o descrittive.
- Stocastici:
in contrapposizione, quei sistemi per cui le grandezze caratteristiche dipendono da dati e analisi aleatorie e non
precisamente determinabili a priori (si pensi ai sistemi complessi delle economie, delle previsioni meteorologiche o dei
modelli atomici).
- Continui:
sono tali quei sistemi per cui le caratteristiche fondamentali dipendono da quantità analogiche continue riferibili a numeri
Reali o Complessi.
- Discreti:
in contrapposizione, quei sistemi governati da quantità finite ed enumerabili - in genere riferibili al dominio dei numeri
Naturali o Interi. Sono detti spesso anche sistemi digitali.
- Propri:
se la funzione di trasformazione non dipende dai valori assunti dall' ingresso mentre si dicono I m p r o p r i se la funzione
di trasformazione dipende esplicitamente dai valori degli ingressi, così come espresso nella definizione generale .
Per i sistemi propri quindi le due funzioni possono essere riscritte:
s(t) = f(t0,t,s(t0),in(t)[t0,...,t[)
'
um(t) = gm(t,s(t))
'
.doc - 1684Kb - 12/set/2008 - 5 / 82
S3Abacus – Architetture/Asm
AUTOMI A STATI FINITI
Un sottoinsieme dei sistemi generali è rappresentato dagli A u t o m i , che possono essere definiti come Sistemi invarianti, deterministici, discreti in
cui VI e VU sono insiemi finiti.
Tra questi sono particolarmente significativi gli A u t o m i A S t a t i F i n i t i (AASF), cioè automi in cui anche l'insieme S è finito.
Di questi ultimi ci occuperemo in particolar modo mostrando metodi di analisi e metodi per l'individuazione delle funzioni di transizione e
trasformazione.
Dato che per i sistemi discreti - come gli automi - l'avanzamento del tempo è implicito, si possono scrivere le funzioni tipiche omettendo la variabile
indipendente t e riportando le due forme, impropria e propria:
IMPROPRIO
PROPRIO
s(t) = f(s(t0),in(t)[t0,...,t[)

s(t) = f(s(t0),in(t)[t0,...,t[)
'
um(t) = gm(s(t),in(t))

um(t) = gm(s(t))
'
Per gli automi a stati finiti, la versione Impropria viene detta Automa di MEALY, mentre la versione Propria è detta Automa di MOORE.
L'analisi e la determinazione delle due funzioni g e f per un automa a stati finiti si realizza con la costruzione di un grafo particolare detto g r a f o
d e l l e t r a n s i z i o n i , sempre identificabile.
Lo schema costruttivo del grafo di transizione per un AASF ove gli insiemi tipici sono univocamente determinati si sintetizza nei seguenti passi:
1. Disposizione in aree circolari di tutti gli stati appartenenti a S;
2. A partire da uno stato qualsiasi - magari s0 - applicare tutti gli ingressi possibili (gli elementi di VI) e per ognuno disegnare un arco orientato
verso lo stato raggiunto, riportando l'ennupla d'ingresso e l'emmupla dell'uscita relativa con la notazione i1,i2,..in/u1,u2,..,um.
3. Naturalmente una regola fondamentale, che al termine della costruzione deve essere assolutamente rispettata, consiste nella completa
mancanza di due coppie identiche di n-ple d'ingresso che, da uno stesso stato, conducono a due stati differenti ovvero che originano due
dfferenti m-ple di uscite.
Un altro sistema per descrivere il funzionamento di un AASF (determinazione di f e g) è ben descritto dalla t a b e l l a d i f u n z i o n a m e n t o ,
che consiste in una tabella cartesiana ove sulle ascisse sono riportati tutti i possibili ingressi (elementi di VI), sulle ordinate tutti i possibili stati
(elementi di S) e sugli incroci, ordinatamente, uscita e stato successivo determinati dai valori d'incrocio.
Tale metodo risulta particolarmente pesante quando VI è ampio ma sufficientemente efficace per automi in cui VI è limitato.
Sia la tabella di funzionamento che il grafo di transizione riassumono esplicitamente per gli AASF le funzioni f e g tipiche per la completa
determinazione di un sistema.
Esse in alcuni casi possono anche essere scritte in notazioni matematiche, anche se ciò non risulta così efficace come i metodi suddetti.
Proseguendo nell'analisi degli AASF è particolarmente interessante introdurre alcune nozioni relative a stati e uscite, nozioni che in una qualche
misura si possono generalizzare anche per i sistemi continui.
REVERSIBILITÀ
Un automa si dice Reversibile se, dato un qualsiasi stato sa Î S e un qualsiasi valore i Î VI, esiste e unico uno stato sb Î S tale per cui è vera sa = f(sb,
i)
Per verificare questa proprietà su un automa dato è sufficiente verificare che invertendo tutti i versi degli archi del grafo di transizione si ottenga
ancora un automa (cioè non si infranga la regola 3.)
RAGGIUNGIBILITÀ
Uno stato s2 è Raggiungibile dallo stato s1 se esiste un a sequenza di ingressi che applicati a s1 consentono di arrivare a s2.
Se la proprietà è estensibile a qualsiasi coppia di stati di un automa esso si dice Connesso, altrimenti Non Connesso.
INDISTINGUIBILITÀ
Due stati s1 e s2 sono Indistinguibili se tutte le coppie ingresso/uscita applicabili a s1 sono le stesse rilevabili su s2.
STATI D'EQUILIBRIO
Uno stato s è d'Equilibrio per l'automa se esiste una sequenza di ingressi tali da mantenere l'automa sempre nello stato s.
USCITE D'EQUILIBRIO
Una uscita u di un automa è d'Equilibrio se esiste uno stato s e una sequenza di ingressi tale per cui l'uscita dell'automa risulti essere sempre u.
Naturalmente le definizioni di cui sopra implicano situazioni di immediata intuizione che per ora non espliciteremo, attendendo che possano essere
messe in evidenza dagli esempi riportati.
La proprietà dell'Indistinguibilità invece torna immediatamente utile per introdurre un metodo che semplifichi la costruzione di un grafo delle
transizioni per un automa qualsiasi.
Come ci si renderà ben presto conto, la costruzione dei grafi di transizione, pur semplice, spesso conduce a diagrammi molto ricchi di stati e archi,
ingenerando confusione e dilungando sensibilmente l'analisi.
Dopo aver costruito un grafo complesso e ricco di stati si rende necessario pensare se il modello ottenuto sia il più semplice possibile e spesso la
risposta è negativa. Esiste infatti un metodo di analisi dei grafi che consente di semplificarli senza perderne le caratteristiche. Tale metodo è detto di
m i n i m i z z a z i o n e e si basa sulla eliminazione metodica degli stati equivalenti (ovvero indistinguibili) eventualmente presenti in un modello.
.doc - 1684Kb - 12/set/2008 - 6 / 82
S3Abacus – Architetture/Asm
MINIMIZZAZIONE
Dopo aver disposto completamente il grafo di transizione iniziale e quindi aver esplicitamente descritto tutti gli stati (es. s0,..,sn) e applicato tutti i VI
ad ogni stato avendole associate alle proprie VU, seguire i seguenti passi:
1. Indicare con P0 l'insieme di tutti gli stati (prima partizione P1);
2. Individuare tutti i gruppi di stati nei quali applicando i VI si ottengono le stesse VU; per ipotesi immaginiamo che si formino i gruppi di stati
nominati G1, G2, ..., Gi, ognuno dei quali contiene stati che a partire dai medesimi ingressi offrono medesime uscite (stati indistinguibili).
3. Questi i gruppi di stati rappresentano la seconda partizione P2;
4. Per ogni stato individuare tutti i possibili stati di arrivo (applicando gli ingressi ad ogni stato) e formare una tabella che ad ogni stato associ
l'identificazione del gruppo di P2 - cioè ai Gj - a cui si è arrivati.
5. Identificare quindi i nuovi gruppi della partizione P3 raggruppando tutti gli stati che giungono alle stesse sequenze di Gj descritte al punto
precedente. Questa sarà la nuova partizione P3.
6. Ripetere i passi 3. e 4. fino a ottenere partizioni identiche notando l'impossibilità di proseguire.
7. A questo punto supponiamo che gli elementi dell'ultima partizione siano k, con k £ i £ n e chiamiamoli Q1,Q2,..,Qk (analoghi ai G1,G2,...,Gi visti
in precedenza); essi determinano k stati generali dei quali, per ogni ingresso, si può determinare sia l'uscita (osservando la partizione P1) sia lo
stato generale a cui passare (osservando l'ultima partizione ottenuta).
Questo nuovo automa è perfettamente equivalente a quello dato.
ESEMPI
DI
AUTOMI
 Esempio 1.
Analizziamo in questo caso solo sistemi discreti, cioè automi a stati finiti cominciando da un sistema
costituito da un distributore di lattine.
Il sistema contiene n lattine, possiede un ingresso per inserire una moneta, una uscita per la lattina e una
per la restituzione della moneta.
Gli insiemi tipici risultano essere:
T
I
VI
U
VU
S
=
=
=
=
=
=
{t0,t1,...,tn}
{M}; dove M stà per moneta e può assumere i valori 1 e 0 (moneta IN, moneta OFF)
{1,0}; naturalmente sono singoletti, dato che il canale è unico;
{L,R}; con L si indica l'uscita lattina (0 o 1) e con R la restituzione (0 o 1);
{<1,0>,<0,0>,<0,1>}; manca il caso in cui è resa sia la lattina che la moneta;
{s0,...,sn}; indica il riempimento del sistema (s0=vuoto)
Allora il grafo si costruisce:
1 / <1,0>
1 / <1,0>
S0
1 / <1,0>
Sn-1
1 / <0,1>
0 / <0,0>
0 / <0,0>
Sn
0 / <0,0>
Come si può notare sono stati riportati tutti gli stati
(agli stati intermedi sono stati sostituiti i quadratini di
sospensione) e ad ogni stato sono applicati tutti gli
ingressi possibili;
Le funzioni f e g si possono anche esplicitare in:
f = ; si=si-1 se ingresso=1 e i≠0
si=si
se ingresso=1 o se i=0;
g =
; ui=<1,0> se ingresso=1 e i≠0
ui=<0,0> se ingresso=0
ui=<0,1> se ingresso=1 e i=0
 Esempio 2.
Sia dato un ascensore che si muove su 3 piani, con un pulsante per piano per la chiamata e un pulsante
interno per indicare la destinazione.
Naturalmente è necessario stabilire alcune regole per determinare il comportamento del sistema tipo indicare
la priorità dei pulsanti (detti PP e PA) e introdurre altre ipotesi di funzionamento (le uscite possono
essere la direzione D (alto o basso o fermo) e la velocità V (0,1,2).
Tali ipotesi devono essere poste dall'analizzatore del sistema se non espressamente indicate.
Pertanto:
T
I
VI
U
VU
S
=
=
=
=
=
=
{t0,t1,...,tn}
{PP,PA}; PP=0,1,2,3; PA=0,1,2,3
{<0,0>,<0,1>,<0,2>,<0,3>,<1,0>,...,<3,3>}
{D,V}; D=0,a,b; V=0,1,2
{<0,0>,<a,1>,<a,2>,<b,1>,<b,2>}
{P1,P2,P3}
0,2/a,1
1,2/a,1
2,0/a,1
2,2/a,1
3,2/a,1
0,0/0,0
0,1/0,0
1,0/0,0
1,1/0,0
2,1/0,0
3,1/0,0
Nel grafo sono state messe in evidenza ed esplicitate solo le
transizioni del primo stato P1, riportando tutti i possibili
valori per gli ingressi (coppie), mostrandone le uscite e
riportandone le transizioni.
P2
In vari casi si nota come la priorità scelta tra i due
ingressi sia più alta per il pulsante nell'ascensore (PA) cioè
il secondo valore della coppia (Es. la coppia <3,1> su P1
ferma l'ascensore su P1 dando l'uscita <0,0>).
P1
0,3/a,2
1,3/a,2
2,3/a,2
3,0/a,2
3,3/a,2
P3
.doc - 1684Kb - 12/set/2008 - 7 / 82
S3Abacus – Architetture/Asm
Si nota anche che nessun ingresso applicato su P1 può generare l'uscita <b,...> dato che l'ascensore si trova
al piano più basso.
Naturalmente analoghe analisi con l'applicazione di tutte le coppie di ingresso devono essere eseguite anche
per P2 e P3.
In questo caso non è particolarmente efficace rappresentare f e g con notazioni matematiche.
 Esempio 3.
Una categoria abbastanza significativa di automi è rappresentata dagli automi riconoscitori.
Si debba implementare un automa che acquisisce in ingresso simboli binari e debba emettere un segnale tutte
le volte che riconosce una sequenza di ingresso pari alla codifica binaria del simbolo '1' in ASCII
(=30h=00110000b).
Gli insiemi tipici:
T = {t0,t1,...,tn}
I = {IB}; che indica Input Binario, con valori 0,1;
VI = {1,0};
U = {R}; Riconoscimento, valevole ACK (riconosciuto), NACK o STBY (in riconoscimento)
VU = {ACK,STBY,NACK};
S = {S0,R1,R2,R3,R4,R5,R6,R7}; indica le fasi di riconoscimento.
1/NACK
0/STBY
0/STBY
1/NACK
0/ACK
R1
S0
R2
1/STBY
0/NACK
1/NACK
0/NACK
R7
0/STBY
1/NACK
1/NACK
1/NACK
R5
R6
1/STBY
Si noti come l'uscita segnali una fase di riconoscimento in corso con
il valore STBY.
R4
0/STBY
1/NACK
R3
La corretta implementazione dell'automa impone alcune correzioni sulle
transizioni che riportiamo in seguito.
0/STBY
0/STBY
0/STBY
0/NACK
1/NACK
R1
S0
0/ACK
1/NACK
R2
0/NACK
R7
0/STBY
1/STBY
R3
1/STBY
1/NACK
1/NACK
1/NACK
R5
R6
0/STBY
R4
0/STBY
Il grafo riportato è in grado di riconoscere la sequenza indicata
(00110000) solo in alcuni casi; infatti, ipotizzando lo stato iniziale
in S0, una sequenza di ingressi del tipo:
11100110000101...
viene correttamente analizzata dall'automa che segnala ACK nel momento
giusto.
Sempre con stato iniziale in S0, una sequenza del tipo:
10001100001011...
non viene segnalata, dato che al terzo zero ricevuto l'automa si
ritrova in S0, perdendo sincronismo con la sequenza da riconoscere.
Questa seconda versione individua tutte le sequenze richieste senza
sbagliare nessun riconoscimento e senza perderne alcuno.
Le modifiche consistono nel far ritornare l'automa allo stato corretto
dopo ogni NACK, evitando di farlo sempre partire da capo.
Si notino le differenze di comportamento sugli stati R2, R3, R7.
.doc - 1684Kb - 12/set/2008 - 8 / 82
S3Abacus – Architetture/Asm
 Esempio 4.
Vediamo ora come si può minimizzare un automa con il metodo delle partizioni.
Sia dato il seguente automa per mezzo del grafo:
0/0
S6
0/0
0/0
1/0
S7
S0
0/0
0/0
1/0
1/0
S3
1/0
1/1
1/1
1/0
S5
S4
1/0
S1
0/0
0/0
S2
0/0
Evidentemente gli stati sono otto (S0,...,S7) con un solo canale di ingresso che può valere 0 o 1 e
analogamente, un solo canale per le uscite che può valere 0 o 1.
A priori non si può affermare che l'automa riprodotto non sia riducibile (minimizzabile), ma basta osservare
gli stati S0 e S4 per verificare che sono Indistinguibili.
Infatti ogni valore dell'ingresso su S0 genera le stesse uscite che gli stessi valori per l'ingresso generano
su S4 (0/0; 1/0).
L'automa è minimizzabile. Utilizziamo il metodo delle partizioni.
Passo 1.
P0 = (S0, S1, S2, S3, S4, S5, S6, S7);
Passo 2.
Per determinare i gruppi Gj della seconda partizione P1, applichiamo ad ogni membro di P0 gli ingressi e
valutiamo le uscite:
S0= 0-0 1-0
S1= 0-0 1-1
S2= 0-0 1-0
S3= 0-0 1-0
S4= 0-0 1-0
S5= 0-0 1-1
S6= 0-0 1-0
S7= 0-0 1-0
Individuiamo quindi i due gruppi G1=(S0,S2,S3,S4,S6,S7) e G2=(S1, S5) che compongono la partizione P1.
Passo 3.
Al gruppo G1 di P1 e al gruppo G2 di P1 applichiamo gli ingressi e costruiamo la tabella delle transizioni
sui gruppi Gj:
Gruppo G1
Gruppo G2
S0, 0-1= G1-G2
S1, 0-1= G1-G1
S2, 0-1= G1-G2
S5, 0-1= G1-G1
S3, 0-1= G1-G1
S4, 0-1= G1-G2
S6, 0-1= G1-G2
S7, 0-1= G1-G1
Passo 4.
Individuiamo quindi i sottogruppi Q1=(S0,S2,S4,S6), Q2=(S3,S7) e Q3=(S1,S5) che compongono la P2.
Passo 5.
Come al passo 3., applichiamo ad ogni sottogruppo gli ingressi:
Gruppo Q1
Gruppo Q2
Gruppo Q3
S0, 0-1= Q1-Q3
S3, 0-1= Q2-Q1
S1, 0-1= Q1-Q2
S2, 0-1= Q1-Q3
S7, 0-1= Q2-Q1
S5, 0-1= Q1-Q2
S4, 0-1= Q1-Q3
S6, 0-1= Q1-Q3
Notiamo che non si ottengono altri sottogruppi, quindi il metodo di partizione si interrompe.
Passo 6.
Si individuano quindi tre stati differenti equivalenti a Q1, Q2,
transizioni e le uscite per ogni valore di ingresso, infatti:
Q1 e I=0: U=0 (da uno stato qualsiasi di Q1) e transizione in Q1
Q1 e I=1: U=0 (da uno stato qualsiasi di Q1) e transizione in Q3
Q2 e I=0: U=0 (da uno stato qualsiasi di Q2) e transizione in Q2
Q2 e I=1: U=0 (da uno stato qualsiasi di Q2) e transizione in Q1
Q3 e I=0: U=0 (da uno stato qualsiasi di Q3) e transizione in Q1
Q3 e I=1: U=1 (da uno stato qualsiasi di Q3) e transizione in Q2
0/0
Q3 di cui si conoscono le
(vedi
(vedi
(vedi
(vedi
(vedi
(vedi
P2);
P2)
P2)
P2)
P2)
P2);
Q1
0/0
1/0
Q2
0/0
1/1
Q3
1/0
.doc - 1684Kb - 12/set/2008 - 9 / 82
S3Abacus – Architetture/Asm
 Esempio 5.
E' dato un file di testo, formattato tipograficamente, giustificato con spazi a sinistra, numero di pagina,
intestazione, e caratteri di controllo.
Si deve ottenere un file di testo ASCII, non formattato, con una sola riga al posto dei salti pagina, senza
spazi a sinistra, eliminando tutti gli eventuali spazi di giustificazione tra le parole.
Si ricorda che un salto riga è descritto dai due caratteri ascii sequenziali CR/LF (13,10); indichiamo con ^
i caratteri di controllo.
T
I
VI
U
VU
S
=
=
=
=
=
=
{...}
{file originale}
{0..255} ≡ {[A..Z], [10], [13], [32], [^]} dove [A..Z] indicano gli ASCII [32..125]
{file da ottenere}
{0, 255, Null}
...
A..Z/A..Z
32/Null
32/32
OK
A..Z/A..Z
A..Z/A..Z
32/Null
10/Null
^/Null
^/Null
LF/Null
10/Null
A..Z/Null
SP/Null
13/Null
^/Null
LF
13/Null
10/Nullll
SP
A..Z/A..Z
^
^/Null
Al trascorrere del tempo, e al variare degli ingressi,
ogni sistema significativo modifica il suo stato (o i
suoi stati). La configurazione che assumono gli stati
è di fondamentale importanza per lo studio del
sistema. Tale configurazione è detta, genericamente,
movimento.
13/Null
13/Null
^/Null
10/Null
CR
Riga
10/Null
32/Null
In Turbo Pascal questo si realizza con un ARRAY di
BOOLEAN, un elemento dell'array per ogni stato.
Per indicare lo stato attuale si pone a TRUE l'
elemento (FALSE in caso contrario); si avranno quindi
6 istruzioni IF per ogni stato che selezioneranno le
valutazioni opportune degli ingressi e determineranno
uscite e transizioni.
Aggiungere uno stato è molto facile: basta aggiungere
un blocco di 6 IF.
10/Null
13/Null
32/Null
Come al solito il diagramma a fianco, rappresenta per
gli automi a stati finiti l'equivalente della funzione
di Trasformazione e, se riportante anche le uscite (come in questo caso) esso sintetizza anche la funzione di
Trasferimento.
CR/CR
Simulazione per l' automa
Tempi
t0
t1
t2
t3
t4
t5
t6
...
(filtro-file):
Ingressi
a
n
32
32
13
10
...
...
Stati
OK
OK
OK
SP
SP
CR
LF
...
La colonna degli stati rappresenta quello che, per
Uscite
a
n
32
Null
Null
Null
...
...
sistemi continui, chiameremo Movimento.
.doc - 1684Kb - 12/set/2008 - 10 / 82
S3Abacus – Architetture/Asm
RAPPRESENTAZIONE E CODIFICA DELL’INFORMAZIONE
NOTAZIONI E RAPPRESENTAZIONI POSIZIONALI
Un sistema di numerazione è l’insieme delle convenzioni che si debbono seguire per descrivere ed operare sul concetto primitivo di numero. Il
concetto originale è unico ed è stato assiomatizzato da tempo, ma il modo in cui esprimerlo, che potremmo associare ad una lingua non è unico
(come uniche non sono le lingue con le quali si può operare sulle cose).
Il sistema di numerazione adottato universalmente è il sistema decimale, ovvero in base 10, introdotto verso il 1200 dalla cultura araba; tale sistema
è detto p o s i z i o n a l e .
Il sistema in uso precedentemente nella nostra cultura era quello romano di tipo ‘additivo’ ed era basato sull'insieme dei simboli: I,V,X,L,C,D,M (es.
MCMXCII=1992).
Questo sistema, oltre ad essere scarsamente leggibile poichè ogni cifra per essere decodificata costringe sempre ad un calcolo, non permette di
utilizzare i sistemi di calcolo veloci per le operazioni sulle cifre, ad esempio quando si volessero applicare le quattro operazioni aritmetiche.
I sistemi posizionali al contrario risultano più compressi e veloci oltrechè intuitivi nella decodifica - si pensi solo al fatto che una cifra con un numero
maggiore di simboli di un’altra indica direttamente il fatto di esserne maggiore. Essi sono inoltre adatti per effettuare calcoli aritmetici veloci.
Le caratteristiche di un qualsiasi sistema di numerazione posizionale sono:
- Base, e cioè il numero di simboli utilizzabili per rappresentare una cifra qualsiasi;
- Set di simboli, cioè l’insieme dei simboli utilizzabili;
- Peso del simbolo, cioè la posizione che ogni simbolo assume all’interno di una qualsiasi cifra adottando una numerazione che parte da zero per il
simbolo più a destra nella rappresentazione.
Esistono anche sistemi di numerazione misti, cioè che utilizzano il sistema decimale all’interno di classi di numeri suddivise in base ad una seconda
base detta secondaria, come il sistema sessaggesimale per la numerazione degli angoli.
Spesso risulta più comodo e veloce esprimere i numeri in una base diversa da 10, pertanto è necessario possedere una certa pratica nel
manipolare direttamente i numeri in diverse basi. Dato però che il sistema di riferimento è quello a base 10 sarà sempre utile sapere anche
trasformare numeri da una base diversa da dieci alla base di riferimento.
In sostanza vedremo come operare direttamente su sistemi di numerazione in diverse basi e, sempre, sapere trasformare ogni risultato parziale in
base 10 e viceversa.
Risulta fin da ora intuitivo che, essendo la logica elettronica ed informatica basata su due soli stati spesso rappresentabili in ultima istanza con due
livelli di tensione, un sistema di numerazione particolarmente utile sarà il sistema in base 2 o binario.
Per analoghe considerazioni si vedrà come utili saranno anche quei sistemi che utilizzano basi direttamente connesse alla base 2 e cioè sistemi di
numerazione la cui base è una potenza di due (sistemi di numerazione in base 4, 8 e 16).
Indichiamo con il termine n u m e r o il concetto generale ed intuitivo di numero, a prescindere dalla sua rappresentazione;
Indichiamo con c i f r a la rappresentazione in simboli di un numero in un determinato sistema posizionale;
Si ricorda che per s i m b o l o si intende uno qualsiasi dei simboli appartenenti al set di simboli di un determinato sistema posizionale.
Si può dire quindi che un determinato numero è rappresentato dalla cifra 101 nel sistema di numerazione posizionale in base dieci utilizzando i
simboli 1 e 0 della base decimale.
Ogni cifra quindi dovrà essere dotata di una specificazione che indichi il sistema di numerazione in cui è espressa, dato che purtroppo gli insiemi di
simboli utilizzati dalle diverse basi di numerazione spesso coincidono.
Dato che si analizzeranno più frequentremente i sistemi in base dieci, in base due, in base otto, e in base sedici, utilizzeremo rispettivamente le
lettere D, B, O, e H (Hex) per indicare i sistemi di numerazione; pertanto la cifra di poco fa si scriverà:
(101)D , mentre le cifre (101)B , (101)O , (101)H rappresenteranno altri tre diversi numeri nelle basi due, otto e sedici.
La rappresentazione di numeri in un sistema posizionale di base generica N composta da n simboli è sempre traducibile con uno stesso algoritmo;
infatti data una cifra qualsiasi composta da una sequenza di k simboli S, e assegnati i pesi ad ogni simbolo si ottiene sempre:
( Sk... S2S1S0 ) N
ESEMPIO: RAPPRESENTAZIONE
=
k
∑
i= 0
i
Si ( n) (N ) N

DECIMALE
 Data la cifra 143 in base 10, verificare la formula .
Per la cifra data gli ingredienti di  sono:
k=2;
N=D
n=10; quindi:
(0)
(1)
(2)
(143)D = 310
( )D D + 410
( )D D + 110
( )D D = (3 * 1 + 4 * 10 + 1 * 100)D = (143)D
 Data la cifra 123 in base 4, verificare la formula .
Per la cifra data gli ingredienti di  sono:
k=2;
N=Q
n=4; quindi:
(0)Q
(123)Q = 3(10)Q
(1)Q
+ 2(10)Q
(2)Q
+ 1(10)Q
= (3 * 1 + 2 * 10 + 1 * 100)Q = (123)Q
Naturalmente la formula  è generalizzabile per rappresentazioni di cifre con parte frazionaria, avendo cura di pesare i simboli a partire da -1 per il
simbolo immediatamente posto dopo la virgola.
.doc - 1684Kb - 12/set/2008 - 11 / 82
S3Abacus – Architetture/Asm
ESEMPIO: RAPPRESENTAZIONE
DECIMALE DI CIFRA CON VIRGOLA
 Data la cifra 234,31 in base 10, verificare la .
(34312
, )D = 210
( )(D− 3)D + 110
( )(D− 2)D + 310
( )(D− 1)D + 410
( )(D0)D + 310
( )(D1)D = (2 * 10− 3 + 1 * 10− 2 + 3 * 10− 1 + 4 * 100 + 3 * 101)D =
= (2 * 0,001 + 1 * 0,01 + 3 * 0,1 + 4 * 1 + 3 * 10)D = (34312
, )D
Il sistema di numerazione posizionale a noi noto è il sistema a base dieci. Ciò significa che il set di simboli utilizzati per la rappresentazione dei
numeri è composto da dieci simboli: 0,1,2,3,4,5,6,7,8,9.
I numeri sono quindi rappresentati da cifre che, a partire dal primo numero, lo zero, associano in sequenza i simboli formando dieci
rappresentazioni da un simbolo.
Per i numeri successivi la rappresentazione si comporrà di due simboli, il primo dei quali sarà il secondo simbolo (1) e il secondo simbolo sarà
progressivamente la successione dei dieci simboli nello stesso ordine in cui si erano disposti per la rappresentazione a un solo simbolo.
Questo meccanismo quindi si ripete progressivamente e si otterranno via via rappresentazioni con tre, quattro, cinque, ..., simboli in sequenza.
Tale regola, che per tutti è implicita, consente anche di effettuare i calcoli aritmetici semplici - addizione, sottrazione, moltiplicazione e divisione utilizzando sostanzialmente il solo accorgimento del riporto e del prestito, dato che moltiplicazioni e divisioni sono riconducibili rispettivamente a
addizioni e sottrazioni ripetute.
Le stessa regole di formazione delle cifre e di calcolo tra le cifre si applicano pari pari in ogni altra rappresentazione di numeri in altre
basi.
SISTEMA DI NUMERAZIONE IN BASE DUE (BIN)
In questo caso i simboli utilizzati per descrivere i numeri e costruirne le rappresentazioni (cifre) sono due: 0 e 1.
Per quanto detto precedentemente, la costruzione delle rappresentazioni dei numeri quindi sarà la seguente:
0,1,10,11,100,101,110,111,1000,1001, ..., tutte rappresentazioni in base 2 e quindi più precisamente:
(0)B , (1)B , (10)B , (11)B , (100)B , (101)B , (110)B , (111)B , (1000)B , (1001)B , ...
Naturalmente eseguire i calcoli i base due può non essere immediatamente intuitivo;il fatto che 1+1 faccia 10 non è proprio naturale anche se è
proprio così dato che 10 in binario rappresenta il numero due, come si può verificare dalla sequenza sopra riportata.
Il gioco dei riporti e dei prestiti si riduce quindi ad una conoscenza pratica dell’aritmetica binaria in cui, ad esempio:
0-1 = -1
OPPURE
1 con prestito 1 dalla cifra a sinistra;
1+1 = 10
OPPURE
0 con riporto 1 sulla cifra a sinistra;
1+1+1+1+1 = 101
OPPURE
1 con riporto 10 sulle cifre a sinistra.
La somma e la sottrazione quindi si riducono in una opportuna gestione dei riporti e dei prestiti.
La moltiplicazione risulta essere molto semplice dato che le singole moltiplicazioni tra cifre binarie sono banali:
0*0=0; 0*1=0; 1*1=1.
Anche la divisione risulta semplice dato che il divisore se inferiore al dividendo sta una volta (Q=1) o se superiore sta zero volte (Q=0) e quindi il
ricalcolo del resto dopo la moltiplicazione per 1 o per 0 diventa immediato: basta eseguire una sottrazione binaria.
ESEMPIO: CALCOLI BINARI
 Si calcoli (101001)B + (11110)B.
r
1 0 1 0 0 1 +
( 41 ) +
1 1 1 1 0 =
( 30 ) =
--------------------1 0 0 0 1 1 1
( 71 )
 Si calcoli (101011)B * (111001)B.
1 0 1 0 1 1 *
1 1 1 0 0 1 =
----------1 0 1 0 1 1
0 0 0 0 0 0 0 0 0 0 0 0 - 1 0 1 0 1 1 - - 1 0 1 0 1 1 - - - 1 0 1 0 1 1 - - - - ----------------------1 0 0 1 1 0 0 1 0 0 1 1
 Si calcoli (10000)B
p p p p
1 0 0 0 0 1 =
--------1 1 1 1
meno (1)B.
 Si calcoli (110110)B : (1001)B.
_______ - 1 1 0 1 1 0 : 1 0 0 1 = 1 1 0
1 0 0 1
------- 1 0 0 1
1 0 0 1
------- 0 0 0 0
Quindi Q=110 e R=000
 Si calcoli (110011)B : (1000)B.
_______ - 1 1 0 0 1 1 : 1 0 0 0 = 1 1 0 . 0 1 1
1 0 0 0
------- 1 0 0 1
1 0 0 0
------- 0 0 1 1 0 0
1 0 0 0
------- 1 0 0 0
Quindi Q=110.011 e R=000
 Si
_____
1 1 1
1 1 1
----- - -
calcoli (111100)B : (111)B.
- - 1 0 0 : 1 1 1 = 1 0 0 0 . 1 0 0 1
1 0 0 0
1 1 1
------- - - 1 0 0 0
.doc - 1684Kb - 12/set/2008 - 12 / 82
S3Abacus – Architetture/Asm
1 1 1
------- - - 1
1 0 0 0 1 0 0 1
1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 1 - ------------------1 1 1 0 1 1.1 1 1 1 +
0.0 0 0 1 =
------------------1 1 1 1 0 0.0 0 0 0
Quindi Q=1000.1001 e R=0.0001, infatti:
1 0 0 0.1 0 0 1 *
1 1 1 =
---------------
SISTEMA DI NUMERAZIONE IN BASE OTTO (OCT)
In questo caso i simboli utilizzati per descrivere i numeri e costruirne le rappresentazioni (cifre) sono otto: 0,1,2,3,4,5,6,7. Pertanto non potranno
aversi rappresentazioni del tipo (1769)O, dato che il simbolo 9 non fa parte del set del sistema ottale.
Per quanto detto precedentemente, la costruzione delle rappresentazioni dei numeri quindi sarà la seguente:
(0)O , (1)O , (2)O , (3)O , (4)O , (5)O , (6)O , (7)O , (10)O , (11)O , ...
Anche in questo caso bisogna tener presente l’aritmetica ottale che prevede prestiti e riporti opportuni:
6+2 = 10
OPPURE
0 con riporto 1 sulla cifra a sinistra
4+10+7 = 23
OPPURE
3 con riporto di due sulla cifra a sinistra
Spesso si eseguono calcoli in base ottale facendo a mente le trasformazioni in decimale degli operandi, trovando il risultato in decimale e quindi
riconvertendo il risultato in ottale.
Ancora meglio è eseguire i caloli direttamente in ottale facendo uso delle tabelline di +, - e * riportate in ottale in modo da non confondere i riporti.
ESEMPI:
CALCOLI
OTTALI
 Calcolare (5327)O + (321)O.
5 3 2 7 +
3 2 1 =
------5 6 5 0
2 4 5 3
1 0 6 7 --------1 3 3 4 3
 Calcolare (715206)O - (31777)O
7 1 5 2 0 6 3 1 7 7 7 =
----------6 6 3 2 0 7
 Calcolare (275)O * (37)O
2 7 5 *
3 7 =
------
SISTEMA DI NUMERAZIONE IN BASE SEDICI (HEX)
In questo caso i simboli utilizzati per descrivere i numeri e costruirne le rappresentazioni (cifre) sono sedici e il sistema è anche detto Esadecimale:
0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F.
Come di norma, la sequenza numerica di rappresentazione sarà:
(0)H , (1)H , (2)H , (3)H , (4)H , (5)H , (6)H , (7)H , (8)H , (9)H , (A)H , (B)H , (C)H , (D)H , (E)H , (F)H ,(10)H , (11)H , ...
Anche in questo caso potrebbe essere utile disporre di tabelline per il calcolo esadecimale, relativamente alle operazioni aritmetiche.
ESEMPIO: CALCOLI
IN
ESADECIMALE
 Calcolare (A701)H + (FFB)H :
A 7 0 1 +
F F B =
------B 6 F C
1 6 8 5 6
 Calcolare (FAC0)H * (21C)H :
F A C 0 *
2 1 C =
------B C 1 0 0
F A C 0 1 F 5 8 0 - ------------2 1 0 E D 0 0
 Calcolare (1A001)H meno (37AB)H :
1 A 0 0 1 3 7 A B =
---------
TRASFORMAZIONI TRA SISTEMI POSIZIONALI
L’algoritmo che a partire dalla rappresentazione di un numero in una data base N composta da n simboli, fornisce la rappresentazione dello stesso
numero in una diversa base M composta da m simboli si può enunciare a passi:

0. Costruire una tabella biunivoca con le rappresentazioni dei simboli nella base maggiore associate alle rappresentazioni delle cifre della base
minore.
.doc - 1684Kb - 12/set/2008 - 13 / 82
S3Abacus – Architetture/Asm
1. Considerare la cifra data come dividendo, la base di destinazione come divisore, effettuare la divisione (nella base di partenza) con i numeri
rappresentati nella base di partenza.
2. Considerare il quoziente Q e il resto R della divisione; indicare con indice progressivo a partire da zero il resto R e ripetere il passo 1.
utilizzando Q come dividendo.
3. Procedere fino a quando Q coincide con zero.
4. Considerare i resti Ri indiciati. La nuova rappresentazione si ottiene disponendo i resti Ri secondo l’indice che rappresenterà il peso e
traducendo eventualmente ogni resto Ri con la tabella di cui al punto 0.
Quando si avessero cifre decimali con parte frazionaria, si può applicare un ulteriore algoritmo da applicare sulla parte frazionaria, del tutto
autonomo e che consente di ottenere la parte frazionaria nella base desiderata:
1. Considerare la cifra frazionaria nella base di partenza e moltiplicarla per la base di destinazione espressa nella base di partenza.
2. Si ottiene una cifra in cui si nomina la parte frazionaria F e la parte intera I indiciandola a partire da -1.
3. Ripetere i passi 1. e 2. fino a ottenere F=0 o fino a quando richiesto.
4. La cifra frazionaria richiesta nella base di destinazione si ottiene dalle parti intere Ii in cui l’indice i è il peso.
ESEMPIO: TRASFORMAZIONE
DI CIFRE TRA BASI
 Trasformare la cifra 123,625 espressa in base dieci nell’equivalente espressa in base due.
Parte Intera
Passo 0.
La tabella diventa:
DEC BIN
0
0
1
1
2
10
3
11
4
100
5
101
6
110
7
111
8
1000
9
1001
Passo 1. e 2.
123 DIV 2
61 DIV 2
30 DIV 2
15 DIV 2
7 DIV 2
3 DIV 2
1 DIV 2
Q:61
Q:30
Q:15
Q: 7
Q: 3
Q: 1
Q: 0
R0:1
R1:1
R2:0
R3:1
R4:1
R5:1
R6:1
Passo 3.
La rappresentazione equivalente binaria della parte intera: (123)D = (R6R5R4R3R2R1R0)B = (1111011)B
Passo
0,625
0,25
0,5
1. e 2.
* 2 = 1,25
* 2 = 0,50
* 2 = 1,0
Parte Frazionaria
F:0,25
F:0,5
F:0
I-1:1
I-2:0
I-3:1
Passo 3.
La rappresentazione equivalente binaria della parte frazionaria: (0,625)D = (I-1I-2I-3)B = (0,101)B
Totale
In totale allora: (123,625)D = (1111011,101)B
 Trasformare la cifra 101010 espressa in base due nell’equivalente espresso in base dieci.
Parte Intera
Passo 0.
La tabella, come prima, diventa:
DEC BIN
0
0
1
1
2
10
3
11
4
100
5
101
6
110
7
111
8
1000
9
1001
Passo 1.
.doc - 1684Kb - 12/set/2008 - 14 / 82
S3Abacus – Architetture/Asm
Attenzione, la base di destinazione, dieci, in binario vale 1010!
101010 DIV 1010
100
DIV 1010
Q:100
Q:0
R0:10 [=2]
R1:100 [=4]
e, pertanto, (101010)B = (42)D
Come si può facilmente notare quando la base di partenza è diversa da dieci, l’algoritmo  costringe ad usare, per le divisioni, l’aritmetica della
base (diversa da dieci) che non sempre risulta essere ben conosciuta, o almeno non quanto l’aritmetica decimale.
In effetti esiste una via alternativa per passare da una base N (≠10) ad una base M (≠10) che consiste nel passaggio intermedio alla base dieci.
Infatti esiste un algoritmo piuttosto semplice per trasformare una cifra espressa in una base N (≠10) alla cifra equivalente in base dieci:
( Sk... S2S1S0 ) N
=
k
∑
i
(Si)D ( n) D( ) D

i= 0
Allora, dalla base dieci alla base M (≠10) di destinazione si può effettivamente adottare l’algoritmo  che implica l’utilizzazione dell’aritmetica
decimale.
ESEMPI: TRASFORMAZIONI
DA BASI
N≠10
A BASE
DIECI
 Trasformazioni BIN DEC
(11010)B = (x)D
x = 0*20 + 1*21 + 0*22 + 1*23 + 1*24 =
= 0*1 + 1*2 + 0*4 + 1*8 + 1*16 =
= 0 + 2 + 0 + 8 + 16 =
= 26
(713,52)O = (x)D
x = 2*8-2 + 5*8-1 + 3*80 + 1*81 + 7*82 =
= 2/64 + 5/8 + 3 + 8 + 7*64 =
= 459,65625
(101,11)B = (x)D
x = 1*22 + 0*21 + 1*20 + 1*2-1 + 1*2-2 =
= 1*4 + 0*2 + 1*1 + 1*(1/21) + 1*(1/22) =
= 4 + 0 + 1 + 0,5 + 0,25 =
= 5,75
 Trasformazioni HEX DEC
(A01F)H = (x)D
x = 15*160 + 1*161 + 0*162 + 10*163 =
= 15 + 16 + 0 + 40960 =
= 40991
 Trasformazioni OCT DEC
(67101)O = (x)D
x = 1*80 + 0*81 + 1*82 + 7*83 + 6*84 =
= 1 + 0 + 64 + 3587 + 24576 =
(1F,2A)H = (x)D
x = 15*160 + 1*161 + 2*16-1 + 10*16-2 =
= 15 + 16 + 2*(1/16) + 10*(1/256) =
= 31,1640625
ESEMPI: TRASFORMAZIONI
DA
BASE 10
 Trasformazione DEC BIN
(1045,123)D = (x)B
1045 | 1
0,123 *
522 | 0
0,246 *
261 | 1
0,492 *
130 | 0
0,984 *
65 | 1
0,968 *
32 | 0
0,936 *
16 | 0
8 | 0
4 | 0
2 | 0
1 | 1
0 |
x =10000010101,00011
2
2
2
2
2
2
=
=
=
=
=
=
 Trasformazione DEC OCT
(3674,65625)D = (x)O
0,246
0,492
0,984
1,968
1,936
...
A BASE
= 28225
N≠10
3674
459
57
7
0
|
|
|
|
|
2
3
1
7
0,65625 * 8 = 5,25
0,25 * 8 = 2,0
x = 7132,52
 Trasformazione DEC HEX
(40991,615)D = (x)H
40991
2561
160
10
0
|
|
|
|
|
15 = F
1
0
10 = A
0,615
0,84
0,44
0,04
0,64
0,24
0,84
* 16
* 16
* 16
* 16
* 16
* 16
...
=
=
=
=
=
=
9,84
13[D],44
7,04
0,64
10[A],24
3,84
x = A01F,9D70A3
RAGGRUPPAMENTO DI BIT
I sistemi di numerazione in base ‘potenza di due’, come ad esempio il sistema Ottale o il sistema Esadecimale posseggono una proprietà
particolare nei confronti del sistema Binario. Molto probabilmente la stessa proprietà è valida tra basi analoghe (es. base 3 e basi 9, 12, ecc..).
La proprietà consiste in questo:
Si considerino una base opportuna N, in modo che ∃ k : 2k = n e si decodifichino in binario tutti gli n simboli della base N in modo da ottenere n
rappresentazioni binarie da k simboli binari ognuna (riempiendo eventualmente con zeri).
Allora data una qualsiasi cifra in base N, si ottiene la rappresentazione binaria di quella cifra sostituendo ad ogni simbolo della cifra la
corrispondente codifica binaria
ovvero
.doc - 1684Kb - 12/set/2008 - 15 / 82
S3Abacus – Architetture/Asm
All’interno delle macchine digitali le informazioni sono sempre rappresentate numericamente, anche le informazioni di natura non numerica come ad
Data unaparole
cifra binaria
qualsiasi, si possono raggruppare i bit in gruppi da k elementi, a partire da destra; si ottiene la rappresentazione in base N
esempio
ed immagini.
sostituendo
ai gruppi
il rispettivo
simbolo
base N.
Evidentemente
esistono
modi specifici
perininterpretare
l’informazione numerica di volta in volta come numero, numero con segno, numero con parte
Se
si fosselettera
in presenza
di cifre dotate
di parte
frazionaria, il raggruppamento della parte frazionaria deve partire immediatamente dopo la virgola.
decimale,
alfanumerica,
immagine,
ecc...
Questa
curiosa
ma
estremamente
utile
proprietà
è
anche
dettaedel
r a g g r u p p adell’informazione.
mento di bit.
L’insieme di tali modi di interpretazione è detto anche codifica
rappresentazione
Un aspetto che in ogni caso è necessario mettere in evidenza è che la rappresentazione numerica generale più spesso utilizzata per rappresentare
le informazioni rispetto alle macchine digitali è la rappresentazione binaria o in generale rappresentazioni in basi potenze di due (base Otto e base
Sedici).
ESEMPI: RAGGRUPPAMENTO DI BIT
Un’altra considerazione che è opportuno ricordare sempre è che i contenitori digitali delle informazioni numeriche sono sempre ben definiti in
Siano ad esempio
le tabelle
di decodifica:
dimensione,
in genere misurati
con gruppi
da otto bit detti B y t e (due simboli esadecimali).
BIN misurazioni
OCT
BIN
HEX sonoBIN
Altre
tipiche
il N iHEX
b b l e (4 bit ovvero un simbolo esadecimale) o la P a r o l a , detta spesso anche Word, contenente in genere 16
0 (4 000
0
0000 8
1000
bit
simboli esadecimali).
1
001questione
1 terminologica
0001 9 che
1001
Un’altra
spesso si accompagna a queste, riguarda il riferimento ai bit di maggior peso in un contenitore, indicati con la
2
010
2 Significant
0010 ABit) o1010
sigla
M
S B (Most
il riferimento ai bit di minor peso, indicati con L S B (Low Significant Bit).
3 limitatezza
011
3
0011con Bla quale
1011
La
intrinseca
si rappresentano le informazioni a livello digitale implica un paio di considerazioni:
4
100 un4certo0100
1100
- assegnato
numeroCn di bit
per la rappresentazione di una informazione, è sempre possibile definire un campo di validità per la
5
101
5 definito
0101 dal
D numero
1101di bit assegnati.
rappresentazione,
110
6
0110
E
1110 all’interno di un contenitore a n bit presenteranno alcuni zeri MSB per completare il contenitore.
-6le rappresentazioni
di numerosi
numeri
7
111
7
0111 F
1111
Se ad esempio si stabilisce un contenitore da n=8 bit per rappresentare numeri naturali, il campo di validità del contenitore - in questo caso il byte va da 0 a 255, infatti la prima rappresentazione possibile evidentemente è (00000000)B=(0)D e l’ultima invece: (11111111)B = (255)D.
Inoltre si può notare come il numero (7)D, che possiede una rappresentazione binaria in (111)B, in questo caso si rappresenterà in (00000111)B.
Si deve notare che se un contenitore è provvisto di n bit, i l c a m p o d i v a l i d i t à (range) per quel contenitore vale da 0 a 2n -1
Si deve anche considerare poi la situazione per cui una operazione, ad esempio di somma, tra due numeri contenuti nello stesso contenitore,
potrebbe originare un numero con una rappresentazione che utilizza un numero di bit superiori a quelli previsti.
In questo caso si parla di t r a b o c c a m e n t o (overflow) e si può operare un troncamento dei bit fuoriusciti mantenendo solo i bit all’interno del
contenitore.
A scanso di equivoci sarà necessario d’ora in poi specificare sempre il contenitore per una data rappresentazione, soprattutto per quanto riguarda le
operazioni sui bit che introduciamo ora.
OPERAZIONI BITWISE
Le operazioni a livello di bit (bitwise) si effettuano conoscendo a memoria le rispettive tabelline binarie che coincidono con le tavole di verità già
viste in logica matematica. Esse sono:
|NOT
--+--0 | 1
1 | 0
| |AND
------0|0| 0
0|1| 0
1|0| 0
1|1| 1
| | OR
------0|0| 0
0|1| 1
1|0| 1
1|1| 1
| |XOR
------0|0| 0
0|1| 1
1|0| 1
1|1| 0
Se gli operandi non avessero lo stesso numero di bit è necessario ‘allineare’ l’operando con minor numero di bit giustapponendo degli zeri.
Esistono anche operazioni miste, cioè con un argomento binario e uno decimale, il cui risultato è binario.
- SHR (spostamento a destra " b shr n " )
Si tratta di spostare a destra di n posizioni i bit del 1° argomento. I bit LSB naturalmente scompaiono, mentre se l’operazione si riferisce ad un
contenitore specifico si sostituiscono gli spazi vuoti con degli 0.
- SHL (spostamento a sinistra " b shl n " )
Si tratta di spostare a sinistra di n posizioni i bit del 1° argomento. Se l’operazione si riferisce ad un contenitore specifico si sostituiscono gli spazi
vuoti con degli 0 e i bit MSB man mano scompaiono.
Inoltre si possono ricordare anche operazioni analoghe a queste però sempre riferite a contenitori, dette rotazioni:
- ROR (rotazione a destra " b ror n " )
Analogo a shr, con l’accortezza di di far rientrare a sinistra i bit espulsi a destra.
- ROL (rotazione a sinistra " b rol n " )
Analogo a shl, con l’accortezza di di far rientrare a destra i bit espulsi a sinistra.
ESEMPIO: OPERATORI
SUI
BIT
NB. Le cifre sono tutte binarie.
Eseguire NOT(1001);
NOT(1001)=110 oppure, se riferito al byte: 11110110
Eseguire (1001) AND (111);
1001 AND 111 = 1, oppure se riferito al byte: 00000001
Eseguire (1001) OR (111);
1001 OR 111 = 1111, oppure se riferito al byte: 00001111
Eseguire (1001) XOR (111);
1001 XOR 111 = 1110, oppure se riferito al byte: 00001110
Eseguire (1001) SHR (3);
1001 SHR 3 = 0001, oppure se riferito al byte: 00000001
Eseguire (1001) SHL (3);
1001 SHR 3 = 1001000, oppure se riferito al byte: 01001000
.doc - 1684Kb - 12/set/2008 - 16 / 82
S3Abacus – Architetture/Asm
Eseguire (00001001) ROR (3);
00001001 ROR 3 = 00100001
Eseguire (00001001) ROL (3);
00001001 ROL 3 = 01001000
Calcolare
1. (x)B =
Oppure
(x)B =
le seguenti
(34)H SHR 3
se riferito
(34)H SHR 3
espressioni:
= (110100)B SHR 3 = (000110)B
a Byte:
= (00110100)B SHR 3 = (00000110)B
2. (x)H = (3F1)H SHL 1 = (1111110001)B SHL 1 = (11111100010)B = (7E2)H
Oppure se riferito a Parola:
(x)H = (3F1)H SHL 1 = (0000001111110001)B SHL 1 = (0000011111100010)B = (07E2)H
Le operazioni a bit Shr e Shl possono essere utilizzate per dividere o moltiplicare per potenze di 2.
In particolare lo Shr divide e restituisce il quoziente (parte intera) della divisione per 2n, dove n è il secondo argomento dell'operazione.
Lo Shl moltiplica e restituisce il prodotto della moltiplicazione per 2n, dove n è il secondo argomento dell'operazione.
MASCHERE DI BIT
Spesso quando si utilizzano informazioni numeriche risulta utile riuscire a sapere se un dato bit di peso n vale zero o uno, oppure è necessario
saper mettere a zero o a uno un determinato bit di peso n all’interno di una quantità numerica.
Per leggere il valore del bit di peso n in una configurazione X è sufficiente creare un valore d’appoggio Mn con uguale numero di bit presenti in X,
porre a 1 il solo bit di peso n e tutti i rimanenti a zero. Ora è possibile verificare il bit n-mo di X con l’operatore AND, cioè:
- se X AND Mn è diverso da zero, allora il bit n-simo di X vale 1, altrimenti vale zero.
Per scrivere a 1 il bit n-mo di un valore X invece è sufficiente costruire come prima Mn e utilizzare l’operatore OR:
- X OR Mn da come risultato ancora X con il bit n-mo sicuramente settato a 1.
Per scrivere a zero il bit n-mo di X invece si nega Mn (ottenendo ad esempio Qn) e si utilizza l’AND:
- X AND Qn da come risultato sicuramente X con il bit n-mo a zero.
Si noti che per costruire il valore Mn è sufficiente considerare che Mn = 2n.
ESEMPIO: PROPRIETÀ SPOSTAMENTI
E
MASCHERE
DI
BIT
 Si verifichi che (7)D SHL 2 coincide con (28)D.
(7)D SHL 2 = (111)B SHL 2 = (11100)B = (24+23+22)D = (16+8+4)D = (28)D
 Verificare il valore del bit 3 di (26)D.
(26)D = (11010)B; Mn = (01000)B; (11010) AND (01000) = (01000) ≠ 0; il bit 3 di (26)D vale 1.
 Settare a 1 il bit 3 di (33)D.
(33)D = (100001)B; Mn = (001000)B; (100001) OR (001000) = (101001)
 Settare a 0 il bit 4 di (26)D.
(26)D = (11010)B; Qn = (01111)B; (11010) AND (01111) = (01010)
NUMERI CON SEGNO
Ora ci si pone il problema della rappresentazione di numeri con segno, dato che una macchina digitale non possiede il simbolo ‘-’ e comunque oltre
ad identificare tali rappresentazioni numeriche bisogna anche essere in grado di utilizzarle per le varie operazioni.
Il problema è questo: avendo a disposizione uno dei contenitori descritti più sopra, come è possibile rappresentare in modo efficiente numeri con
segno? cioè distinguere ad esempio -37 da + 37 dato che la loro rappresentazione binaria assoluta è la medesima?
E’ necessario porre una convenzione di rappresentazione, in modo tale che quando la macchina è informata di tale convenzione riguardo una
configurazione binaria essa possa comportarsi di conseguenza.
Naturalmente la convenzione non può che utilizzare ancora solo i simboli zero e uno, magari disposti con oculatezza.
E’ anche evidente che se un contenitore di ordine n può contenere 2n configurazioni di bit di valore assoluto, lo stesso contenitore dovrà ora
contenerne la metà di segno positivo e l’altra metà di segno negativo, considerando lo zero avente uno dei due segni.
La convenzione è la seguente:
- I numeri positivi mantengono la consueta rappresentazione limitatamente a n-1 bit, badando di mantenere a zero il bit MSB;
- I numeri negativi si ottengono dal rispettivo positivo complementandolo a due in binario.
L’operazione di complementazione a 2 di un numero binario si ottiene facilmente negando il numero e aggiungendo uno.
Consideriamo un contenitore a 4 bit;
numeri positivi:
numeri negativi:
0001 = +1
-1 = C2(0001) = (1110)+1 = 1111
0010 = +2
-2 = C2(0010) = (1101)+1 = 1110
0011 = +3
-3 = C2(0011) = (1100)+1 = 1101
0100 = +4
-4 = C2(0100) = (1011)+1 = 1100
0101 = +5
-5 = C2(0101) = (1010)+1 = 1011
0110 = +6
-6 = C2(0110) = (1001)+1 = 1010
0111 = +7
-7 = C2(0111) = (1000)+1 = 1001
.doc - 1684Kb - 12/set/2008 - 17 / 82
S3Abacus – Architetture/Asm
Rimangono ora le due sole configurazioni 0000 e 1000 che si decide di assegnare la prima allo zero e la seconda al -8, dato che come si può
notare, tutti i numeri positivi possiedono il MSB a zero e i numeri negativi a uno.
La ragione per cui si usa la complementazione a due per la rappresentazione dei numeri negativi risiede nella proprietà che la somma tra due
numeri con segno risulta essere perfettamente algebrica (se si tronca il traboccamento) e quindi nei vari microprocessori non è necessario
realizzare un circuito apposta per la differenza dato che basta il sommatore.
Si può generalizzare questa convenzione ad un contenitore di n bit e quindi affermare che il campo di validità per numeri con segno vale:
-2n ... +2n -1
ESEMPI: OPERAZIONI
Eseguire
1. (+4)D
2. (-2)D
3. (+5)D
4. (-1)D
CON
COMPLEMENTAZIONE
su 4 bit:
+ (-3)D
+ (+5)D
+ (-8)D
+ (-4)D
Leggendo le codifiche direttamente dalla tabella:
(0100)+(1101)
= (0001) = (+1)D
(1110)+(0101)
= (0011) = (+3)D
(0101)+(1000)
= (1101) = (-3)D
(1111)+(1100)
= (1011) = (-5)D
Naturalmente se si decidesse di sommare algebricamente -1 a -8 oppure +2 a +7 il risultato non sarebbe corretto e si parla rispettivamente di
u n d e r f l o w e di o v e r f l o w generato dall’operazione. queste situazioni sono rilevate generalmente senza alcun problema dalle macchine
digitali.
NUMERI CON PARTE FRAZIONARIA
Lo stesso problema di rappresentazione si ripropone a maggior ragione per numeri con parte frazionaria. in questo caso la classica
rappresentazione in virgola mobile consistente di m a n t i s s a per base elevata ad esponente del tipo m·be (es. 6,68183·103 =
0,668183·104 = 668183·10-2 ) può essere utilizzata per formalizzare la rappresentazione in v i r g o l a m o b i l e n o r m a l i z z a t a in cui
la mantissa è tale per cui: 1/b < m < 1.
Nell'esempio precedente la forma in virgola mobile normalizzata diventa: 0,668183*104
Ad esempio, dovendo rappresentare il valore binario 11010,111 (26,875 decimale), la sua virgola mobile normalizzata binaria diventa:
0,11010111·10101 (cioè 0,83984·25), che in una rappresentazione a 16 bit può diventare:
mantissa
esponente
± 1 1 0 1 0 1 1 1 0 0 0 ± 1 0 1
dove i simboli ± rappresentano i bit di segno di mantissa ed esponente
Nell’esempio entrambi i simboli ± sarebbero stati a zero dato che la parte esponenziale in genere è rappresentata in complemento a due, mentre la
mantissa in modulo e segno.
Il numero di bit dedicati alla mantissa è detta p r e c i s i o n e della rappresentazione e il numero di bit destinati all'esponente è detto o r d i n e d i
g r a n d e z z a della rappresentazione.
Inoltre, siccome il primo bit dopo il segno della mantissa è sempre a 1, esiste la convenzione di darlo per scontato, recuperando quindi una
posizione di precisione (c o n v e n z i o n e d e l b i t n a s c o s t o ).
RAPPRESENTAZIONI IN ECCESSO 2
N-1
Prima di introdurre i dettami dello standard che dal 1980 disciplina la convenzione di rappresentazione dei numeri in virgola mobile, introduciamo un
secondo tipo di rappresentazione dei numeri negativi in formato binario.
Dato un contenitore a m bit, si definisce rappresentazione di numeri n e g a t i v i i n e c c e s s o 2 m-1 la rappresentazione che si ottiene quando,
dato un qualsiasi numero con segno s in notazione decimale ad esempio, si rappresenta quel numero in binario con la forma binaria standard di:
s + 2m-1
ESEMPIO: RAPPRESENTAZIONI
IN ECCESSO
8
Se si parla di eccesso 8 significa che
Iniziamo la rappresentazione a partire
0, allora si considera 0 + 8 = 8, la
+1, allora si considera +1 + 8 = 9, la
+2, allora si considera +2 + 8 =10, la
...
+7, allora si considera +7 + 8 =15, la
il contenitore vale 4 (infatti
da zero:
cui rappresentazione binaria è
cui rappresentazione binaria è
cui rappresentazione binaria è
24-1 = 8).
1000, quindi 0 -> 1000
1001, quindi +1 -> 1001
1010, quindi +2 -> 1010
cui rappresentazione binaria è 1111, quindi +7 -> 1111
Evidentemente non è possibile rappresentare +8, dato che +8+8=16 che non è rappresentabile su 4 bit.
Si passa ai numeri negativi, e quindi si inizia con meno uno:
-1, allora si considera -1 + 8 = 7, la cui rappresentazione binaria è 0111, quindi -1 -> 0111
-2, allora si considera -2 + 8 = 6, la cui rappresentazione binaria è 0110, quindi -2 -> 0110
...
-8, allora si considera -8 + 8 = 0, la cui rappresentazione binaria è 0000, quindi -8 -> 0000
Si noti come la rappresentazione eccesso 2m-1 è identica alla rappresentazione in complemento due se si
invertono i bit di segno.
.doc - 1684Kb - 12/set/2008 - 18 / 82
S3Abacus – Architetture/Asm
STANDARD IEEE 754
Il documento I E E E 7 5 4 prima di tutto classifica le rappresentazioni rispetto alla grandezza dei contenitori e quindi prevede:
- rappresentazione con p r e c i s i o n e s e m p l i c e a 32 bit
- rappresentazione con p r e c i s i o n e d o p p i a a 64 bit
- rappresentazione con p r e c i s i o n e e s t e s a a 80 bit
Naturalmente la rappresentazione dell’IEEE tratta numeri reali in virgola mobile normalizzata utilizzando la convenzione del bit nascosto della
mantissa (primo bit della mantissa considerato di default a 1 e quindi non riportato nella rappresentazione), la quale, non essendo completa, viene
chiamata s i g n i f i c a n t e .
Inoltre la normalizzazione in base 2 adottata prevede sempre una cifra intera a 1 binario (cioè le normalizzazioni binarie adottate sono della forma
1,bbbbb.... invece che della forma standard 0,bbbbb...); pertanto il bit nascosto non sarà la prima cifra dopo la virgola ma il bit a 1 sempre presente
nella parte intera.
Esaminando lo standard in precisione semplice, la disposizione degli elementi all’interno dei 32 bit è la seguente:
segno esponente
significante
1
8
23
Esaminando lo standard in precisione doppia, la disposizione degli elementi all’interno dei 64 bit è la seguente:
segno esponente
significante
1
11
52
In precisione estesa i tre campi S,E,M valgono 1,15 e 64.
Le convenzioni utilizzate sono le seguenti:
• il bit di segno, che indica il segno globale del numero in virgola mobile, è 0 per i numeri positivi e 1 per quelli negativi;
• l’esponente è espresso in eccesso 127 (precisione semplice) o in eccesso 1023 (precisione doppia)
• il significante è quasi sempre in virgola mobile normalizzata con bit nascosto.
• eccezioni:
i. se l’esponente vale 0 e il significante vale 0, allora il numero complessivo vale 0 a prescindere dal bit di segno;
ii. se l’esponente vale 0 e il significante è ≠ da 0, il numero complessivo è denormalizzato;
iii. se l’esponente ha tutti i bit a 1, cioè è il massimo valore positivo concesso in eccesso 128 o 1024, e il significante vale 0, il numero
complessivo vale infinito (∞);
iv. se l’esponente ha tutti i bit a 1, cioè è il massimo valore positivo concesso in eccesso 128 o 1024, e il significante è ≠0, il numero
complessivo è un NAN (Not a Number) cioè ad esempio ∞ diviso ∞.
Va da sè che un numero in virgola mobile normalizzata non potrà avere esponente con tutti zeri o con tutti uno. Infatti si usa l’eccesso 127 (1023) e
non l’eccesso 128 (1024) per evitare le due configurazioni di esponente con tutti i bit uguali.
Un numero denormalizzato è tale quando l’esponente vale 0 e il significante è ≠ 0; in questo caso si da come convenzione che l’esponente vale 2-127
(o 2-1023) e il significante ridiventa una mantissa non essendoci più il bit nascosto. In questo caso si ottengono rappresentazioni fino a 2-150 (o 2-1075)
che allontanano l’underflow.
ESEMPIO: RAPPRESENTAZIONI
IN PRECISIONE SEMPLICE
IEEE 754
Vediamo la rappresentazione del numero 1,5 in precisione semplice.
Trasformato in binario si ottiene: (1,1)B.
Normalizzato: 1,1 100 (attenzione: 10B = 2D !)
Mantissa: 11000000
Significante: 10000000 (tolto il primo bit)
Esponente: (0)B = (0)D, quindi in eccesso 127: (0+127)D = (127)D = (01111111)B
Segno: 0
(00111111110000000000000000000000)B IEEE754 = (1,5)D
(3
F
C
0
0
0
0
0
)H IEEE754 = (1,5)D
Vediamo la rappresentazione per 128,5.
Binario: 10000000,1
Normalizzato: 1,00000001 10111
Mantissa: 100000001
Significante: 00000001
Esponente: (111)B = (7)D, quindi in eccesso 127: (7+127)D = (134)D = (10000110)B
Segno: 0
(01000011000000001000000000000000)B IEEE754 = (128,5)D
(4
3
0
0
8
0
0
0
)H IEEE754 = (128,5)D
Per 1 si ottiene 3F800000
S3Abacus – Architetture/Asm
per 0,5 si ottiene 3F000000.
.doc - 1684Kb - 12/set/2008 - 19 / 82
.doc - 1684Kb - 12/set/2008 - 20 / 82
S3Abacus – Architetture/Asm
CODICI, COMUNICAZIONE, INFORMAZIONE
Le rappresentazioni numeriche su diverse basi che si sono studiate in precedenza sono necessarie per poter far funzionare le macchine digitali, le
quali possiedono unità intelligenti basate solo sull’aritmetica binaria. Possiamo affermare che le rappresentazioni numeriche su diverse basi sono
parte integrante e interna alle macchine digitali (i calcolatori).
In questo capitolo invece si prendono in considerazione vari modi per rappresentare l’informazione, e quindi non necessariamente i numeri.
Qualche malinteso potrebbe sorgere dato che anche i numeri sono informazione, ma, come ci insegna l’esperienza, ne rappresentano solo una
parte. Ad esempio una qualsiasi frase tratta dall’esperienza quotidiana rappresenta informazione: “Dove ti rechi oggi pomeriggio?”; questa frase è
informazione, ma non contiene numeri. Cionoindimeno molta informazione utilizza numeri al suo interno, senza i quali non significherebbe nulla:
“Oggi vado a scuola alle 8 e 30”. Il fatto che avremmo potuto esprimere la stessa informazione con: “Oggi vado a scuola alle otto e trenta” per ora
non sarà considerato.
Fondamentale invece è la considerazione per cui l’informazione ha senso di esistere solo se può essere veicolata, cioè trasferita a qualcuno che sia
diverso da chi la detiene.
Ciò significa che l’informazione prevede necessariamente la comunicazione e viceversa.
Il problema della codifica e dei codici per l’informazione è strettamente collegato alla comunicazione.
La comunicazione è un processo che si instaura tra due o più entità, umane o materiali (es. le macchine digitali), che ha lo scopo di trasferire
informazione.
Il processo quindi prevede almeno due soggetti, che possono essere chiamati t r a s m e t t i t o r e e r i c e v i t o r e i quali possono scambiarsi
vicendevolmente i ruoli: il trasmettitore può diventare ricevente e viceversa. L’oggetto della comunicazione invece rimane già individuato e coincide
con l’informazione stessa.
I due soggetti della comunicazione per poter comunicare devono possedere alcuni requisiti indispensabili:
a. un linguaggio in comune (per rappresentare l’informazione)
b. un dispositivo per realizzare il linguaggio
c. alcune regole condivise per saper comunicare (per non interferirsi durante la comunicazione)
d. un canale di trasmissione in comune
Abbiamo omesso l’apparato ricevente dato che possiamo pensarlo assolutamente integrato nel dispositivo di realizzazione della trasmissione di cui
al punto b.
Questi requisiti sono sempre presenti in ogni forma di comunicazione, dalla comunicazione tramite il l i n g u a g g i o n a t u r a l e tra uomini, alla
comunicazione automatizzata che avviene tra due calcolatori elettronici.
Nel linguaggio naturale si possono individuare le componenti che abbiamo elencato in:
- soggetti della comunicazione: due persone;
- oggetto della comunicazione: un concetto, una frase, ecc...;
- linguaggio della comunicazione: lingua in uso nella comunità a cui appartengono le persone;
- dispositivo della comunicazione: l’insieme di cervello, orecchie, lingua e corde vocali posseduto dalle persone;
- regole della comunicazione: es. chiedere la parola e non sovrapporsi, sincronizzando automaticamente ascolto e parlato;
- canale della comunicazione: l’aria, che consente la propagazione delle onde sonore di cui è composto il suono emesso dalla voce.
In una comunicazione automatizzata tra macchine invece potremmo individuare:
- soggetti: due calcolatori;
- oggetto: un qualsiasi insieme di concetti in genere detti dati ;
- linguaggio: una convenzione qualsiasi su come organizzare i bit;
- dispositivo: una scheda di interfaccia;
- regole: un insieme di regole che sincronizza le fasi della comunicazione e che in gergo viene detto protocollo;
- canale: un cavo oppure onde elettromagnetiche (radio), infrarosso, ecc...
ARGOMENTI DI QUESTA SEZIONE SARANNO SOLTANTO:
- il linguaggio di organizzazione dei dati, anche detto c o d i f i c a d e l l e i n f o r m a z i o n i
- alcune regole di sincronizzazione della comunicazione; in particolare quelle volte a individuare gli errori nel processo di comunicazione, detto
anche g e s t i o n e e r r o r i .
Per quanto ci riguarda useremo come unità minima di informazione il bit, cioè la possibilità di un sistema di assumere solo due possibili stati che si
indicano in genere con 0 e 1. L’informazione quindi sarà identificata con la rappresentazione di dati tramite sequenze di bit.
Se si individuano allora un numero M di dati che saranno oggetto di informazione, la rappresentazione di questa informazione in formato binario cioè tramite sequenze di bit - potrà essere possibile se ad ogni dato tra gli M dati disponibili è associabile una differente sequenza di bit.
Se ad esempio si dovesse gestire l’informazione basata sulle 26 lettere dell’alfabeto inglese, sarà necessario individuare 26 configurazioni di bit
differenti per rappresentare ogni singola lettera. In questo caso la quantità di informazione M vale 26. La codifica delle M (=26) informazioni
differenti (=“abcdefghijklmnopqrstuvwzxy”) avviene associando ad ogni dato (cioè ad ogni singola lettera) una sequenza di bit differente in modo
che, data una lettera qualsiasi si possa immediatamente individuare la sua codifica binaria e, data una codifica binaria sia immediatamente
possibile individuare il suo dato (la lettera).
In altre parole, un insieme di potenza M può essere misurato (interscambiabile) con un insieme di M configurazioni binarie (e viceversa).
Chiarito questo fatto generale, giunge spontaneo porsi il problema: assegnata una quantità di informazione da rappresentare e definita M, quante
cifre binarie (bit) sono necessarie per effettuare le configurazioni che dovranno rappresentare gli M dati?
Il passo successivo riguarda l'individuazione del numero di cifre binarie (N) necessarie per esprimere M informazioni.
Ciò deriva dalla relazione seguente per la quale, assegnato M, il valore N si ricava:
N = log2 M
(se M potenza esatta di 2)
oppure
N = INT(log2M)+1
con INT() che significa: “parte intera di ( )”.
(se M non potenza esatta di 2)
S3Abacus – Architetture/Asm
ESEMPIO: RAPPRESENTAZIONE
.doc - 1684Kb - 12/set/2008 - 21 / 82
OTTIMALE DI INFORMAZIONE
Si debba rappresentare l’informazione contenuta nell’insieme delle lettere dell’alfabeto inglese.
Le lettere dell’alfabeto inglese sono 26, cioè M=26;
M non è una potenza esatta di due, infatti (24=16≠26 e 25=32≠26); allora si utilizza la formula:
N = INT(log2M)+1
con M=26;
N = INT(log226)+1;
N = INT(4.701)+1;
N = 4+1 = 5;
Quindi servono sequenze da 5 bit per rappresentare 26 informazioni differenti
Una possibile codifica delle 26 informazioni potrebbe essere:
a - 00000
f - 00101
k - 01010
p - 01111
u - 10100
x - 11001
b - 00001
g - 00110
l - 01011
q - 10000
v - 10101
c - 00010
h - 00111
m - 01100
r - 10001
w - 10110
d - 00011
i - 01000
n - 01101
s - 10010
z - 10111
e - 00100
j - 01001
o - 01110
t - 10011
x - 11000
Naturalmente rimangono 32-26 = 6 configurazioni da 5 bit inutilizzate.
Si debba rappresentare l’informazione relativa ai nominativi degli abitanti di Parma, stimati in 175000 unità
Il dato M quindi vale 175000; siccome 175000 non è una potenza esatta di 2 (), si usa la formula:
N = INT(log2M)+1
con M=175000;
N = INT(log2175000)+1;
N = INT(17.41)+1;
N = 17+1 = 18;
Allora con un contenitore da 18 bit si possono rappresentare, con una codifica opportuna, tutti gli abitanti
di Parma.
CODIFICHE NUMERICHE
Come si diceva poco fa, l’informazione è composta variamente sia di concetti numerici, sia di concetti alfabetici; spesso è necessario effettuare
comunicazioni il cui oggetto è solo informazione numerica, mentre in altri casi bisogna poter comunicare sia dati numerici che dati alfabetici che, in
una parola, sono detti alfanumerici.
Per questo motivo si distingue tra C o d i f i c h e N u m e r i c h e e C o d i f i c h e A l f a n u m e r i c h e .
La codifica numerica riassume una serie di regole e proprietà per rappresentare con configurazioni di bit i simboli numerici decimali da 0 a 9 o, in
qualche caso, i numeri esadecimali da 0 a F.
Le proprietà generiche delle configurazioni di bit rappresentanti simboli numerici si possono riassumere:
• Distanza di Hamming
• Autocomplementazione (solo codifica numerica)
• Pesatura (solo codifica numerica)
• Ridondanza
La D i s t a n z a d i H a m m i n g tra due configurazioni è data dal numero di bit differenti di ugual peso presenti nelle due configurazioni;
La proprietà di A u t o c o m p l e m e n t a z i o n e tra due configurazioni si valuta solo quando i simboli numerici rappresentati sono complemento a
9 (o a F); in questo caso l’autocomplementazione è presente se le due configurazioni corrispondenti sono complemento a 1 (una la negazione
dell'altra);
La caratteristica della P e s a t u r a è rispettata se esiste un algoritmo che applicato ad una configurazione di bit consente di ottenere il simbolo
rappresentato mediante somme di valori sempre uguali per ogni posizioni (sommati o meno a seconda del valore 0 o 1 del bit);
La R i d o n d a n z a in una codifica si ottiene se il numero di configurazioni di bit ottenibili (N) per rappresentare M informazioni è superiore al
numero di informazioni, cioè se 2N > M.
ESEMPI
DI PROPRIETÀ DEI CODICI
 Esempio di distanza di Hamming
codifica1: 10101010
codifica2: 10100001
^ ^^
La distanza di Hamming tra le due codifiche vale 3 dato che i bit di ugual peso cambiano tre volte.
 Esempi di autocomplementazione
codifica simbolo 3: 0011
codifica simbolo 6: 1100
Le due configurazioni sono complementanti infatti: 3+6=9 e le codifiche sono negate
codifica simbolo 4: 0011
codifica simbolo 6: 1100
Le due configurazioni non sono complementanti infatti (4+6≠9);
codifica simbolo 3: 0011
codifica simbolo 6: 1101
Le due configurazioni non sono complementanti infatti pur essendo 6+3=9, i bit non sono invertiti.
S3Abacus – Architetture/Asm
.doc - 1684Kb - 12/set/2008 - 22 / 82
 Esempio di pesatura
codifica simbolo 3: 0011, se pesi b3=2, b2=4, b1=2,b0=1 si ottiene 0*2+0*4+1*2+1*1=3;
codifica simbolo 6: 1100, se pesi b3=2, b2=4, b1=2,b0=1 si ottiene 1*2+1*4+0*2+0*1=6;
codifica simbolo 1: 0000, non esiste algoritmo per ottenere 1 da 0000 !
Vediamo ora alcuni tra i più utilizzati codici numerici e le loro proprietà complessive
CODIFICA NUMERICA BCD
La codifica numerica BCD (B i n a r y C o d e d D e c i m a l ) è quella che si ottiene trasformando
in binario le 10 cifre decimali e considerandone la configurazione a 4 posti (siccome M=10,
N=log2M+1, cioè 4).
- Il codice BCD è sicuramente pesato (b0=1; b1=2; b2=4; b3=8);
- è ridondante (infatti esistono 6 configurazioni inutilizzate);
- la distanza di Hamming massima tra due configurazioni adiacenti è 4 (tra 7 e 8);
- non è autocomplementante (es. 0 e 9 non hanno i bit invertiti) e così via
CODIFICA NUMERICA ECCESSO 3
La c o d i f i c a n u m e r i c a E c c e s s o 3 si ottiene da quella BCD iniziando a codificare lo 0
con il 3 BCD e proseguendo analogamente.
- Il codice Eccesso 3 non è pesato;
- è ridondante (infatti esistono 6 configurazioni inutilizzate);
- la distanza di Hamming massima tra due configurazioni adiacenti è 4 (tra 9 e 0);
- è autocomplementante (ogni configurazione di cifre complemento a 9 ha i bit corrispondenti
invertiti)
CODIFICA NUMERICA AIKEN
la c o d i f i c a n u m e r i c a A i k e n si ottiene considerando che il peso dei bit assegnato è
b3=2, b2=4, b1=2, b0=1;
- Il codice numerico Aiken è sicuramente pesato;
- è ridondante (infatti esistono 6 configurazioni inutilizzate);
- la distanza di Hamming massima tra due configurazioni adiacenti è 4 (tra 9 e 0);
- è autocomplementante (ogni configurazione di cifre complemento a 9 ha i bit corrispondenti
invertiti)
CODIFICA NUMERICA GRAY
La c o d i f i c a n u m e r i c a G r a y si ottiene considerando che la distanza tra una
configurazione e la successiva (e la precedente) è sempre e solo 1.
In realtà il codice è usato per rappresentare 16 simboli a partire dalla prima configurazione per lo 0
che possiede la forma 0000; il codice Gray a 10 simboli è l'eccesso 3 del codice gray a 16 simboli
(si scartano le configurazioni di 0,1,2,D,E,F).
- Il codice numerico Gray non è pesato;
- è ridondante (infatti esistono 6 configurazioni inutilizzate);
- la distanza di Hamming massima tra due configurazioni adiacenti è 1;
- non è autocomplementante.
BCD
b2 b1
0
0
0
0
0
1
0
1
1
0
1
0
1
1
1
1
0
0
0
0
0
1
2
3
4
5
6
7
8
9
b3
0
0
0
0
0
0
0
0
1
1
b0
0
1
0
1
0
1
0
1
0
1
0
1
2
3
4
5
6
7
8
9
ECCESSO 3
b3 b2 b1
0
0
1
0
1
0
0
1
0
0
1
1
0
1
1
1
0
0
1
0
0
1
0
1
1
0
1
1
1
0
0
1
2
3
4
5
6
7
8
9
b3
0
0
0
0
0
1
1
1
1
1
AIKEN
b2 b1
0
0
0
0
0
1
0
1
1
0
0
1
1
0
1
0
1
1
1
1
b0
0
1
0
1
0
1
0
1
0
1
0
1
2
3
4
5
6
7
8
9
b3
0
0
0
0
0
1
1
1
1
1
GRAY
b2 b1
0
1
1
1
1
1
1
0
1
0
1
0
1
0
1
1
1
1
0
1
b0
0
0
1
1
0
0
1
1
0
0
b0
1
0
1
0
1
0
1
0
1
0
CODIFICHE ALFANUMERICHE
CODIFICA ALFANUMERICA BAUDOT
Il c o d i c e B a u d o t è stato uno dei primi codici alfanumerici ad essere implementato (serviva per gestire la comunicazione tra telescriventi); si
tratta di un codice a 5 bit per una codifica totale di 32 informazioni; in realtà due configurazioni sono dedicate per la decodifica delle successive
(11111=LTR; 11011=NUM) in modo tale da poter interpretare le configurazioni in un modo (LTR=lettera) se anticipate dal blocco LTR o in un altro
(NUM=numero) se anticipate dal blocco NUM.
In tutto quindi si codificano 60 informazioni.
CODIFICA ALFANUMERICA ASCII
Si tratta del codice più utilizzato nel mondo dei calcolatori (A m e r i c a n S t a n d a r d C o d e I n t e r c h a n g e I n f o r m a t i o n ) e si tratta
di un codice standard di 7 bit (128 informazioni) e pseudostandard a 8 bit (256 informazioni). Il codice ASCII esteso a 8 bit di norma usato sulle
macchine PC è detto P C - 8 . Tra le categorie di informazioni codificate all’interno del codice ASCII ricordiamo:
• simboli di controllo di trasmissione (da 0 a 6 e da 21 a 23)
S3Abacus – Architetture/Asm
•
•
•
•
simboli di controllo di stampa (da 7 a 15)
simboli di controllo dispositivi (dal 17 al 20)
simboli numerici e alfanumerici (dal 32 al 127)
simboli grafici e tipografici (dal 128 al 255)
.doc - 1684Kb - 12/set/2008 - 23 / 82
.doc - 1684Kb - 12/set/2008 - 24 / 82
S3Abacus – Architetture/Asm
CODICI ED ERRORI
Una caratteristica tipica della comunicazione con codifica delle informazioni è la possibilità di gestire in fase di ricezione eventuali errori dovuti a
disturbi incontrati dai dati durante la comunicazione. Infatti quando l’informazione si trova sul mezzo di trasmissione (cavi, aria, onde
elettromagnetiche) può subire, in varie misure, interferenze che modificano il contenuto dei bit.
Sintetizzando: succede piuttosto spesso che il trasmettitore trasmette il dato A correttamente, durante la comunicazione eventuali disturbi mutano il
dato in B e quindi il ricevitore riceve B.
Per affrontare politiche di controllo, prevenzione e verifica di errori di trasmissione si possono analizzare e studiare vari aspetti della costruzione dei
codici e delle codifiche.
In questo senso si può operare per costruire c o d i c i r i v e l a t o r i di errore, mediante i quali è possibile stabilire se un dato ricevuto è o non è
corretto; oppure si possono costruire c o d i c i c o r r e t t o r i di errore mediante i quali si riesce ad individuare l'errore presente nel dato e a
correggerlo automaticamente.
In tutti i casi i codici costruiti in tal senso devono per forza di cose essere r i d o n d a n t i (cioè devono contenere più informazione di quella
strettamente necessaria per il dato da trasmettere), in modo da trasmettere nell’informazione supplementare le informazioni utili per la rilevazione
e/o la correzione dell'errore.
PARITÀ
Una tecnica molto utilizzata nel campo delle comunicazioni è quella di dotare ogni codifica - eventualmente ogni gruppo specificato di codifiche - di
un bit supplementare che vale:
0 se il numero di bit a 1 nella codifica è dispari o 1 se il numero di bit a 1 presenti all’interno della codifica è pari (p a r i t à D I S P A R I )
ovvero
0 se il numero di bit a 1 presenti all’interno della codifica è pari o 1 se il numero di bit all’interno della codifica è dispari (p a r i t à P A R I ).
ESEMPIO: PARITÀ
Dato da trasmettere: 10000001;
n. di bit a “1”: 2; il bit di parità
PARI è 0; in totale: 10000001(0).
Dato da trasmettere: 10000011;
n. di bit a “1”: 3; il bit di parità
PARI è 1; in totale: 10000011(1).
Oppure
Dato da trasmettere: 10000001;
n. di bit a “1”: 2; il bit di parità DISPARI è 1; in totale: 10000001(1).
Dato da trasmettere: 10000011;
n. di bit a “1”: 3; il bit di parità DISPARI è 0; in totale: 10000011(0).
Corredare di controllo di parità un codice significa che il trasmettitore e ricevitore si mettono d’accordo a priori sul tipo di parità - Pari o Dispari -, e il
trasmettitore, una volta deciso di inviare una codifica (es. 10000001), calcola il bit di parità della codifica (in questo caso per parità Pari il bit di parità
vale 0) e invece di inviare la codifica originale, invia una nuova codifica in cui compare al primo (o all’ultimo) posto il bit di parità. Il trasmettitore
quindi spedirà: 100000010 (parità pari).
Il ricevitore, che sa di questa convenzione, prende in considerazione la parte di codifica depurata dal bit di parità, ne calcola il bit di parità e lo
confronta con il bit di parità ricevuto.
Il ricevitore quindi potrà ricevere, a seconda dei casi:
a) dato corretto, cioè 100000010. Egli considererà solo 10000001, ne calcolerà il bit di parità (0) e lo confronterà con quello ricevuto (0)
deducendo che il dato ricevuto è corretto.
b) dato errato, ad esempio 100000110. Egli considererà solo 10000011, ne calcolerà il bit di parità (1) e lo confronterà con quello ricevuto (0)
deducendo che il dato ricevuto è errato.
Naturalmente il metodo di semplice parità offre un sistema per rivelare un errore e non è esente da problemi di doppi errori (in cui la parità persiste)
e nel caso in cui l’errore dovesse modificare proprio il bit di parità.
In altri termini il ricevitore riesce solo a stabilire con certezza un errore (quando il controllo di parità non torna), ma non è in grado di essere certo
della validità del dato (quando il controllo di parità risulta corretto).
PARITÀ INCROCIATA
Con la p a r i t à i n c r o c i a t a si suggerisce un sistema per rivelare ed individuare (quindi correggere) eventuali errori. Si tratta di applicare la
parità ad un gruppo di dati sia in senso orizzontale (come già visto prima) detta p a r i t à T r a s v e r s a l e , sia in senso verticale, detta p a r i t à
Longitudinale.
Tali parità devono quindi essere spedite; la prima assieme ai bit della codifica a cui si riferisce, la seconda come blocco finale al termine della
sequenza. Naturalmente tutto ciò è verificabile solo su gruppi specificati a priori di codifiche.
ESEMPIO: PARITÀ INCROCIATA
Vediamo di spedire ad esempio l'informazione ERRORE!! utilizzando ASCII a 7 bit con parità pari incrociata:
informazione
ascii 7 bit
parità
trasversale
INFORMAZIONE TRASMESSA
.doc - 1684Kb - 12/set/2008 - 25 / 82
S3Abacus – Architetture/Asm
E
R
R
O
R
E
!
!
1 0 0 0 1
1 0 1 0 0
1 0 1 0 0
1 0 0 1 1
1 0 1 0 0
1 0 0 0 1
0 1 0 0 0
0 1 0 0 0
    
0 0 1 1 1
parità longitudinale
0
1
1
1
1
0
0
0

0
1
0
0
1
0
1
1
1

1








1
1
1
1
1
1
0
0
1
1
1
1
1
1
0
0
0
1
1
1
1
1
1
0
0
0
0
0
0
0
0
0
1
1
0
0
1
1
0
1
0
0
0
1
0
0
0
1
0
0
0
0
1
1
0
0
1
0
1
0
0
1
0
1
1
1
1
0
0
0
0
1
0
0
1*
0
1
1
1
1
 0
Se ad esempio ci fosse un errore di trasmissione nel b0 della lettera “O”, cioè se il ricevente ricevesse 0
invece di 1, facendo i propri conti scoprirebbe che il b0 di parità longitudinale ricevuto sarebbe dovuto
essere 0 (mentre invece è 1) e contemporaneamente il b7 della lettera “O” (parità trasversale) dovrebbe
essere a 0 (invece è a 1).
Il ricevente, scoprendo l'incongruenza di b0 Parità Longitudinale e b7 Parità Trasversale, può dedurre che il
bit errato è proprio il b0 della lettera “O”.
Naturalmente anche in questo caso la parità incrociata fallisce se ci sono errori doppi o se l'errore riguarda proprio le informazioni di parità. Come
prima il ricevitore ha solo certezza quando il controllo di parità incrociata fallisce (ha la certezza dell’errore).
CODICE DI HAMMING
Il c o d i c e d i H a m m i n g consente di aggiungere ad un codice dato, una serie di bit in ogni codifica che consente di rilevare errori singoli e
multipli e, in qualche caso, di autocorreggerli.
Naturalmente non è sempre possibile, anche con la codifica di Hamming, poter rilevare o correggere l’errore. In particolare, data la codifica
originale, se la distanza minima di Hamming è 2, è possibile solo una codifica autorilevante; affinchè la codifica sia anche autocorrettiva, è
necessario che la distanza minima di Hamming tra le configurazioni sia 3.
Il numero di bit supplementari (k) necessari per dotare un codice a n bit con la codifica di Hamming, si calcola ricordando la r e l a z i o n e d i
Hamming:
n ≤ 2k-k-1
dove:
n = numero di bit del codice dato;
k = numero di bit da aggiungere ad ogni codifica per ottenere il nuovo codice rilevatore e correttore;
In questo modo, a partire da un codice a n bit se ne otterrà uno a (n + k) bit.
Le fasi per la costruzione del codice sono:
1. Creare una codifica valida con distanza di Hamming opportuna (esempio: 2 o 3) determinando quindi n;
2. Individuazione di k mediante la relazione di Hamming;
3. Scrivere una sequenza vuota di bit di lunghezza j=n+k numerando ogni posizione da sinistra a destra partendo da 1; trascrivere il valore della
posizione riportata anche nella sua forma binaria.
4. Assegnare ai k bit aggiuntivi - chiamati Xi =1...k - le posizioni graduali a potenze di due;
(Es. se k=3; X1 va in posizione 1; X2 va in posizione 2; X3 va in posizione 4); scrivere le descrizioni (o i contenuti) dei bit del codice (detti Bi =1...n)
nelle posizioni lasciate libere dagli Xi;
5. Determinare i valori di controllo per ogni Xi, individuando le posizioni in cui l'unico bit a 1 della posizione binaria di X i è presente nelle rimanenti
posizioni; sommando tutti i bit delle posizioni di controllo (senza riporti) si ottengono i valori per Xi
Per il controllo e la verifica:
7. Individuare k cifre binarie dette Gi =1...k formanti il numero binario G, dove ogni Gi è ottenuto come somma dei bit (senza riporto) di controllo per
Xi compreso se stesso; l'indice i determina la posizione della cifra binaria in G;
8. Se G=0, il codice non contiene errori, se G≠0, esso indica, in binario invertito, la posizione del bit errato.
ESEMPIO
DI CODIFICA DI
HAMMING
Costruire il codice di Hamming per la codifica BCD dei simboli numerici.
fase 1.
n=4; infatti la codifica BCD ha bisogno di soli 4 bit: 0=0000; 1=0001; 2=0010; 3=0011; ... ;9=1001
fase 2.
k=3 (infatti se k = 2, n >22-2-1 cioè 4 > 1, mentre con k = 3, n =23-3-1, cioè 4 = 8-3-1)
fase 3.
Il codice sarà formato da j bit: j = n+k = 4+3 = 7;
posizioni decimali:
1
2
3
4
5
6
7
posizioni binarie: 001 010 011 100 101 110 111
fase 4.
X1 X2 B1 X3 B2 B3 B4
- X1 controlla le posizioni 3,5,7 (X1 è in posizione 001; le altre posizioni che hanno quel bit a 1 sono 011
(pos. 3), 101 (pos. 5), 111 (pos. 7).
.doc - 1684Kb - 12/set/2008 - 26 / 82
S3Abacus – Architetture/Asm
- X2 controlla le posizioni
(pos. 3), 110 (pos. 6), 111
- X3 controlla le posizioni
(pos. 5), 110 (pos. 6), 111
fase 5.
Caso simbolo
1
2
3
X1 X2
0
X1 = 0+0+1 =
X2 = 0+1+1 =
X3 = 0+1+1 =
pertanto:
1
0
0
3,6,7
(pos.
5,6,7
(pos.
(X2 è in posizione 010; le altre posizioni che hanno quel bit a 1 sono 011
7).
(X3 è in posizione 100; le altre posizioni che hanno quel bit a 1 sono 101
7).
3=0011 (B1=0; B2=0; B3=1; B4=1)
4
5
6
7
X3
0
1
1
1;
0;
0;
0
0
1
1
Controllo:
Ipotesi: spedito 3, ricevuto corretto (1000011)
Determino G = G3 G2 G1 (k=3);
G1 = bit pos1+ bit pos3+ bit pos5+ bit pos7 = 1+0+0+1 = 0;
G2 = bit pos2+ bit pos3+ bit pos6+ bit pos7 = 0+0+1+1 = 0;
G3 = bit pos4+ bit pos5+ bit pos6+ bit pos7 = 0+0+1+1 = 0;
G=000; allora risultato corretto.
Ipotesi: spedito 3, ricevuto sbagliato (1010011)
Determino G = G3 G2 G1 (k=3);
G1 = bit pos1+ bit pos3+ bit pos5+ bit pos7 = 1+1+0+1
G2 = bit pos2+ bit pos3+ bit pos6+ bit pos7 = 0+1+1+1
G3 = bit pos4+ bit pos5+ bit pos6+ bit pos7 = 0+0+1+1
G=011; allora risultato scorretto con errore in pos 3
= 1;
= 1;
= 0;
(011=3).
CODICI CICLICI - CRC
Il metodo dei C o d i c i C i c l i c i (C R C , ovvero C y c l i c R e d u n d a n t C o d e s ) consente di aggiungere a una codifica consueta un numero
di bit supplementare i quali sono in grado di rilevare la presenza di eventuali errori.
A differenza del controllo di parità, se il controllo CRC del ricevitore ha esito positivo si ha la certezza della correttezza del dato e, come prima, in
caso di fallimento del controllo CRC si ha ancora la certezza della scorrettezza del dato.
Data quindi una codifica a n bit, se il calcolo del CRC necessita di k bit, si ottiene una codifica totale composta da (n+k) bit.
Di seguito vediamo le fasi per determinare la codifica dotata di CRC.
Se la sequenza da considerare - cioè la codifica, che chiameremo A - contiene n bit:
1. Si considera una nuova sequenza a piacere di k bit (k<n) detta polinomio generatore (G), nel quale il bit più significativo deve essere 1.
2. Si considera una sequenza di k-1 bit tutti a zero detta G’ e una nuova sequenza T’ formata da A giustapposta a G': T'=AG'.
3. Si effettua quindi una divisione tra T' e G in modulo 2 (*) e si ottiene il resto R (con R avente j bit, j≤k);
4. Si costruisce G'' ottenuto come G' OR R.
5. A questo punto si costruisce T come AG''. T è il codice da trasmettere con CRC.
Per il controllo si effettuano le seguenti fasi:
6. Si effettua la divisione in modulo 2 tra T ricevuto e G
7. Se il resto è nullo, il codice ricevuto è corretto; altrimenti contiene un errore.
Naturalmente la scelta dei polinomio generatore, essendo praticamente libera, si è standardizzata su alcuni valori tipici che riportiamo di seguito
(per codifiche complesse formate da un numero molto alto di bit):
CRC_16:
1100000000000101
CRC_CCITT:
1000100000100001
CRC_32:
10000010011000001000111011011001
(*) Le divisioni binarie modulo due sono estremamente più semplici delle divisioni binarie consuete e pertanto è estremamente facile implementarle
in circuiti logici e svolgerle via hardware. Le differenze con la divisione standard sono due:
a. Il divisore sta nel dividendo quando il numero di bit significativi del dividendo è pari al numero di bit del divisore (es. 1000 mod2 1101 = 1 !)
b. La differenza per ottenere il resto si effettua con lo XOR e non con la normale sottrazione.
ESEMPIO
DI CODIFICA
CICLICA
Costruire il CRC per il dato A=1000001 (n=7);
fase 1.
Scelgo G=10001 (k=5)
fase 2.
Allora G'=0000 e quindi T'=AG'=10000010000;
fase 3.
Divisione in modulo 2:
T'
: G
10000010000 : 10001 = 1000101
10001
----10100
S3Abacus – Architetture/Asm
.doc - 1684Kb - 12/set/2008 - 27 / 82
10001
--10100
10001
--101
Individuo quindi R=101
fase 4.
Costruisco G'' = G'
OR R
G'' = 0000 OR 0101 = 0101
fase 5.
Calcolo T con CRC = AG'' = 10000010101
Controllo:
Ipotesi: spedito T come sopra, ricevuto corretto (10000010101)
fase 6.
Effettuo divisione in modulo 2 (T:G):
10000010101 : 10001 = 1000101
10001
----10101
10001
--10001
10001
----Il resto è nullo, perciò ricezione corretta;
Ipotesi: spedito T come sopra, ricevuto scorretto (10000110101)
fase 6.
Effettuo divisione in modulo 2 (T:G):
10000110101 : 10001 = 10001110
10001
----11101
10001
-11000
10001
-10011
10001
---10
Il resto non è nullo (10), perciò la ricezione è scorretta;
CHECKSUM
Spesso, oltre a gestire una codifica che prevede il controllo direttamente sulle singole o su poche configurazioni, è possibile, e di fatto molto diffuso,
corredare un’insieme numeroso di configurazioni con qualche nuova configurazione di puro controllo.
In genere queste configurazioni aggiuntive sono poste al termine della sequenza originale di configurazioni, la quale è detta in gergo f r a m e ; la
parte aggiuntiva invece, che serve solo per il controllo della integrità del frame, è detta C o d a o T a i l o F C S (Frame Control Sequence).
Spesso quindi il frame è corredato da una coda che può contenere ad esempio il CRC calcolato sull’intero frame o, molto più semplicemente, una
configurazione speciale detta C h e c k S u m (letteralmente: somma di controllo).
La configurazione di CheckSum quindi non è altro che la somma - logica o aritmetica o calcolata in altro modo ancora - delle varie codifiche
contenute nel frame.
Il calcolo di checksum più diffuso prevede la somma aritmetica di tutte le configurazioni del frame ottenuta troncando i traboccamenti (rispetto al
contenitore originale delle codifiche del frame). Una sequenza composta da frame+CheckSum costituisce un codice rivelatore di errore, ed è anche
detto controllo dell’errore orientato al byte.
La tecnica del calcolo di CheckSum è utilizzata spesso nelle comunicazioni seriali tra dispositivi e per frame relativamente poco numerosi (< 100
byte). Era usata, inoltre, nella prima versione di protocollo XMODEM.
ESEMPIO
DI
CHECKSUM
DI UN FRAME
Sia dato un frame composto dai seguenti byte (Hex): 01 A3 74 9C
Si noti come il contenitore originale dei dati del frame sia il byte. La sua CheckSum calcolata con somma
aritmetica ed esclusione del traboccamento (rispetto al byte) vale:
CheckSum = 01 + A3 + 74 + 95 = (1)1D = 1D
Allora l’intera sequenza (frame+coda) sarà: 01 A3 74 9C 1D
Se il ricevitore ricevesse una sequenza con il secondo byte errato (es. A4 invece di A3), ricalcolando la
CheckSum per proprio conto e confrontandola con la CheckSum ricevuta, si accorgerebbe che la sequenza
presenta almeno un errore.
S3Abacus – Architetture/Asm
.doc - 1684Kb - 12/set/2008 - 28 / 82
ARCHITETTURE
La storia del calcolatore elettronico ha inizio in epoca moderna, già a partire dalla metà del 1600, in Francia, con B . P a s c a l , che ideò il primo
dispositivo manuale di calcolo aritmetico. Il primo tentativo di calcolatore programmabile fu invece ad opera del britannico C . B a b b a g e , i cui
programmi furono scritti dalla programmatrice Ada Lovelace. Si deve attendere però il ‘900 per avere il primo effettivo calcolatore programmabile a
relè, probabilmente lo Z 1 del costruttore statunitense Zuse (1936). Il più significativo calcolatore elettronico invece fu l’E n i a c degli statunitensi
Eckert-Mauchley (1944), da cui si sviluppò la moderna tecnologia dei calcolatori elettronici, dopo la svolta dell’I a s di J . V o n N e u m a n n
(1952) che introdusse, tra le altre innovazioni, l’uso della numerazione binaria. In seguito, un impulso fondamentale alla tecnologia costruttiva fu
l’invenzione del circuito integrato in silicio da parte del premio Nobel R . N o y c e (1958, poi fondatore di Intel), che aprì la strada ai calcolatori di
t e r z a g e n e r a z i o n e , dopo la generazione delle valvole e quella dei transistor.
La struttura tipica di un calcolatore elettronico assume quindi la forma attuale in base ad almeno due svolte tecnologiche fondamentali, un modello
costruttivo storico riconducibile allo scienziato austriaco J. Von Neumann (a r c h i t e t t u r a d i V o n N e u m a n n ) risalente agli anni ’40/’50 e
l’invenzione del microprocessore da parte del tecnico italiano F . F a g g i n (microprocessore I n t e l 4 0 0 4 ), risalente al 1971.
Dai primi anni ’70 le tecnologie che hanno fatto sviluppare i calcolatori elettronici hanno subito una evoluzione costante e prendono il nome di
Architetture hardware, all’interno delle quali il calcolatore elettronico assume le sue caratteristiche di base.
Attraverso lo schema dell’architettura di Von Neumann vediamo di chiarire i componenti base di un calcolatore elettronico, le loro interazioni e
quindi i principi del funzionamento.
.doc - 1684Kb - 12/set/2008 - 29 / 82
S3Abacus – Architetture/Asm
ARCHITETTURA DI VON NEUMANN
TASSONOMIA DI FLYNN
Tra le varie classificazioni che nel tempo sono state formulate circa la fisionomia dei calcolatori, una è sopravvissuta con successo, data la sua
semplicità e sinteticità (t a s s o n o m i a d i F l y n n , 1972). Essa si basa su due concetti chiave: sequenze di istruzioni e sequenze di dati, a
seconda che il calcolatore sia in grado di gestire solo una sequenza o più sequenze nello stesso tempo.
Si hanno quindi le seguenti tipologie di calcolatori, in base alle combinazioni:
S I S D : Single Instruction, Single Data, il modello originale, che equivale alla macchina di Von Neumann.
S I M D : Single Instruction, Multiple Data, modello in cui alcune singole istruzioni possono computare più sequenze di dati.
M I S D : Multiple Instruction, Single Data, modello senza alcuna implementazione reale
M I M D : Multiple Instruction, Multiple Data, modello in cui più unità di calcolo agiscono su più sequenze di dati nello stesso tempo.
In effetti, per capire la classificazione, bisognerebbe considerare un istante di tempo come unità, e quindi verificare se, in quella unità di tempo
‘congelata’, operano una o più istruzioni su una o più sequenze di dati.
MACCHINA DI VON NEUMANN
Il modello di Von Neumann (m a c c h i n a d i V o n N e u m a n n ), circa la struttura e le funzioni di un calcolatore, mette in chiaro che il
funzionamento della macchina deve basarsi sul concetto di programma da eseguire, ovvero stabilisce con chiarezza le funzioni tipiche del
s o f t w a r e (sw, programmi, cioè sequenze di istruzioni per ottenere risultati) e dell’h a r d w a r e (hw, macchina, cioè unità che esegue il
software e ottiene i risultati). Questo concetto fu mutuato dagli studi teorici del britannico A . T u r i n g (macchina di Turing).
Per avvicinare la parte software a quella hardware, Von Neumann stabilisce anche che il sistema di codifica dell’informazione da usare deve essere
quello su b a s e b i n a r i a , cioè il sw deve essere rappresentato con codice in base due, cosicchè per l’hardware sia più immediato decodificare i
programmi, basandosi sulle differenze di potenziale elettrico a due livelli.
Da un punto di vista funzionale, quindi, Von Neumann propone una struttura di tipo modulare, e non compatta come in passato: diverse unità con
compiti specifici devono interagire tra loro in modo sincronizzato tramite un modulo di collegamento.
Quindi il modello di Von Neumann per il calcolatore prevede le seguenti unità funzionali:
•
P r o c e s s o r e , cioè l’unità di calcolo che esegue le istruzioni del sw
•
M e m o r i a , cioè il contenitore del software e dei dati
•
I n p u t / O u t p u t , cioè i moduli attraverso i quali fornire sw e dati (Input) o raccogliere i risultati (Output)
•
B u s , cioè l’elemento di interconnessione comune delle suddette unità funzionali.
Bisogna ricordare che tale modello è sopravvissuto praticamente intatto fino alla realizzazione di moderni calcolatori. L’ultimo calcolatore che
implementa il modello di Von Neumann in modo completo è l’Intel 80486 (1989). In questo caso la macchina di Von Neumann è una macchina
puramente S I S D .
In seguito il modello fu aggiornato, ma solo relativamente alla scomposizione dei due flussi di programma e dati, che furono gestiti in modo separato
dal modello successivo denominato A r c h i t e t t u r a H a r v a r d . Inoltre i moderni calcolatori contengono istruzioni e unità di calcolo che fanno
pendere il modello SISD di Von neumann verso modelli misti SISD-SIMD, come si vedrà più oltre.
Von Neumann
PROCESSORE
BUS
MEMORIA
I/O
MEMORIA
La Memoria, detta M e m o r i a P r i n c i p a l e , è un contenitore di celle ordinato. Nelle celle di memoria vengono immesse o lette le istruzioni del
software e i dati di Input e di Output. Ogni cella è ampia un b y t e e ogni cella possiede un i n d i r i z z o (address) proprio.
Gli indirizzi delle celle partono da zero e l’indirizzo dell’ultima cella coincide con il numero totale di celle (+1, dato che gli indirizzi partono da zero),
ed è definito s p a z i o d e g l i i n d i r i z z i o s p a z i o d i i n d i r i z z a m e n t o .
L’ampiezza dello spazio di indirizzamento fisico di una Memoria principale è determinato dall’ampiezza del Bus Indirizzi (cfr. Bus).
Il valore nella cella di Memoria viene conservato fintanto che la Memoria è alimentata da corrente, cosicchè la Memoria è un tipo di m e m o r i a
v o l a t i l e , cioè perde i suoi contenuti tutte le volte che la macchina viene spenta.
.doc - 1684Kb - 12/set/2008 - 30 / 82
S3Abacus – Architetture/Asm
Indirizzo: 0
Von Neumann
PROCESSORE
BUS
MEMORIA
23
Indirizzo: 17
Valore:
23
I/O
La Memoria è realizzata in gran parte in tecnologia R a m (Random Access Memory) che, per non perdere il contenuto, deve essere sempre
rinfrescata con un segnale elettrico a frequenza costante per tutto il tempo in cui il sistema è alimentato (r e f r e s h ).
I bit nelle celle di RAM, infatti, sono dei micro condensatori ed è per questo che questo tipo di Ram è nota anche come D R a m (Dynamic Ram).
I contenuti delle celle di DRam vengono aggiornate continuamente durante il funzionamento del calcolatore (con nuovi programmi e dati). Il
processo di lettura e scrittura delle celle di DRam non è immediato ma necessita di un tempo tecnico detto t e m p o d i a c c e s s o .
Il tempo di accesso alla memoria Ram è decisamente alto rispetto ai tempi presenti nelle attività di un calcolatore (es. il tempo dell’esecuzione delle
istruzioni nel processore). Questa situazione è detta c o l l o d i b o t t i g l i a d i V o n N e u m a n n .
All’interno della memoria di sistema deve però trovare posto una speciale area che non perde i valori dopo lo spegnimento. Infatti il sistema, per
potersi avviare (fase di B o o t s t r a p ), deve immettere sul Bus le istruzioni iniziali per configurare i dispositivi di base come video e tastiera (fase
di P O S T o Power On Self Test)) e caricare i programmi del Sistema Operativo da una memoria secondaria come ad es., un disco.
Quest’area è riservata all’interno delo spazio di indirizzamento, pur conservando la struttura tipica di Indirizzo e Cella, ed è denominata,
genericamente, B I O S (Basic Input/Output System).
TECNOLOGIE
Per contrastare il ritardo cronico nella gestione della memoria DRam, si può realizzare la Ram con tecnologia statica S R a m (Static Ram), dove ai
microcondensatori si sostituiscono micro flip-flop. La SRam però è molto costosa e occupa troppo spazio a parità di cella, pertanto il suo uso è
limitato a speciali memorie di transito - tra Memoria e Processore - denominate memorie C a c h e (cfr. Cache).
Le memorie Dram vengono prodotte in banchi di dimensione fissa e montati sulla piastra madre del calcolatore su schede D I M M (Dual In-line
Memory Module) a due facce. La tecnologia più diffusa (2008) è la cosiddetta D D R 2 S D R a m (Double Data Rate Synchronous Dynamic Ram).
Le regioni di Memoria che contengono il BIOS sono realizzate in R o m (Read Only Memory), cioè una tecnologia che consente alle celle di
mantenere il contenuto anche in assenza di alimentazione, cioè a sistema spento. Il codice e i programmi contenuti in maniera non volatile nella
memoria centrale sono detti F i r m w a r e (Fw).
Buona parte della Memoria Rom è realizzata, in realtà, con tecnologia E p r o m o E e p r o m , cioè memorie con tecnologie che, pur mantenendo i
dati nelle celle quando manca l’alimentazione, possono essere riprogrammate (valori memorizzati in C M O S R a m ), cioe’ sostituite con nuovi
valori attraverso procedure speciali.
In questo modo le caratteristiche di avvio e alcune caratteristiche funzionali del sistema possono essere modificate e/o aggiornate tramite la
modifica dei parametri durante il Setup del Bios o la sostituzione effettiva di tutto il Bios tramite speciali procedure di configurazione.
BUS
Il Bus è l’unità di interconnessione tra i moduli del modello di Von Neumann. Esso si presenta come un fascio ordinato di linee, ognuna delle quali
può assumere il significato di un bit, cioè di un valore binario. Si dice che i moduli Processore, memoria e Input/Output si affacciano sul Bus, ovvero
essendovi collegati, possono leggere o modificare i valori presenti sulle linee che lo compogono.
Gran parte dell’attività di un calcolatore elettronico si riduce, infatti, a trasferimenti (di informazione) tra i moduli: trasferimenti che vedono il
Processore come soggetto (M a s t e r ), Memoria e I/O come oggetti (S l a v e ) e il Bus come veicolo: da Processore a Memoria, da Memoria a
Processore, da Processore a I/O e da I/O a Processore (i casi di trasferimento tra I/O e Memoria sono visti come un caso speciale, per ora).
Considerando come soggetto master il Processore, una operazione che trasporta un dato dal Processore alla Memoria (o all’I/O) è detta operazione
di s c r i t t u r a (Write), mentre se il verso è opposto (da Memoria o I/O verso il Processore) l’operazione è detta di l e t t u r a (Read).
Per gestire i trasferimenti il Bus è scomponibile in tre sottoinsiemi ordinati di linee, denominati A d d r e s s B u s (ABus) o Bus Indirizzi, D a t a
B u s (DBus) o Bus dei dati e C o n t r o l B u s (CBus) o Bus di controllo.
Von Neumann
PROCESSORE
BUS
MEMORIA
I/O
DBus
ABus
CBus
In questo modo, se si stabiliscono alcune linee del CBus in modo opportuno, es. prevedendo una linea che specifica la direzione del trasferimento
(Memoria-Processore o I/O-Processore), una che specifica il verso del trasferimento (lettura o scrittura) e una che indica se il trasferimento è
completato, il trasferimento di una certa quantità di informazione (sul DBus) può essere inviato nel posto giusto (all’indirizzo sull’Abus) in modo
completamente sincronizzato (tramite le linee sul Cbus).
.doc - 1684Kb - 12/set/2008 - 31 / 82
S3Abacus – Architetture/Asm
Considerando che il Bus possiede un proprio orologio che ne scadenzia in modo costante le operazioni nel tempo in Mhz (c l o c k d i B u s ), si
usano le seguenti linee di controllo (sul Cbus) per gestire i trasferimenti: I / O - M e m , R / W , W a i t (la convenzione vuole che la sigla sottolineata
indichi valore di bit a 0). La linea Wait (attesa) indica trasferimento completato (1) o trasferimento in corso (0), situazione che mostra il collo di
bottiglia di Von Neumann: i tempi di accesso alla Memoria sono più lunghi dei tempi di elaborazione del Processore.
La quantità delle linee dell’Abus e del DBus, non necessariamente coincidenti, dipendono dalle caratteristiche specifiche del Processore e,
comunque, sono quasi sempre potenze di due (4, 8, 16, 32, 64 linee, anche se in alcuni Processori troviamo 20, 36 o 80). In generale la dimensione
dell’Abus specifica la quantità di memoria raggiungibile dai programmi (spazio di indirizzamento fisico) e si calcola elevando 2 al numero di linee
dell’ABus. La dimensione del Dbus, invece, rappresenta il grado di parallelismo del Processore, ovvero la massima quantità di dati che è in grado di
elaborare in un solo trasferimento di Bus.
ESEMPIO: TRASFERIMENTI
SUL
BUS
Si voglia indicare lo stato del Bus al completamento del trasferimento del valore 23 alla cella di indirizzo 17 della Memoria, su un Bus con 8 linee di
DBus, 8 linee di Abus e 3 linee di controllo.
Indicare la sequenza dei valori binari delle linee del Bus.
Prima di tutto si trasformano i valori decimali in binario, riempiendo i risultati al ‘byte’ (cioè aggiungendo tanti zeri a sinistra fino a ottenere sequenze
di 8 bit).
(23)D = (10111)B = (00010111)B
(valore da trasferire)
(17)D = (10001)B = (00010001)B
(indirizzo di Memoria di destinazione)
I/O-Mem = 0
(indica la direzione Processore-Memoria)
R/W = 0
(indica verso di scrittura)
Wait = 1
(indica operazione completata)
In definitiva:
DBus
76543210
00010111
ABus
76543210
00010001
CBus
R/W
0
I/O-Mem
0
Wait
1
TECNOLOGIE
All’interno della scheda madre (motherboard) di un calcolatore è abbastanza complicato isolare la sezione del bus di sistema.
In effetti l’insieme di circuiti, linee e chip dedicati al bus di sistema viene indicato con il termine complessivo di C h i p S e t , che viene fornito e
montato in base alle specifiche del processore utilizzato.
Il Chipset realizza, tra l’altro, il Bus di sistema tramite l’interconnessione di due aree distinte normalmente denominate N o r t h B r i d g e , (che si
occupa della connessione Processore-Memoria) e S o u t h B r i d g e (dedicato alle connessioni tra Processore e sezione di I/O).
Le tecnologie con cui viene realizzato il Bus di sistema sul classico PC Intel x-86 si sono evolute dallo standard I S A (Dbus a 8 bit e clock a 8,33
MHz), allo standard E I S A (Dbus a 16 bit e clock a 8,33 MHz), allo standard P C I (Peripheral Component Interconnect, Dbus a 32 bit e clock a 33
MHz).
Nel tempo sono comparsi bus interni dedicati per veicolare i dati della scheda video e del processore, a partire dal V E S A fino al più recente A G P .
Allo stato attuale (2008) la tecnologia di bus più diffusa è il P C I a 32 bit, 33MHz e 133MBytes/ e il P C I - X (32-64bit, 66MHz, da 528Mbytes/s a
1035 Mbytes/s).
L’evoluzione più recente in termini di tecnologie di Bus è il P C I E x p r e s s , per ora utilizzato come sostituto di AGP come bus dedicato per le
schede video. Altri tipi di bus hanno avuto alterne fortune commerciali, tra cui Microchannel, lo SCSI e il PMCIIA dedicato ai dispositivi portatili.
INPUT/OUTPUT
La sezione di Input/Output (I/O) di un calcolatore è dedicata all’acquisizione dei dati e programmi, e alla rappresentazione degli stessi in varie
forme, dal video alla stampa a valori memorizzati su M e m o r i e S e c o n d a r i e (tutti i tipi di memorie di massa, dal DVD all’Hard Disk al
PenDrive).
Concettualmente la sezione di I/O è ancora rappresentabile come un contenitore, del tutto analogo alla Memoria, anche se dotato di uno spazio di
indirizzamento (spazio degli indirizzi di I/O) molto più ridotto. Ogni dispositivo periferico, di Input o di Output, possiede un proprio range di indirizzi di
I/O riservato (indirizzi di I/O, detti anche r e g i s t r i d i I / O o porte di I/O) all’interno dello spazio di indirizzamento di I/O.
Qualche volta alcuni dispositivi, magari che necessitano di grandi spazi di I/O, usano indirizzi di Memoria invece di indirizzi di I/O. In questo caso si
parla di dispositivi m a p p a t i i n M e m o r i a .
La sezione di I/O dispone tuttavia di almeno un’altra modalità fondamentale per consentire la gestione delle attività di I/O con le attività generali del
Bus, ovvero linee di sincronizzazione dedicate denominate linee di I n t e r r u z i o n e . Per gestire i segnali di interruzione, sul CBus sono
implementati normalmente due segnali tipici (I N T R e I N T A ) che segnalano, rispettivamente, la richiesta di una interruzione e il suo
completamento. Con un segnale di interruzione, il dispositivo periferico chiede al Processore di sospendere temporaneamente la sua esecuzione
per eseguire una parte di codice che lo riguarda, sottoforma di routine associata a quella interruzione (I S R , Interrupt Service Routine). Questo è
molto importante per quell’I/O denominato a s i n c r o n o , ovvero che può intervenire senza preavviso e in ogni momento (es. il mouse).
In realtà i calcolatori prevedono anche speciali modalità di trasferimento di I/O che evitano di occupare il Bus di sistema e il Processore, veicolando i
valori di I/O direttamente verso (e dalla) Memoria, mediante tecniche denominate D M A (Direct Memory Access) o di B u s M a s t e r i n g . In
questi casi ampie quantità di informazioni vengono spostate sul Bus senza impegnare il Processore.
La circuiteria dedicata ad affacciarsi sul Bus di sistema, a rendere disponibili i propri indirizzi di I/O, a sincronizzarsi con i trasferimenti tramite il
CBus e a condividere le proprie linee dedicate di Interruzione e/o DMA, è detta S c h e d a C o n t r o l l e r del dispositivo.
Molti dispositivi di I/O sono i n t e r n i al calcolatore, cioè hanno la scheda controller integrata nel ChipSet della scheda madre (es. Tastiera,
scheda di rete e scheda video). Anche speciali elementi di I/O dedicati al controllo dei trasferimenti di I/O sono integrati nel ChipSet, come ad
esempio il dispositivo di controllo delle Interruzioni (Interrupt Controller) e di gestione del DMA (DMA Controller).
Con l’introduzione della tecnologia P C I per il Bus di sistema (che gestisce anche i collegamenti con l’I/O tramite il SouthBridge) viene a risolversi
l’annosa questione della esatta distribuzione dei registri dedicati di I/O e della gestione dedicata delle linee di sicronizzazione dell’I/O (es,. le linee di
.doc - 1684Kb - 12/set/2008 - 32 / 82
S3Abacus – Architetture/Asm
interruzione e i canali DMA). Infatti, essendo tutti indirizzi, linee e canali privati e dedicati, essi devono essere ben separati e distribuiti tra le diverse
schede controller installate sul sistema.
Prima del bus PCI, infatti, ogni scheda controller doveva essere configurata ‘a mano’ (tramite jumper modificabili sulla scheda controller) per
specificarne gli indirizzi di I/O, il n. di interruzione e il canale DMA in modo coerente con eventuali altre schede controller presenti sul sistema, per
evitare conflitti.
Attualmente il bus PCI consente di utilizzare una tecnica denominata P l u g & P l a y per la quale BIOS, Sistema Operativo e FW residente sulla
scheda controller stabiliscono, all’avvio del calcolatore (o nel momento dell’installazione ‘a caldo’ di un dispositivo), indirizzi, canali e linee
completamente separati per ogni scheda controller presente sul sistema, eliminando la possibilità di conflitti e evitando operazioni di configurazione
manuale da parte dell’utente.
In definitiva un trasferimento di I/O avviene, sul Bus, con le stesse modalità di un trasferimento standard, avendo cura di segnalarne correttamente
la direzione sulla linea IO/Mem del CBus.
TECNOLOGIE
Altri elementi di I/O o periferiche, quelli opzionali o comunque gestibili come componenti dall’utente, sono detti e s t e r n i e si connettono al Bus
tramite sistemi standard che vengono resi disponibili attraverso appositi connettori.
Tra questi ricordiamo le connessioni dirette al Bus di sistema tramite i cosiddetti S l o t (s l o t P C I o s l o t I S A - E I S A ), sui quali l’utente può
connettere direttamente (ma a calcolatore spento) una scheda controller di I/O (es. una seconda scheda di rete o schede di I/O dedicate).
Praticamente sempre disponibili sono le connessioni I D E - E I D E per Hard Disk (conosciute anche con il nome A T A - A T A 2 ) e/o dispositivi DVD
(che utilizzano lo standard A T A P I su IDE-EIDE).
Le connessioni di questo tipo constano di due connettori IDE-EIDE su piastra madre (primario e secondario) a cui collegare due dispositivi (un
Master e uno Slave selezionabili tramite jumper sulle periferiche) a scelta tra Hard Disk e dispositivi DVD
In alternativa, questi dispositivi possono avvalersi di una più recente tecnologia di connessione, di tipo S A T A (Serial ATA), per Hard Disk di nuova
generazione collegabili anche a caldo.
Tra gli standard di connessione più diffusi ricordiamo lo standard U S B , il F i r e w i r e (o IEEE 1394) e l’E t h e r n e t 8 0 2 . x .
In ribasso altri standard storici di I/O tipo porte seriali Rs232, parallele Centronics e seriali PS/2, progressivamente implementate tramite standard
più moderni (tipicamente convertitori USB).
USB e Firewire sono modi di connessione dell’I/O molto utilizzati, sia per l’ampia larghezza di banda (480 Mbit/s per USB 2.0, 400 Mbit/s per
Firewire), sia per la possibilità di installare ‘a caldo’ i dispositivi che se ne servono. Inoltre forniscono anche una certa quantità di corrente sullo
stesso cavo dati, così da rendere molti dispositivi del tutto autonomi anche dall’alimentazione. In entrambi i casi USB e Firewire consentono
connessioni multiple in catena, tramite Hub nel caso di USB, direttamente in modo passante per Firewire.
PROCESSORE
Un Processore è un singolo circuito integrato in grado di effettuare operazioni decisionali, di calcolo o di elaborazione dell'informazione; il
microprocessore principale di un computer viene chiamato processore o C P U (Central Processor Unit).
Il Processore può essere visto come suddiviso in tre unità funzionali, l’U n i t à d i C o n t r o l l o (UC), l’area dei R e g i s t r i , l’A L U (Unità
Aritmetico-Logica).
L’UC si affaccia sul Bus, lo arbitra impostando i valori sulle linee Abus, Dbus e CBus, legge il Dbus e il CBus, legge dalla Memoria (e dall’I/O) i dati
o li aggiorna in Memoria (o nell’I/O) dopo aver compiuto operazioni.
I Registri contengono i dati letti dall’UC sul Bus per predisporli all’esecuzione delle istruzioni che avverranno nell’ALU; oppure contengono i risultati
delle operazioni compiute dall’ALU in attesa di essere passasti all’UC e quindi sul Bus.
L’ALU è l’unità di esecuzione effettiva del Processore, all’interno della quale si trovano m i c r o p r o g r a m m i cablati direttamente in hardware,
scritti nel cosiddetto m i c r o c o d i c e con relative m i c r o i s t r u z i o n i .
Ogni processore viene progettato con un set di istruzioni specifico denominato I S A (I n s t r u c t i o n S e t A r c h i t e c t u r e o
I n s t r u c t i o n S e t ), in corrispondenza di ognuna delle quali è implementato un preciso microprogramma in ALU.
Ogni istruzione dell’ISA è contraddistinta da un numero specifico, denominato O p e r a t i o n C o d e (Op. Code) e ogni istruzione dotata di Op.
Code necessita di un numero preciso e definito di parametri che, assieme all’Op.Code, determinano la l u n g h e z z a d e l l ’ i s t r u z i o n e (in
byte).
Un registro speciale del Processore, detto P r o g r a m C o u n t e r (PC), si incrementa della lunghezza dell’istruzione appena eseguita.
Il Processore tipico quindi agisce secondo una rigida sequenza di passi che si ripetono fino all’arresto della macchina:
F e t c h : l’UC pone sull’Abus il valore del registro Program Counter, imposta lettura da Memoria e carica l’op.code ivi memorizzato
Decode:
l’UC, a partire dall’Op.Code appena letto, determina la lunghezza dell’istruzione, cioè la quantità di parametri di cui necessita
quindi attiva una fase intermedia di caricamento degli operandi (O p e r a n d F e t c h ) che si trovano necessariamente e in modo
ordinato agli indirizzi adiacenti a quello dell’Op.Code. Gli operandi caricati andranno a depositarsi nei Registri.
Execute:
viene avviato il microprogramma relativo all’Op.Code attuale, che usa i propri parametri correttamente memorizzati nei registri.
La frequenza in base alla quale vengono eseguiti i microprogrammi è regolata dal c l o c k d i C P U (frequenza del Microprocessore).
S t o r e : al termine della fase di Execute gli eventuali risultati, posti nei Registri, vengono scritti sul Bus dall’UC, o verso la Memoria, o verso l’I/O.
PROCESSORE
Registri
ALU
BUS
MEMORIA
U
C
I/O
Il ciclo del Processore qui descritto termina, in effetti, consultando il segnale INTR per capire se il controller di una periferica ha richiesto una
interruzione, ovvero la sospensione temporanea dell’esecuzione per servire il codice associato alla interruzione pendente.
S3Abacus – Architetture/Asm
.doc - 1684Kb - 12/set/2008 - 33 / 82
Ogni singola istruzione dell’ISA di un Processore è contraddistinta da un proprio Op.Code, una determinata lunghezza (in base al numero di
operandi che utilizza) e un preciso numero di cicli di Bus per il suo completamento (compresi tra il Fetch, il Decode e lo Store). Il tempo di effettiva
esecuzione del microprogramma influisce relativamente sulla durata dell’istruzione, essendo il clock di CPU di almeno un ordine di grandezza
superiore al clock di Bus.
Questa considerazione riassume il cosiddetto collo di bottiglia dell’architetura CISC (cfr. Cisc, Risc, Crisc), causato da una fase di Decode molto
lunga e onerosa a causa della quantità di istruzioni dell’ISA e della loro complessità e varietà in termini di numero di operandi.
Ovviamente lo schema è semplificato. Ad esempio un microprocessore reale contiene, oltre all’ALU, una F P U (Float Point Unit) per i calcoli in
virgola mobile e varie unità di calcolo per istruzioni complesse (es. per calcoli vettoriali), ma il modello di riferimento rimane piuttosto simile.
CISC, RISC, CRISC
Il modello di processore appena descritto contiene microprogrammi e microcodice, cioè si dice che è un microprocessore ‘a interprete’. In altre
parole, ogni istruzione dell’ISA deve essere decodificata (Decode), quindi dotata degli operandi che richiede (Operand Fetch) e, infine, avviata alla
fase di esecuzione (Execute) che richiede l’avvio di uno specifico microprogramma. Tutto questo significa che ogni singola istruzione di un’ISA del
genere ha un d a t a p a t h a più cicli. Il data path è il percorso dei dati all’interno del Processore, attraverso l’attuale istruzione, e i suoi cicli sono
scanditi dal clock della CPU.
Questi tipi di ISA sono denominate C I S C (Complex Instruction Set Code). Le architetture CISC possiedono quindi un set di istruzioni molto ampio,
istruzioni di grandezza variabile e molto specifiche con corrispondente fase di Decode complessa, un data path a più passi. Si tratta di architetture
che facilitano la portabilità del sw, dato che l’insieme dei microprogrammi (o interprete del processore) può essere trasportato su processori più
recenti, e quindi sono processori adatti per essere programmati anche in A s s e m b l y (cfr. Assembly x-86)
Al contrario, una architettura R I S C (Reducted Instruction Set Code), possiede un data path a singolo passo. Il set di istruzioni di una architettura
RISC è limitato, contiene istruzioni di lunghezza costante (con un numero di operandi fisso), con fase di Decode breve e senza microprogrammi da
eseguire nel processore: ogni istruzione è eseguita direttamente in hardware con pochi cicli di clock. In questo modo una elaborazione RISC appare
nettamente più veloce (almeno di un ordine 10). In sostanza una istruzione CISC - con molti passi nel data path - equivale a numerose istruzioni
RISC con data path singolo. Per questo i programmi per ISA RISC sono molto più lunghi di un analogo programma per ISA CISC. Tutto ciò implica
maggiori difficoltà per la portabilità del software e maggiore complessità dei compilatori. I calcolatori RISC non sono, infatti, programmabili in
Assembler, causa la mancanza di istruzioni ISA di alto livello.
Nessuna delle due architetture è ideale.
Piuttosto la tendenza attuale è l’implementazione di processori su base CISC - come descrive il modello di Von Neumann, dotati di sottosistemi
interni basati su RISC, soprattutto dedicati alla computazione delle istruzioni semplici (e più comuni) dell’ISA adottata. In questo caso si parla di
architetture C R I S C (Complex-Reducted Instruction Set Code).
Il modello SISD/CISC dell’architettura di Von Neumann si scontra con un paio di problemi che ne limitano, strutturalmente, la performance.
Una singola istruzione su singolo dato eseguita nell’unità di tempo è un limite oramai inaccettabile. Molte delle soluzioni per incrementare le
prestazioni di un calcolatore tendono a parallelizzare l’esecuzione.
Questo è il limite di una architettura SISD.
Così come il cronico ritardo con cui si accede alla Memoria (clock del Bus, sull’ordine dei MHz), rispetto alla velocità di esecuzione del Processore
(clock della CPU, sull’ordine dei GHz), penalizza enormemente il modello CISC con una fase di Decode troppo lenta e complessa. Molte delle
soluzioni per incrementare le prestazioni di un calcolatore tendono a fornire istruzioni e operandi dalla Memoria alla stessa velocità che impiega il
Processore per eseguirle.
Questo è il limite di una architettura CISC.
CACHE
Un modo ingegnoso per diminuire gli accessi al Bus e alla Memoria, e quindi di superare i limiti di una architettura CISC, è quello di dotare il
calcolatore di una memoria ‘tampone’ (C a c h e M e m o r y ) tra il Processore e il Bus.
Man mano che il Processore legge dalla Memoria, ad un determinato indirizzo, molte locazioni di Memoria con indirizzi prossimi a quello, vengono
spostati nella memoria cache (nello stesso tempo di Bus). Questi gruppi di valori che vengono portati nella cache sono detti l i n e e di cache.
Il motivo è che, secondo il principio della l o c a l i t à s p a z i o - t e m p o r a l e dei programmi, un programma utilizza, entro un breve intervallo di
tempo, solo una piccola parte del suo spazio degli indirizzi, una parte composta da indirizzi numericamente vicini tra di loro. Cosicchè la memoria
cache (SRam), che è molto più veloce di una Memoria DRam, può fornire, nell’immediato futuro (es., la prossima istruzione da eseguire), i valori
senza dover accedere al Bus.
Quindi, ad ogni operazione di lettura (del Processore dalla Memoria), l’informazione viene cercata prima di tutto nella cache; se è presente (h i t ),
non è necessario accedere al Bus; se non è presente (m i s s ) si accede alla Memoria e, oltre all’informazione richiesta, si carica una nuova linea in
cache, sovrascrivendo la linea di cache meno usata di recente.
Nei calcolatori sono montate almeno tre tipi di cache (livelli): Livello 1, all’interno del processore, Livello 2, collegato al Processore, Livello 3 sulla
piastra madre.
PREFETCH, PIPELINE, SUPERSCALARITÀ
Un modo per aumentare il parallelismo d’esecuzione, e quindi di superare i limiti di una architettura SISD, fu quello di caricare nel Processore più
istruzioni oltre a quella richiesta. Fin dagli esordi, ad esempio, i Processori erano dotati di una c o d a d i P r e f e t c h , ovvero di un buffer interno
in cui il Processore memorizzava i successivi 6 o 8 byte consecutivi a quello appena letto dalla Memoria. In questo modo, con un solo accesso alla
Memoria, si aveva a disposizione una serie di valori che potevano essere usati successivamente (come istruzioni o operandi) senza dover accedere
di nuovo al Bus.
Ben presto, alla coda di Prefetch, fu affiancato un sistema a P i p e l i n e che ha lo scopo di una catena di montaggio: invece di eseguire una
istruzione completamente e, solo al termine, la successiva, si puo’ avviare la successiva subito dopo che la precedente è stata inserita nel data
path. Per esempio, basta che la prima istruzione si trovi in fase di Decode, e la successiva può essere posta in stato di Fetch. Così come in una
catena di montaggio, un nuovo pezzo può essere lavorato anche se il precedente non è stato completato: basta che le fasi (dette anche S t a d i
della pipeline) non si sovrappongano. Così, una Pipeline a 5 stadi trasporta cinque istruzioni in catena di montaggio.
La Pipeline sopperisce così alle attese di CPU veloci nei confronti di Memorie lente (cfr. collo di bottiglia di Von Neumann in Macchina di Von
Neumann)
S3Abacus – Architetture/Asm
.doc - 1684Kb - 12/set/2008 - 34 / 82
Una volta dotato di Pipeline, in un Processore si è notato che lo stadio di esecuzione è il più lento: lo stadio precedente fornisce più valori di quanto
lo stadio di esecuzione, implementato nell’ALU, può elaborare. Ecco allora che sui Processori sono montate più ALU in modo da servire
velocemente ogni istruzione che arriva allo stadio di Execute. In questo caso il Processore è detto S u p e r s c a l a r e .
In questo modo è possibile dotare i Processori anche di due o quattro pipeline differenti.
Tutte queste metodologie, però, vengono vanificate da due ovvie situazioni:
a.
b.
Istruzioni di salto
Dipendenza dei dati tra le istruzioni
Nel primo caso la pipeline viene del tutto persa se l’istruzione corrente è un salto (b r a n c h ) distante dall’istruzione successiva che è attualmente
in pipeline. In questo caso viene persa anche la cache.
Nel secondo caso una pipeline deve essere interrotta se l’istruzione successiva necessita, come operando, del risultato finale dell’istruzione
precedente. Es. Istruzione1: A=B+C; Istruzione 2: D=A+1. La pipeline che sta servendo l’Istruzione 2 deve interrompersi allo stadio di Operand
Fetch, dato che A non è disponibile se non quando l’Istruzione 1 non è del tutto terminata.
ESECUZIONE PREDICATIVA E SPECULATIVA
L’e s e c u z i o n e p r e d i c a t i v a , implementata in moduli del Processore denominati u n i t à d i p r e v i s i o n e d e i s a l t i (Dynamic
Branch Prediction), cerca di prevenire la perdita delle pipeline a causa delle istruzioni di salto. In questo caso le unità cercano, con vari algoritmi che
usano tabelle simili a memorie cache, di capire se una istruzione di salto avverrà o meno (cosa del tutto non deterministica ma solo statistica o
‘storica’, dato che il fatto avviene solo a runtime). Ad esempio, alcuni criteri considerano sempre ‘presi’ (t a k e n ) i salti all’indietro, tipici dei cicli (in
un ciclo il salto avviene molto più spesso all’indietro). Il problema di questa tecnica, che in realtà è molto efficiente, si ha quando la previsione è
sbagliata: le istruzioni eseguite inutilmente devono essere gettate e lo stato della macchina ripristinato.
L’esecuzione predicativa è anche nota come e s e c u z i o n e s p e c u l a t i v a , intendendosi quella elaborazione che computa anche codice che
potrebbe non essere mai utilizzato.
ESECUZIONE FUORI ORDINE E VLIW
Con l’esecuzione fuori ordine (o u t o f o r d e r e x e c u t i o n ) si cerca di prevenire lo svuotamento delle pipeline a causa di dipendenze tra le
istruzioni. Quando il Processore individua una dipendenza in una pipeline, invece ‘di buttarla’ e attendere l’operando mancante, la salta e prosegue
con istruzioni “future” che, in teoria, dovrebbero essere eseguite solo DOPO quella interrotta. In questo caso la pipeline viene quasi del tutto
conservata (un solo stadio rimane bloccato, fino all’arrivo dell’operando mancante) ma, una volta risolta la dipendenza, saranno già state eseguite
altre istruzioni (quelle che avevamo chiamato istruzioni “future”), con conseguente risalita delle prestazioni.
Naturalmente le istruzioni “future” possono essere eseguite solo se non hanno, a loro volta, dipendenze con istruzioni in corso. Non appena le
istruzioni con dipendenze terminano, il Processore continuerà l’esecuzione in ordine (i n o r d e r e x e c u t i o n ).
La condizione più critica per questa tecnica si presenta quando il Processore deve essere interrotto a causa di un interrupt; se il Processore si trova
in fase di fuori ordine, lo stato del sistema potrebbe non essere coerente. In questi casi il Processore deve ripristinare lo stato della CPU ritirando ‘in
ordine’ tutte le fasi fuori ordine.
Gran parte del lavoro delle unità che gestiscono l’esecuzione fuori ordine è dovuta all’individuazione delle dipendenze nelle istruzioni. La
dipendenza classica e più complicata (R A W , Read After Write) è proprio quella descritta nell’esempio precedente (Istruzione1: A=B+C; Istruzione
2: D=A+1): l’Istruzione 2 contiene una dipendenza RAW.
Per poter riordinare il giusto flusso di esecuzione dopo aver saltato e ricalcolato una istruzione con dipendenza, i Processori utilizzano una serie di
registri d’appoggio (interni e invisibili al programmatore) su cui memorizzare i calcoli temporanei delle istruzioni fuori ordine. All’atto del
riordinamento, per evitare di spostare i valori dai registri interni a quelli effettivamente usati nel data path, i processori sono in grado di rinominare i
registri interni nei nomi dei registri effettivi, risparmiando il tempo del trasferimento (r e g i s t e r r e n a m i n g ).
Anche se non esplicitamente, tutte queste innovazioni (pipeline, superscalarità, predicazione, esecuzione fuori ordine) cercano di implementare un
modello di esecuzione parallelo molto studiato nei centri di calcolo, e denominato V L I W (Very Long Instruction Word). In questo modello, oltre alla
parallelizzazione dell’esecuzione ottenuta in harwdware, si presuppone che lo stesso codice esecutivo generato dai compilatori sia pre-cucinato per
essere parallelizzato ottimamente dalle CPU.
S3Abacus – Architetture/Asm
.doc - 1684Kb - 12/set/2008 - 35 / 82
ARCHITETTURE INTEL
X-86
Con x-86 si intende l’architettura di microprocessori inizialmente sviluppata dall’azienda Intel negli anni ’70, e che è ancora oggi (2008)
predominante sul mercato mondiale. Altre importanti aziende producono calcolatori basati su questa tecnologia, i cui diritti sono stati a suo tempo
venduti, ad esempio AMD.
Il nome x-86 deriva dal primo microprocessore della serie, Intel 8086 (1976), a cui sono seguite numerose versioni via via più potenti che hanno
mantenuto il suffisso nel nome: 8088 (1979), 80186 (1980), 80286 (1982), 80386 (1986), 80486 (1989).
Sono da considerarsi macchine x-86 anche i modelli successivi all’80486, che hanno dovuto rinunciare al nome ‘numerico’ per l’impossibilità di
brevettarlo: Pentium (o P5, 1993), Pentium Pro (o P6, 1995), Pentium II (1997), Pentium III (1999), Pentium 4 (o P7, 2000), Pentium M (2003),
Pentium D (2005), Core 2 Duo (o P8, 2006).
Tutta questa famiglia di microprocessori condividono una qualità comune che ne ha decretato il successo, ma anche la notevole complessità
progettuale: sono tutti r e t r o c o m p a t i b i l i e, in particolare, tutti ancora in grado di eseguire le istruzioni originali dell’ISA primitiva del
progenitore, l’Intel 8086, benchè esso fosse una macchina a 16 bit, mentre le ultime citate sono tutte a 32 bit e, in parte, a 64 bit.
Tutti questi modelli sono sostanzialmente macchine CISC/SISD, benchè dal Pentium in avanti si sia cercato di aumentare progressivamente il
grado di parallelismo dell’esecuzione (cfr. Prefetch, Pipeline, Superscalar, Esecuzione Predicativa e Speculativa, Esecuzione Fuori ordine).
INTEL 8086
L'Intel 8086 (consideriamo l’Intel 8088 come del tutto equivalente, anche se il bus dati è a 8 bit), è un m i c r o p r o c e s s o r e a 1 6 b i t
(ampiezza dei registri e del Dbus) con 2 0 l i n e e s u l l ’ A b u s per un totale di 1Mbyte di spazio di indirizzamento fisico (220 = 1048756 celle).
L'unità di interfaccia con il bus o Unità di Controllo è denominata B I U (Bus Interface Unit), e passa le istruzioni all'ALU (detta E U da Execution
Unit) attraverso una coda di prefetch di 6 byte (4 nell’8088), implementando una sorta di rudimentale meccanismo di Pipeline.
L'Intel 8086 possiede 1 4 r e g i s t r i da 16 bit, di cui quattro registri per uso generico (A X , B X , C X , D X ), a cui si può accedere anche come se
fossero otto registri a 8 bit (AH e AL, BH e BL, CH e CL, DH e DL), due registri indice per indirizzare in memoria (S I , D I ) e due registri dedicati alla
gestione dello stack (B P e S P ).
A questi si aggiungono altri quattro registri detti d i s e g m e n t o (C S , E S , D S e E S ), dedicati specificatamente all’indirizzamento della
Memoria. Completano il set di registri l’Instruction Pointer I P e il registro PSW (Program Status Word), denominato F l a g r e g i s t e r .
Lo spazio degli indirizzi di I/O si avvale di un indirizzamento a 16 bit, per un totale di 64KByte (216 = 65536) registri di Input/Output disponibili.
Completa la sezione di I/O un set di 8 l i n e e d i i n t e r r u z i o n e hardware (poi ampliato a 16) e un canale DMA per dispositivi di I/O con
ampio traffico. La frequenza originale del clock di CPU valeva 4,77 MHz.
Specifiche soluzioni adottate da questo microprocessore sono l’uso della S e g m e n t a z i o n e d e l l a M e m o r i a e l’utilizzo in comune delle
sedici linee del Dbus con altrettante linee dell’Abus (M u l t i p l e x i n g B u s ), cosicchè è necessario un segnale speciale nel CBus (M/IO) che
segnali se su quelle linee è presente attualmente un dato o (parte di) un indirizzo. La memorizzazione delle parole di byte in Memoria avviene in
modalità l i t t l e e n d i a n (il byte meno significativo all’indirizzo più basso), modo caratteristico di tutte le macchine Intel.
Le istruzioni dell’ISA 8086, lunghe da 1 a 4 byte, prevedono spesso l’uso implicito di alcuni registri, complicando le operazioni di salvataggio
temporaneo e rendendo il processore cronicamente povero di registri disponibili.
La ridotta quantità di registri dell’8086 – così come nelle macchine successive fino al Pentium – ha designato l’instruction Set dell’8086 come ISA “a
due indirizzi”, ovvero come architettura che ha continuamente bisogno di depositare dati temporanei in memoria, invece che su registri
supplementari. I molti accessi alla Memoria, come abbiamo visto, determinano una forte penalizzazione delle prestazioni.
REGISTRI
I quattro registri di uso generale, pur utilizzati frequentemente come registri di memorizzazione temporanea (a 16 o a 8 bit), sono dedicati a precisi
compiti e sono coinvolti implicitamente in numerose istruzioni dell’Instruction Set x-86.
AX, o registro A c c u m u l a t o r e , è predisposto per le istruzioni aritmetiche (somme, sottrazioni, moltiplicazioni e divisioni).
BX, o registro B a s e , è l’unico dei registri di uso generale che può specificare un indirizzo di memoria (gli altri sono DI, SI e BP)
CX, o registro C o n t a t o r e , è utilizzato implicitamente nelle istruzioni di conteggio dei cicli o di operazioni che esigono una numerazione.
DX, o registro di I / O è l’unico registro dell’8086 che consente di indirizzare le porte di I/O. Usato anche in moltiplicazioni e divisioni.
I due registri indice, pur utilizzabili come registri di memorizzazione temporanea (ma solo a 16 bit), sono usati implicitamente nelle istruzioni
dedicate alla manipolazione di array di caratteri (stringhe).
SI, o registro I n d i c e S o r g e n t e , specifica l’indirizzo da cui leggere l’array
DI, o registro I n d i c e D e s t i n a z i o n e , specifica l’indirizzo in cui scrivere l’array.
I due registri dedicati allo stack sono in grado di indirizzare in memoria, anche se non liberamente.
BP, o B a s e P o i n t e r , contiene l’indirizzo di partenza della pila di stack, per poter gestire il passaggio dei parametri delle procedure
SP, o S t a c k p o i n t e r , contiene sempre l’indirizzo di memoria dell’ultimo elemento sullo stack.
Infine, i due registri IP e Flag, sono registri non modificabili esplicitamente dato che il Processore li mantiene aggiornati automaticamente.
IP, o I n s t r u c t i o n P o i n t e r , contiene la parte meno significativa dell’indirizzo della prossima istruzione da eseguire. Il programmatore non lo
modifica mai.
Flag, o r e g i s t r o d e i F l a g s , è l’unico registro intepretato a singolo bit, ove ogni bit ha un significato differente e concorre a descrivere lo
stato attuale del Processore dopo l’esecuzione dell’ultima istruzione.
S3Abacus – Architetture/Asm
.doc - 1684Kb - 12/set/2008 - 36 / 82
INDIRIZZAMENTO
Ai dieci registri precedenti nell’Intel 8086 bisogna aggiungere 4 registri di segmento (CS, DS, ES, SS) che hanno sempre il compito di contenere i
16 bit più significativi di un qualsiasi indirizzo di Memoria. Infatti l’Abus è a 20 linee, mentre i registri solo a 16 bit, per mantenere la compatibilità con
un modello di microprocessore precedente, l’Intel 8080. Cosicchè un solo registro è insufficiente per rappresentare un indirizzo completo, ne
servono due, accoppiati, per formare un cosiddetto I n d i r i z z o S e g m e n t a t o .
L’indirizzo segmentato è quindi formato da due parti, e si indica con le notazioni (Seg:Spz) o (Seg:Ofs), ove la parte più significativa (Seg) è
detta parte S e g m e n t o dell’indirizzo, mentre la parte meno significativa (Spz o Ofs) è detta parte S p i a z z a m e n t o (o O f f s e t )
dell’indirizzo.
La regola per ottenere un i n d i r i z z o L i n e a r e (F l a t , cioè la sequenza di 20 bit da collocare sull’Abus) da un indirizzo segmentato, è la
seguente:
Indirizzo Lineare = Seg * 10h + Spz
Si deve tenere presente che moltiplicare per 10h un numero esadecimale significa solo aggiungere uno zero (esadecimale) a destra, cosicchè un
indirizzo segmentato A100h:023Dh si trasforma in lineare velocemente:
Indirizzo Lineare = A100h * 10h + 23Dh = A1000h + 23Dh = A123Dh (= 660029d)
Si noti che un qualsisi indirizzo lineare può essere rappresentato da più coppie di indirizzi segmentati, dato che non tutti i 32 bit dell’indirizzo
segmentato sono utilizzati.
Ad esempio i due indirizzi segmentati A100h:023Dh e A110h:013Dh rappresentano lo stesso indirizzo (lineare) A123Dh.
Inoltre, molti indirizzi segmentati sono illeciti, cioè rappresentano valori superiori all’indirizzo lineare massimo (FFFFFh = 220-1 = 1048575d).
Ad esempio F31Ah:E100h, che risulta essere l’indirizzo lineare 1012A0h (> FFFFFh, cioè 1012A0h = 1053344d > 1048575d)
I registri di segmento sono spesso impliciti, ovvero, stabiliti gli accoppiamenti di default con i registri di indirizzamento standard, non è necessario
indicarli espressamente nelle istruzioni dell’x-86.
CS, o C o d e S e g m e n t , contiene sempre la parte più significativa del Program Counter, ed è associato a IP (CS:IP = Program Counter dell’x86)
DS, o D a t a S e g m e n t , contiene sempre la parte più significativa della memoria dei dati. E’ associato a BX, SI, DI
ES, o E x t r a S e g m e n t , contiene sempre la parte più significativa di una seconda memoria dati. E’ associato a BX, SI, DI
SS, o S t a c k S e g m e n t , contiene sempre la parte più significativa della memoria in cui è allocato lo Stack. E’ associato a BP e a SP.
IA-32
Con I A - 3 2 (Intel Architecture 32 bit) si indica il modello di architettura e l’Instruction Set dei Processori Intel a partire dal 1986, anno di
commercializzazione del microprocessore Intel 80386, il primo microprocessore Intel a 32 bit. Questa architettura si è mantenuta presente sulle
macchine Intel fino ad oggi (2008), seppur con numerose varianti introdotte per aumentare le prestazioni.
La terminologia IA-32 si contrappone all’IA-64 (cfr. IA-64), l’architettura a 64 bit che dovrebbe modificare radicalmente la struttura dei calcolatori
Intel introducendo in modo nativo il calcolo parallelo e abbandonando il modello CISC/SISD originale dell’IA-32.
In realtà anche l’IA-32, nelle sue evoluzioni, si è sempre più avvicinata ad un modello SIMD, già a partire dallo stesso 80386, che fu il primo
microprocessore Intel a montare regolarmente una F P U (Floating Point Unit, coprocessore matematico), un parziale ma effettivo elemento SIMD.
Successivamente l’IA-32 si arricchisce di vere e proprie istruzioni SIMD denominate M M X (MultiMedia eXtension, 1996) e S S E (Streaming SIMD
Extensions, 2000)
E’ con l’introduzione dell’IA-32 – nello specifico con l’80386, che Intel perde il monopolio dell’architettura x-86 (anche se il primo 8086 fu clonato su
licenza da NEC già nei primi anni ’80) a favore, dapprima, del produttore statunitense A M D (1991).
Naturalmente tutte le macchine con architettura IA-32 sono ancora compatibili con l’architettura x-86 a 16 bit dei modelli precedenti all’Intel 80386.
Le specifiche caratteristiche dell’IA-32 rispetto alla classica x-86 a 16 bit si hanno soprattutto nel nuovo spazio di indirizzamento fisico a 32bit per un
totale di 4 G b y t e di locazioni (232 = 4294967296) e la gestione completa di un modello di m e m o r i a v i r t u a l e e p r o t e t t a .
La gestione della memoria virtuale, infatti, consente a più programmi contemporaneamente di usare tutto lo spazio di indirizzamento a prescindere
da quanta memoria fisica sià effettivamente installata. Ciò significa la possibilità di far funzionare i programmi in m u l t i t a s k i n g (cioè più
programmi contemporaneamente), potendo usare uno spazio lineare (e non più segmentato) molto ampio.
La gestione della memoria protetta, infine, garantisce che i programmi non interferiscano tra di loro né con il Sistema Operativo. La protezione della
memoria e dell’I/O, quindi, garantisce anche che i dispositivi non possano essere gestiti con codice errato, tramite livelli di esecuzione detti
p r i v i l e g i . Tutto ciò ha reso sostanzialmente più stabile l’esecuzione delle applicazioni, superando l’annoso problema dei frequenti blocchi di
sistema sulle macchine x-86.
Per questi motivi l’IA-32 può essere avviata o usata dai programmi in tre modalità di microprocessore differenti: m o d a l i t à p r o t e t t a (la più
evoluta, con indirizzamento lineare), m o d a l i t à r e a l e (come un 8086 segmentato), m o d a l i t à v i r t u a l e 8 0 8 6 (emulazione 8086
segmentata, ma con protezione).
In altri termini i programmi possono essere eseguiti, anche contemporaneamente, in modalità protetta o virtuale 8086. In questo modo il blocco di
un programma non arresta il sistema. Se invece il processore viene avviato in modalità reale, esso si comporta solamente ed esattamente come un
grosso Intel 8086 (se avviene un blocco sul programma, il sistema si arresta) e non può più cambiare modalità.
I registri dell’IA-32 sono sostanzialmente gli stessi della x-86 a 16, ma tutti a 32 bit. Solo due registri di segmento sono stati aggiunti, per un totale di
16 registri a 32 bit.
Per analogia, ogni nuovo registro eredita il nome del vecchio, ma con un prefisso E (Extended): E A X , E B X , E C X , E D X , E S I , E D I , E S P ,
EBP, EIP, EFlags, ECS, EDS, EES, ESS, EFS, EGS.
Il significato e le funzioni dei registri rimangono immutate, considerando che i 16 bit meno significativi dei registri di uso generale assumono lo
stesso nome della vecchia architettura (AX, BX, CX, DX) e, a loro volta, si scompongono nei soliti 8 registri a 8 bit (AH, AL, BH, BL, CH, CL, DH,
DL).
S3Abacus – Architetture/Asm
.doc - 1684Kb - 12/set/2008 - 37 / 82
NETBURST, EM64T
Con N e t b u r s t si indica una tecnologia che mira ad aumentare le prestazioni dei calcolatori agendo sull’aumento progressivo della frequenza di
clock del processore (clock di CPU) agendo sulla continua miniaturizzazione dei circuiti. Per questo motivo i processori equipaggiati con questa
tecnologia posseggono ALU che girano a frequenza doppia di quella della CPU. Aumentando la velocità interna alla CPU, si possono implementare
anche pipeline molto più ampie, aumentando quindi il numero di istruzioni eseguite nell’unità di tempo. Netburst ha infatti introdotto pipeline a 20
stadi, contro i dieci dei microprocessori precedenti. Naturalmente il fallimento della pipeline diventa una evenienza critica, per questa tecnologia. Se
la predizione di un salto è errata o se le istruzioni contengono molte dipendenze, la pipeline si svuota e le prestazioni crollano. Per prevenire queste
eventualità Netburst ha elevato la complessità dell’unità di predizione dei salti (cfr. Esecuzione Predicativa e Speculativa) e aggiunto una cache di
livello 2 in grado di conservare le microistruzioni, così da non dover accedere alla memoria in caso di ricostruzione della pipeline.
L’E M 6 4 T (Extended Memory 64 Technology) è una tecnologia che viene utilizzata nei processori IA-32 per poter sfruttare alcuni benefici di
calcolo eseguiti a 64 bit, all’interno di processori a 32 bit.
Tramite una speciale modalità (6 4 b i t M o d e ) in cui può operare il processore, modalità impostabile solo da programmi appositamente scritti in
tal senso, diviene disponibile un indirizzamento lineare a 64 bit, per uno spazio degli indirizzi a disposizione di tali programmi, di 17.179.869.184
GByte o 16 ExaByte (264 = 18446744073709551616) e si possono utilizzare 8 nuovi registri a 64 bit che diminuiscono gli accessi alla memoria per
salvataggi temporanei. Naturalmente il processore dotato di EM64T può operare anche in modo classico IA-32, nella modalità C o m p a t i b i l y
Mode.
MULTICORE
Secondo la strada indicata dalla tecnologia Netburst, si sarebbero dovute ottenere frequenze al limite dei 10GHz. Tecnologicamente, però, tali
frequenze non sono state raggiunte né sembra lo saranno a breve; per ora (2008) le frequenze di clock massime non hanno raggiunto la metà di
quella previsione. A frequenze così alte, infatti si crea una enorme instabilità sui chip e la potenza richiesta pone seri problemi di dissipazione del
calore all’interno dei circuiti.
L’orientamento attuale, denominato M u l t i C o r e , si basa sulla progressiva implementazione di calcolo parallelo, ottenuto inizialmente
progettando Processori con due (o più) unità complete di calcolo, ognuna basata su.CPU che operano a frequenze più basse di una Netburst.
Questo tipo di architettura, al pari dei sistemi semplicemente dual core e, più generalmente, di tutti i sistemi biprocessore e multiprocessore,
consente di aumentare la potenza di calcolo senza aumentare la frequenza di lavoro, a tutto vantaggio del minore calore dissipato. Se con la
tecnologia Netburst il grado di parallelismo raggiunto era di tre istruzioni per ciclo di clock (usando grandi pipeline), un processore multicore arriva a
4 istruzioni per ciclo di clock per ogni core implementato, anche se le frequnze di clock interno sono normalmente inferiori. La tecnologia parallela di
un sistema Multicore è detta W i d e D y n a m i c E x e c u t i o n e usa una pipeline con meno stadi (14 invece dei 31 raggiunti dalla Netburst),
risultando meno vulnerabile allo svuotamento della pipeline e operando a frequenze minori di clock con abbassamento della potenza richiesta e del
calore sviluppati.
In teoria un processore MultiCore assume le caratteristiche di una macchina MIMD, dato che i singoli processori operano in modo indipendente,
condividendo solo la c a c h e d i l i v e l l o 2 per evitare di accedere continuamente al Bus.
Per sfruttare i Core multipli però è necessario che i programmi siano scritti in modo adeguato, ovvero contengano istruzioni dedicate all’esecuzione
parallela, con la granularità del processo o del sottoprocesso (t h r e a d ). Infatti un programma non multithread sarà eseguito da un singolo
processore, senza alcun aumento delle prestazioni.
In ogni caso tutti i sistemi operativi mulltiprogrammati (multitasking) godono di maggiore efficienza su un sistema MultiCore dato che i processi sono
distribuiti su più unità di calcolo (Core) invece di condividerne uno solo.
Il notevole risparmio in potenza e di calore da dissipare - a causa dell’abbassamento delle frequenze di clock, rende le tecnologie MultiCore
estrememente idonee per i calcolatori portatili.
IA-64
Con I A - 6 4 (Intel Architecture 64 bit) si intende una famiglia completamente nuova di architettura per microprocessore, contrapposta a IA-32 e, più
generalmente, radicalmente differente dall’architettura x-86.
Tale progetto è stato affrontato già da molti anni ma ha da sempre incontrato notevoli resistenze per imporsi sul mercato dato che, per forza di
cose, esso perde completamente la retrocompatibilità con i programmi scritti per l’x-86, cioè quasi tutti i programmi disponibili sul mercato a tutt’oggi
(2008).
Il primo processore IA-64 completamente a 64 bit è datato 2001 e conosciuto come Intel I t a n i u m . Nel frattempo l’azienda ha continuato a
sviluppare la vecchia tecnologia IA-32 cercando di spremergli ogni possibilità di evoluzione e prestazione fino agli attuali sistemi MultiCore.
I vantaggi di una tecnologia a 64 bit suppliscono agli svantaggi della tecnologia a 32 bit, prima di tutto alla struttura fondamentalmente CISC dell’IA32, con la presenza di molte istruzioni di lunghezze differenti e formati diversi che rallentano inevitabilmente la fase di Decode.
Inoltre il Set dell’IA-32 è zeppo di istruzioni che fanno riferimento alla memoria (ISA a due indirizzi) mentre sono più efficienti le ISA a tre indirizzi (o
ISA load/store), che accedono alla memoria solo per caricare gli operandi e memorizzare i risultati.
Un altro limite strutturale è la cronica povertà di registri dell’IA-32 che costringe i compilatori a spostare in memoria molti risultati temporanei, con
conseguente abbassamento delle prestazioni.
Infine la complessità delle istruzioni dell’IA-32 comporta la presenza di molte dipendenze WAR (cfr. Esecuzione Fuori ordine e VLIW), complicando
notevolmente l’uso delle pipeline e la loro profondita’ (numerosi stadi). E molti stadi rendono le pipeline vulnerabili ai salti, ottenendo perciò un
sistema continuamente in bilico con le prestazioni.
Una architettura a 64 bit, poi, consente uno spazio di indirizzamento di una grandezza veramente notevole (264 = 18446744073709551616 celle), in
linea (e forse molto di più) con l’ampiezza del software richiesto dai sistemi operativi moderni.
La struttura dell’IA-64 è quindi quella di una I S A l o a d / s t o r e , con 64 registri a 64 bit. Tutte le istruzioni dell’IA-64 hanno lo stesso formato, in
modo da ottenere una architettura fondamentalmente RISC. Tutti i registri sono dotati di un meccanismo di finestra (register window) che riesce a
srotolare efficacemente i cicli (l o o p ), cioè a esplicitare un ciclo di istruzioni eseguite più volte elencandole tutte per esteso, il che in genere porta a
un miglioramento delle prestazioni.
L’innovazione determinante di IA-64 è l’adozione di E P I C (Explicitly Parallel Instruction Computing), ovvero una tecnica che sposta l’analisi del
flusso delle istruzioni di un programma a livello del compilatore in modo tale che in CPU arrivino sequenze di istruzioni (f a s c i o b u n d l e ) già
pronte per una elaborazione parallela. Anche l’analisi della predizione dei salti è preconfezionata a livello della compilazione, pertanto un IA-64
ottiene previsioni di salto corrette nel 98% dei casi. Si può dire che EPIC è la implementazione completa e specifica di VLIW già adottata a suo
tempo su IA-32.
Da notare che le prime versioni di macchine con IA-64 sono a singolo Core, malgrado la presenza di una tecnologia esplicitamente orientata al
calcolo parallelo. Il calcolo parallelo, infatti, si ottiene con la superscalarità e le pipeline ottimizzate da EPIC.
.doc - 1684Kb - 12/set/2008 - 38 / 82
S3Abacus – Architetture/Asm
Naturalmente, a causa della sua natura completamente rinnovata e dipendente dalla preanalisi del codice da parte del compilatore, l’architettura IA64 deve rinunciare a quasi tutto il software di sistema (Sistemi Operativi) e utente (programmi applicativi) disponibile per IA-32, e questo
rappresenta un notevole impedimento alla sua diffusione.
TABELLA RIASSUNTIVA ARCHITETTURE INTEL (2008)
Nome
anno
architettura
8086
80286
80386
80486
Pentium
Pentium Pro
Pentium II
Pentium III
Pentium 4
Itanium
Pentium D
Core 2 Duo
1979
1982
1986
1989
1993
1996
1997
1999
2000
2001
2005
2006
x-86
x-86
IA-32
IA-32
IA-32
IA-32
IA-32
IA-32
IA-32
IA-64
IA-32
IA-32
PIN
n.
20
68
68
168
296
387
242
370
468
610
775
775
Transistor
n.
29.000
134.000
275.000
1.200.000
3.100.000
5.500.000
7.500.000
9.500.000
42.000.000
2.000.000.000
125.000.000
291.000.000
Clock
CPU
MHz
4
12
33
133
300
200
300
1200
3400
1600
3600
3000
Clock
BUS
MHz
10
10
33
50
66
66
100
133
400
533
400
533
Abus
linee
20
24
32
32
32
36
36
36
36
64
36
36
DBus
linee
16
16
32
32
64
64
64
64
64
64
64
64
Core
n.
1
1
1
1
1
1
1
1
1
1/2
2/4
2
Stadi
pipeline
n.
5
10
20
20
31
10
10
10
Cache
livelli
L1
L1
L1-L2
L1-L2
L1-L2
L1-L2-L3
L1-L2-L3
L1-L2-L3
L1-L2-L3
Volt
v
5
5
5
5
3,3
3,3
2
2
1,5
1,4
1,4
1
.doc - 1684Kb - 12/set/2008 - 39 / 82
S3Abacus – Architetture/Asm
ASSEMBLY X-86
ISTRUZIONI X-86
Le istruzioni x-86 utilizzabili per creare programmi per questa architettura sono le seguenti (in evidenza le più comuni):
Codice
Mnemoni
co
Op.
Code
Cicli
Descrizione
AAA
37
4
Regolazione ASCII dopo l'addizione (per aritmetica BCD)
AAD
D5 0A
19
Regolazione ASCII prima della divisione (per aritmetica BCD)
AAM
D4 0A
17
Regolazione ASCII dopo la moltiplicazione (per aritmetica BCD)
AAS
3F
4
Regolazione ASCII dopo la sottrazione (per aritmetica BCD)
ADC
*
2-7
Somma con carry.
ADD
*
2-7
Somma.
AND
*
2-7
ARPL
63
21
BOUND
62
10
BSF
0F BC
*
Esegue l'AND logico bit a bit fra i due operandi, di cui uno può essere implicitamente il registro AL/AX/EAX.
Regolazione del campo RPL del selettore di segmento. Si usa nei sistemi operativi, per assicurarsi che un
programma non chiami una subroutine che abbia un privilegio superiore a quello del programma stesso.
Controlla che l'operando sia entro determinati limiti. Serve ad evitare di indirizzare per errore zone al di fuori di un
array: di solito, per motivi di efficienza, si usa soltanto nelle versioni di debug di un programma.
Scansione in avanti dei bit dell'operando.
BSR
0F BD
*
Scansione all'indietro dei bit dell'operando.
BT
*
3-12
Test del bit specificato dell'operando
BTC
*
6-13
Test del bit specificato dell'operando e sua negazione
BTR
*
6-13
Test del bit specificato dell'operando e sua impostazione a 0
BTS
*
6-13
Test del bit specificato dell'operando e sua impostazione a 1
CALL
*
7-98+
CBW
98
3
Conversione da byte a word
CDQ
99
2
Conversione da doubleword a quadword
CLC
F8
2
Azzeramento del flag di Carry
CLD
FC
2
Azzeramento del flag di Direzione
CLI
FA
3
Azzeramento del flag di Interrupt
CLTS
0F 06
5
Azzeramento del flag di cambio task (TS) nel registro speciale CR0
CMC
F5
2
Negazione del flag di Carry
CMP
*
2-6
CMPS*
*
10
CWD
99
2
Confronto fra due operandi
Confronto fra due stringhe di memoria i cui indirizzi relativi sono memorizzati nei registri indice SI (o ESI) e DI (o
EDI): entrambi i registri vengono decrementati di uno. A seconda se si devono considerare byte, word o
doubleword sono disponibili le varianti CMPS, CMPSB, CMPSW e CMPSD. Molto spesso questa istruzione viene usata
con prefissi REP* in modo da confrontare automaticamente intere zone di memoria.
Conversione da word a doubleword
CWDE
98
2
Conversione da word a doubleword
DAA
27
4
Regolazione decimale dopo l'addizione (per aritmetica BCD)
DAS
2F
4
Regolazione decimale dopo la sottrazione (per aritmetica BCD)
DEC
*
2-6
Chiamata di procedura o subroutine
Decrementa di uno l'operando specificato
.doc - 1684Kb - 12/set/2008 - 40 / 82
S3Abacus – Architetture/Asm
Codice
Mnemoni
co
Op.
Code
Cicli
Descrizione
Divisione senza segno. DIV (divisore). Se divisore 8bit, il dividendo è in AX. In AL il quoziente, in AH il resto.
DIV
*
38-41
ENTER
*
10-
HLT
F4
IDIV
*
19-43
Creazione dello stack frame necessario per le chiamate di procedura dei linguaggi ad alto livello.
Ferma il processore. Dopo un HLT non vengono eseguite nuove istruzioni finché non si verifica un interrupt o un
reset: in caso di interrupt, dopo la routine di servizio il processore riprende l'esecuzione dall'istruzione successiva
alla HLT. Di solito si usa questa istruzione a fini di sincronizzazione o di risparmio energetico.
Divisione con segno.
IMUL
*
9-41
Moltiplicazione con segno
IN
*
12+
Lettura di un byte o di una word dalla porta di I/O specificata nell'operando.
INC
*
2-6
INS*
*
15-29
Incrementa l'operando di uno.
Lettura di un byte o di una word dalla porta di I/O specificata nella stringa specificata dal registro indice DI (o EDI).
Si usa spesso con prefissi REP* per leggere automaticamente interi vettori di dati.
INT
*
33-119
Interrompe l'esecuzione corrente ed esegue la subroutine di interrupt specificata dall'operando.
INTO
CE
59-119
Interrompe l'esecuzione corrente ed esegue la subroutine di interrupt dedicata agli overflow. È sinonimo di INT 4.
IRET*
CF
22-82
Ritorno da una subroutine di interrupt. Come IRETD
J*
*
7+
JMP
*
7-49+
LAHF
9F
2
LAR
0F 02
16
LEA
8D
2
LEAVE
C9
4
LGDT
0F
01 /2
11
LIDT
0F
01 /3
*
11
L*S
5
7-25
Salto condizionato. Il salto all'indirizzo specificato viene eseguito solo se determinati flag hanno un determinato
valore: altrimenti l'esecuzione continua normalmente con l'istruzione successiva. Esistono numerosi tipi di salti
condizionati: JZ,JE,JNE,JG,JL,JGE,JLE, ecc…
Salto incondizionato. L'esecuzione del programma continua a partire dalla locazione indicata dall'argomento del
salto: se l'argomento non dovesse puntare ad una istruzione valida, viene generata una eccezione e il programma
si ferma.
Copia il registro dei flag nel registro AH
Carica il byte dei diritti di accesso nel descrittore di segmento. Questa istruzione serve ad impostare i privilegi di
un determinato segmento: è una istruzione privilegiata e viene usata solo dal sistema operativo.
Caricamento dell'offset dell'indirizzo effettivo.
Uscita da una procedura di un linguaggio ad alto livello: è l'istruzione simmetrica di ENTER e provvede a
distruggere lo stack frame della procedura terminata.
Caricamento del registro della tabella dei descrittori globali dei segmenti: questa istruzione è usata soltanto dai
sistemi operativi, un programma utente non ha nessun motivo di usarla.
Caricamento del registro della tabella degli interrupt. Questa operazione viene fatta una volta per tutte all'avvio
dal sistema operativo.
Caricamento di un puntatore completo segmento: offset. Le varie forme dell'istruzione (LGS, LFS, LDS, LES, LSS)
specificano quale registro di segmento conterrà la parte segmento del puntatore.
Caricamento del registro della tabella del descrittore locale. Come tutte le istruzioni sui descrittori di segmento,
anche questa è usata solo dai sistemi operativi.
LLDT
0F
00 /2
20
LMSW
10-13
LODS*
0F
01 /6
*
LOOP*
*
LSL
0F 03
LTR
0F
00 /3
MOV
*
2-4
MOVS*
*
7
MOVSX
0F BE
3-6
Copia il secondo operando nel primo.
Copia il valore corrente in una certa posizione di una stringa nella corrispondente posizione della seconda. Si usa
spesso con prefissi REP*.
Copia il secondo operando nel primo e ne estende il segno.
MOVZX
*
3-6
Copia il secondo operando nel primo e azzera il resto de primo operando.
MUL
*
9-41
Moltiplicazione senza segno di AL o AX
NEG
*
2-6
Negazione dell'operando in complemento a due
NOP
90
NOT
*
2-6
Negazione logica dell'operando
OR
*
2-7
Or logico inclusivo di due operandi.
OUT
*
10-25
Scrittura di un byte o di una word nella porta di I/O specificata dall'operando.
OUTS*
*
8-28
Scrittura di un byte o di una word di una stringa nella porta di I/O specificata dall'operando.
POP*
*
5-24
PUSH*
*
2-18
RCL
*
9-10
5
11+
20-26
3
Caricamento della parola di stato della macchina (Machine Status Word)
Caricamento di un operando stringa. L'operando puntato dal registro SI (o ESI) viene caricato in AL/AX/EAX, a
seconda di quale versione dell'istruzione viene usata (LODS, LODSB, LODSW, LODSD)
Salto condizionato in base al valore del registro CX/ECX. Dopo il salto, CX/ECX viene decrementato di uno: quando
il registro è zero, il salto non viene più eseguito.
Carica il limite del segmento nel relativo descrittore, specificato nell'operando. È di esclusivo uso del sistema
operativo.
Carica il registro del task con il registro o locazione di memoria specificata dall'operando. Anche questa istruzione
è privilegiata e usata soltanto dai sistemi operativi.
Nessuna operazione. Sinonimo di XCHG AX, AX (vedi).
Caricamento dallo stack di alcuni registri. Il valore del puntatore alla cima dello stack, lo Stack Pointer SP, viene
decrementato di tante unità quanti byte sono stati letti.
Scrittura nello stack di alcuni registri. Il valore del puntatore alla cima dello stack, lo Stack Pointer SP, viene
decrementato di tante unità quanti byte sono stati scritti.
Rotazione a sinistra dell'operando con carry: tutti i bit dell'operando vengono spostati di una posizione a sinistra e
in quella rimasta libera viene copiato il valore del flag di carry, che assume il valore del bit uscito da destra.
.doc - 1684Kb - 12/set/2008 - 41 / 82
S3Abacus – Architetture/Asm
Codice
Mnemoni
co
Op.
Code
Cicli
Descrizione
Rotazione a destra dell'operando con carry: tutti i bit dell'operando vengono spostati di una posizione a destra e in
quella rimasta libera viene copiato il valore del flag di carry, che assume il valore del bit uscito da sinistra.
RCR
*
9-10
RET
*
10-68
ROL
*
3-7
ROR
*
3-7
SAHF
9E
SAL
*
3-7
SAR
*
3-7
SBB
*
2-7
SCAS*
*
7
SET*
*
4-5
SGDT
SHL
0F
01 /0
*
3-7
SHLD
*
3-7
SHR
*
3-7
SIDT
0F
01 /1
*
3-7
SHRD
STC
0F
00 /0
0F
01 /4
F9
STD
SLDT
SMSW
3
9
9
2
2-3
Ritorno da una subroutine o da una procedura a basso livello.
Rotazione a sinistra dell'operando: tutti i bit dell'operando vengono spostati di una posizione a sinistra e quello
uscito all'estrema sinistra viene copiato nella posizione liberatasi a destra.
Rotazione a destra dell'operando: tutti i bit dell'operando vengono spostati di una posizione a destra e quello
uscito all'estrema destra viene copiato nella posizione liberatasi a sinistra.
Scrittura del contenuto di AH nel registro dei flag.
Spostamento dei bit dell'operando N volte a sinistra: i bit fuoriusciti da sinistra vengono persi. Se nessun bit viene
perso, questa operazione equivale ad una moltiplicazione per 2N.
Spostamento dei bit dell'operando N volte a destra: i bit fuoriusciti da destra vengono persi. Questa operazione
equivale ad una divisione per 2N senza resto.
Sottrazione intera con riporto.
Confronto di stringhe. Le posizioni di memoria puntate dai registri SI e DI (o ESI ed EDI) vengono confrontate e i
due registri incrementati/decrementati di uno a seconda del valore del flag D. Questa istruzione si usa spesso con
prefissi REP*.
Impostazione del byte in base alla condizione specificata. In modo analogo alle istruzioni J*, se i valori dei flag sono
quelli imposti dalla particolare versione di SET* usata, nel byte operando viene scritto il valore 1.
Memorizzazione della tabella del descrittore globale. Ad esclusivo uso dei sistemi operativi.
Spostamento a sinistra dei bit dell'operando: il bit uscito da sinistra è perso. Se era zero, l'operazione equivale ad
una moltiplicazione per 2.
Spostamento a sinistra dei bit dell'operando in doppia precisione. Come SHL, ma coinvolge anche un secondo
registro, concatenato al primo.
Spostamento a destra dei bit dell'operando: il bit uscito da destra è perso. L'operazione equivale ad una divisione
per 2 senza resto.
Memorizzazione della tabella degli interrupt in modalità protetta. Ad esclusivo uso dei sistemi operativi.
Spostamento a destra dei bit dell'operando in doppia precisione. Come SHR, ma coinvolge anche un secondo
registro, concatenato al primo.
Carica il registro della tabella del descrittore locale. Usata soltanto nei sistemi operativi.
Memorizzazione della parola di stato della macchina (Program Status Word, detto anche registro Flags)
2
Imposta a uno il flag di Carry
FD
2
Imposta a uno il flag di Direzione
STI
FB
3
Imposta a uno il flag di Interrupt
STOS*
*
4
STR
SUB
0F
00 /1
*
TEST
*
VERR
WAIT
0F
00 /4
0F
00 /5
9B
XCHG
*
XLAT
D7
XOR
*
VERW
23-27
Memorizza il valore di AL/AX/EAX nella posizione di una stringa puntata da DI (o EDI). Dopodiché il valore di (E)DI
viene incrementato/decrementato a seconda del valore del flag D.
Memorizza il registro dei task. Utile solo ai sistemi operativi.
10-11
Sottrazione intera.
Confronto logico non distruttivo di due operandi. Viene eseguito l'AND logico fra i due, ma il risultato non viene
memorizzato: vengono modificati soltanto i flag.
Verifica di accesso in lettura di un segmento: se sì, il flag Zero viene posto a 1, altrimenti viene azzerato.
15-16
Verifica di accesso in scrittura di un segmento: se sì, il flag Zero viene posto a 1, altrimenti viene azzerato.
2-7
2-5
6
3-5
5
2-7
Il processore si ferma finché il segnale esterno BUSY# (proveniente dal coprocessore matematico) non si disattiva:
si usa per sincronizzare i calcoli del coprocessore con quelli della CPU principale.
Scambia i valori dei due operandi.
Come XLATB. Trasformazione con tabella di consultazione. Il valore corrente di AL viene sostituito con quello nella
cella di memoria della tabella puntata da DS:BX + AL stesso.
OR logico esclusivo fra due operandi.
Questa tabella si riferisce, in realtà, all’Instruction Set dell’Intel 80386 dato che sostanzialmente coincide con quella dell’8086 e che la maggior parte
dei programmi in Assembler, oggi, si eseguono in ambienti che simulano l’80386 (cfr. modalità reale e virtuale 8086 in IA-32).
REGISTRO FLAGS (PSW)
In ogni Instruction Set prima di cominciare a programmare, è fondamentale conoscere il registro P S W (Program Status Word) che descrive lo stato
della CPU al termine di ogni singola istruzione eseguita.
Nell’x-86 tale registro è chiamato F l a g s e il suo contenuto è interpretato a livello di singolo bit. Naturalmente si tratta di un registro che non è
modificabile esplicitamente dal programmatore (non è operando di nessuna istruzione), ma solo implicitamente tramite istruzioni che lo modificano
nel modo voluto dal programmatore. I 16 bit del registro di Flag non sono tutti utilizzati. Le sigle di quelli significativi sono le seguenti:
C P A Z S T I D O , dove ogni flag indica una precisa condizione desunta dal risultato dell’ultima istruzione eseguita (CPAZSO) oppure una modalità
impostabile dal programmatore (TID):
C, flag di C a r r y , a 1 segnala l’avvenuto riporto (carry) o prestito (borrow) di una somma o di una sottrazione tra numeri naturali (senza segno)
P, flag di P a r i t à , a 1 segnala che il risultato ha parità pari (cfr. Parità), a 0, parità dispari
A, flag A u s i l i a r i o , come il flag di Carry, ma relativo a un nibble (solo i quattro bit meno significativi degli operandi)
Z, flag di Z e r o , a 1 segnala che il risultato è zero. Fondamentale per sapere se due operandi sono uguali (es. tramite una sottrazione)
S, flag di S e g n o , a 1 segnala che il risultato ha segno negativo, nella rappresentazione in complento a due (cfr. Numeri con segno).
T, flag di T r a p , impostato a 1 obbliga il processore a eseguire le istruzioni passando il controllo all’utente dopo ogni singola esecuzione (debug)
I, flag di I n t e r r u z i o n e , impostato a 1 abilita le linee di interruzione hw (cfr. Input/Output), impostato a zero le disabilita.
.doc - 1684Kb - 12/set/2008 - 42 / 82
S3Abacus – Architetture/Asm
D, flag di D i r e z i o n e , impostato a 1 indica la direzione del trasferimento inverso (da un indirizzo alto a uno basso) nelle istruzioni stringa.
O, flag di O v e r f l o w , come il flag di Carry, ma su numeri interi (con segno)
I Flag T, I e D si impostano a 1 e a 0 con le corrispondenti istruzioni: STT, CLT, STI, CLI, STD, CLD.
SINTASSI E INDIRIZZAMENTI
L’ISA x-86 è una ISA “a d u e i n d i r i z z i ”, ovvero usa una sintassi in cui ogni istruzione possiede al più due operandi.
Questo implica che una normale operazione aritmetica, ad esempio, deve possedere il terzo operando (il risultato) in modo implicito, cioè
predeterminato dall’istruzione stessa. Detti Destinazione e Sorgente i due operandi di una istruzione, il primo operando (Destinazione) conterrà
anche il risultato dell’istruzione.
La sintassi generale di una istruzione x-86 è la seguente:
2 indirizzi:
Op.Code Destinazione, Sorgente
1 indirizzo:
Op.Code Destinazione
0 indirizzi:
Op.Code
1. Sorgente e Destinazione possono essere un numero costante (i m m ), un Registro (r e g ) o una locazione di Memoria (m e m ), ma mai,
contemporaneamente, due locazioni di Memoria.
2. Una costante (imm) non può mai essere una Destinazione.
3. Sorgente e Destinazione devono essere c o n c o r d i , ovvero devono avere la stessa dimensione in bit.
4. Per indicare una locazione di Memoria (mem) si pone l’indirizzo tra p a r e n t e s i q u a d r e (es. [0] è la cella di indirizzo zero)
5. Se la dimensione del trasferimento non è deducibile dall’istruzione, bisogna specificarla con le parole riservate b y t e p t r , w o r d p t r ,
ecc…
6. I numeri costanti (imm) devono essere prefissati con uno 0 se espressi in esadecimale. Per esprimere in esadecimale o in binario i numeri
costanti, si usano rispettivamente i suffissi h e b .
Sorgenti e Destinazioni delle istruzioni possono essere specificati in determinati modi previsti dall’ISA x-86, modi comunemente denominati
indirizzamenti.
Per elencare le modalità degli indirizzamenti previsti dall’x-86 si usa l’istruzione di gran lunga più utilizzata dai programmi, l’istruzione che attiva un
trasferimento, l’istruzione M O V , che trasferisce un byte o una word:
Istruzione MOV
Sintassi:
MOV
Scopo:
Il contenuto di src (sorgente) viene trasferito in dest (destinazione).
dest, src
Indirizzamento i m m e d i a t o :
MOV reg, imm
Esempi:
MOV AL,2
MOV CX,0Ah
MOV DL,10101010b
Nota:
L’operando imm viene automaticamente convertito nel contenitore Destinazione, cosicchè il valore Ah del secondo caso riempie anche il
byte più significativo di CX con uno zero.
Indirizzamento r e g i s t r o : MOV reg, reg
Esempi:
MOV AX, BX
MOV AL, BL
Nota:
I due registri operandi devono avere la stessa dimensione.
Indirizzamento d i r e t t o : MOV mem, reg
MOV reg, mem
MOV mem, imm
Esempi:
MOV [102], AL
MOV CX, [106]
MOV byte ptr [200], 3
Nota:
Uno dei due operandi deve sempre specificare la dimensione del trasferimento. Nel primo caso vengono spostati 8 bit, nel secondo 16
bit, prelevati dall’indirizzo 106 e 107, nel terzo caso va specificata la dimensione del trasferimento.
Indirizzamento i n d i r e t t o :MOV mem, reg
MOV reg, mem
MOV mem, imm
Esempi:
MOV [BX], AL
MOV AX, [DI+3]
MOV ES:[BX], 23
Nota:
All’interno delle parentesi quadre si possono solo usare registri indice: BX, SI, DI, BP, corrispettivamente associati ai registri di segmento
DS, DS, DS, SS. Nel terzo caso, volendo usare un altro registro di segmento, bisogna specificarlo espressamente
(o v e r r i d e di segmento).
X-86 E MSDOS
Per scrivere programmi in Assembler x-86 è necessario decidere su quale Sistema Operativo operare per avere a disposizione tutta la serie di A P I
(Application Program Interface) che consentono di accedere correttamente all’hardware di Input/Output della macchina, come ad esempio stampare
caratteri sullo schermo o acquisire valori da tastiera.
Senza le API di un Sistema Operativo a disposizione, la programmazione in Assembly rimane abbastanza frustrante, non potendo fornire dati a run
time né poter visualizzare i risultati delle elaborazioni. Inoltre le API di un Sistema Operativo consentono di usare correttamente e velocemente tutti
gli altri dispositivi di I/O necessari per un programma applicativo standard, a partire dalla gestione dei dischi e relativa gestione dei files.
Storicamente l’x-86 è stata distribuita con il Sistema Operativo MsDos o equivalente (PcDos e DrDos); inoltre anche i modelli più recenti che
implementano l’IA-32 prevedono l’esecuzione di codice x-86 in particolari modalità del processore (modalità Reale e Virtuale86), comunemente
attraverso quelle che, sotto i sistemi Operativi Windows, sono note come S h e l l d i M s D o s (attraverso il comando C m d ).
Ciò non esclude il fatto che sia proficua anche la programmazione in Assembly x-86 sotto Sistemi Operativi differenti, come ad esempio Linux
(tramite la s i n t a s s i A T & T ) o Windows-Win32 (denominata W i n A s m ).
.doc - 1684Kb - 12/set/2008 - 43 / 82
S3Abacus – Architetture/Asm
MEMORIA
M s D o s è stato progettato per l’Intel 8086, pertanto vede una memoria a 20 bit, per un totale di 220 = 1048576 locazioni di memoria numerate da 0
a 1048575 (o da 00000h a FFFFFh). Per la particolare gestione della memoria utilizzata, gli indirizzi sono spesso rappresentati con la notazione
segmentata (Seg:Ofs). Qualora la parte spiazzamento (Ofs) di un indirizzo segmentato valga zero, esso si dice i n d i r i z z o d i p a r a g r a f o .
MsDos è un Sistema Operativo (SO) m o n o p r o g r a m m a t o , o monotask, e m o n o u t e n t e ; può mandare in esecuzione un solo
programma alla volta.
La Memoria principale è suddivisa in tre aree tipiche, la M e m o r i a d i S i s t e m a , la M e m o r i a C o n v e n z i o n a l e , la M e m o r i a
Riservata.
Nella memoria di Sistema, molto piccola, vengono salvate all’avvio alcune strutture dati fondamentali per il SO, come l’area dei vettori di Interrupt (o
I D T , Interrupt Descriptor Table).
Nella memoria Convenzionale, la più ampia, viene conservato il k e r n e l di MsDos e il programma applicativo dell’utente.
Nella memoria Riservata viene allocata una porzione di Firmware (B I O S ) e, in generale, viene allocato l’I/O mappato in memoria (cfr.
Input/Output) di schede di I/O installabili dall’utente.
Lo schema generale è il seguente:
Indirizzi (hex)
00000-005FF
Dimensione
(byte)
(Kb)
1536
1,5
00600-9FFFF
653823
A0000-FFFFF
393215
640
384
Descrizione
Strutture dati e valori riservati a MsDos
IO.SYS, MSDOS.SYS e COMMAND.COM
Programmi Applicativi
ROM BIOS
ROM d’espansione di schede di I/O
Nome
Memoria di Sistema
Memoria Convenzionale
Memoria Riservata
BOOT
Ogni Sistema Operativo deve avviarsi contando sul software scritto all’interno del calcolatore (FirmWare su B i o s ) e sul software scritto nel settore
di avvio di un dispositivo avviabile (es. un hard disk, o in generale un dispositivo di memoria secondaria avviabile) nel cosiddetto B o o t S e c t o r .
Tutta la fase di avvio di un calcolatore è denominata B o o t s t r a p (o Boot), e la prima parte dell’avvio è indipendente dal Sistema Operativo cioè è identica per ogni Sistema Operativo installabile sulla macchina.
Questa fase, denominata in breve P O S T (Power On Self Test) è a carico del FirmWare scritto dalla casa costruttrice nel Bios – come si nota dalla
classica schermata di avvio che ne riporta il logo. Le routine scritte nel Bios sono scritte in puro linguaggio Assembly, non essendo ancora
disponibile alcun supporto per programmi ad alto livello.
Quando la fase di POST termina, essa individua un dispositivo avviabile – secondo l’ordine che è memorizzato nella EEPROM del Bios, e cede, alla
cieca, il controllo al Boot Sector del dispostivo.
Il codice nel Boot Sector è ancora scritto in puro linguaggio Assembly, e usa qualche sottoprogramma messo a disposizione dal Bios per
cominciare a caricare i dati del Sistema Operativo da disco fisso (se il dispositivo di Boot era il disco fisso).
Il codice scritto nel Boot Sector carica il primo programma per MsDos, che è contenuto nel file I O . S Y S . Questo si avvia e a sua volta carica il
secondo file di MsDos, che si chiama M S D O S . S Y S . Infine, MSDOS.SYS carica il terzo programma fondamentale di MsDos, denominato
COMMAND.COM.
Il processo di avvio prevede che i files di sistema, durante il boot, vadano a consultare due files di testo in cui l’utente può scrivere alcune istruzioni
di configurazione specifiche per il proprio calcolatore. Questi files di configurazione sono CONFIG.SYS e AUTOEXEC.BAT.
CONFIG.SYS contiene parametri di configurazione personalizzata, solo letti da MsDos, mentre AUTOEXEC.BAT contiene una lista di istruzioni
personalizzate da eseguire al termine della fase di avvio del Sistema Operativo.
Si può dire che il k e r n e l di MsDos (ovvero la parte di SO più profonda) è costituito da IO.SYS e MSDOS.SYS, che interagisce con l’hardware e
con i programmi, mentre la s h e l l di MsDos (ovvero la parte di SO più superficiale) è costituita da COMMAND.COM, che interagisce con l’utente e
il kernel.
MsDos è completato da una serie di programmi residenti su disco che ne estendono la funzionalità (es. format.exe). Questi programmi di sistema
sono anche detti comandi e s t e r n i , per differenziarli da quelli direttamente eseguiti dalla shell, denominati comandi i n t e r n i (es. copy).
FORMATO DEGLI ESEGUIBILI E RILOCAZIONE: EXE E COM
MsDos riconosce come programmi eseguibili due tipi di formati che corrispondono a due tipi di files a loro volta distinti da una estensione
caratteristica: files eseguibili di tipo C O M (con estensione .COM) e files eseguibili di tipo E X E (con estensione .EXE). Inoltre la shell è in grado di
eseguire in serie una lista di comandi (interni o esterni) scritti, riga per riga, su un file di testo denominato b a t c h e di estensione B A T (.BAT).
Pur esssendo eseguito, un file .BAT non è un file eseguibile.
Gli eseguibili di tipo COM non possono essere più ampi di 64Kb, mentre gli eseguibili di tipo EXE possono essere di qualsiasi dimensione, purchè
inferiore alla dimensione della memoria convenzionale disponibile (tipicamente 640 Kb).
L’eseguibile di tipo COM è molto semplice: contiene direttamente la lista di istruzioni assembler da eseguire, senza alcuna informazione ulteriore.
Quando la shell deve eseguire un file COM - su ordine dell’utente o perché presente in una riga di un file batch, decide a quale indirizzo di Memoria
caricarlo – operazione detta di r i l o c a z i o n e s t a t i c a (o caricamento rilocante statico) di MsDos.
Nei primi 256 byte, a partire da quell’indirizzo, scrive una serie di informazioni canoniche dette P S P (Program Segment Prefix), quindi legge il
file .COM da disco byte per byte e lo ricopia nello stesso ordine in Memoria, subito dopo il PSP. Terminato il caricamento, MsDos cede il controllo
alla prima istruzione del programma COM in memoria, impostando il Program Counter sull’indirizzo iniziale del programma. Quando il programma
terminerà, dovrà preoccuparsi di riconsegnare il controllo a MsDos, affinche il calcolatore ritorni nella condizione di partenza.
Il caricamento rilocante statico di MsDos per i files COM avviene in questo modo:
a. MsDos cerca una zona di Memoria libera contigua di ampiezza 64Kb (ampiezza denominata s e g m e n t o ), a partire da un indirizzo di
paragrafo nella memoria convenzionale (es. 10A0h:0000h)
S3Abacus – Architetture/Asm
b.
c.
d.
.doc - 1684Kb - 12/set/2008 - 44 / 82
A partire dall’indirizzo di paragrafo così prescelto, scrive il PSP, all’interno del quale compaiono informazioni utili al programma che sta
per essere caricato, come ad esempio il suo nome e i gli eventuali parametri su linea di comando che l’utente può aver aggiunto (es. in
format c:, c: è un parametro su linea di comando)
Subito dopo il PSP, il loader ricopia ordinatamente, byte per byte, il contenuto del file COM in Memoria (nessuna rilocazione).
Imposta tutti i registri di segmento al valore della parte segmento (seg) dell’indirizzo di paragrafo prescelto (nel nostro caso 10A0h), il
registro SP a FFFFh, il registro IP a 0100h e i rimanenti registri a 0. L’impostazione del registro IP a 0100h coincide con l’impostazione
del Program Counter (nel nostro caso CS:IP = 10A0h:0100h), quindi l’esecuzione viene di fatto ceduta al programma.
Si deve ricordare che l’operazione di caricamento di un programma in memoria, e sua effettiva esecuzione, viene fatta dal cosiddetto l o a d e r del
Sistema Operativo; nel nostro caso il loader di MsDos risiede all’interno di COMMAND.COM.
Una volta in esecuzione, il programma prende il nome di p r o c e s s o (mentre ‘programma’ è il nome delle istruzioni contenute nel file).
Il valore costante 0100h impostato su IP, coincide con la dimensione del PSP (100h = 256), esattamente così da indicare sempre ed esattamente
l’indirizzo di memoria della prima istruzione del programma caricato.
La ragione dell’impostazione al valore costante FFFFh del registro SP la si vedrà quando si studierà lo s t a c k .
I files eseguibili di tipo EXE vengono caricati in modo sostanzialmente differente, dato che la loro ampiezza può superare il limite del segmento
(limite di 64Kb, cioè la dimensione ottenibile usando i 16 bit di un registro x-86).
E’ necessario che ogni file eseguibile contenga, oltre al proprio codice eseguibile, anche una serie di informazioni supplementari che serviranno al
loader di MsDos per poterlo caricare correttamente in memoria. In questo caso si parla ancora di rilocazione statica, ma anche di c a r i c a m e n t o
rilocante dinamico.
Le informazioni supplementari contenute in un file EXE si dicono h e a d e r d e l f i l e E X E , cioè intestazione del file.
Esse sono ampie almeno 512 byte, cosicchè un file EXE è sempre almeno ampio 514 byte (due byte servono comunque per l’istruzione di
terminazione INT 21h). L’header di un file EXE inizia sempre con due byte costanti, che sono i primi due byte di qualsiasi file EXE: MZ (le iniziali del
nome di un progettista di MsDos). Nell’header del file EXE sono memorizzate in un formato specifico – e a cura dei linker dei compilatori, tutte le
informazioni necessarie al loader di MsDos per caricare il programma in memoria. Queste informazioni riguardano ad esempio la quantità di blocchi
da 64Kb (segmenti) di cui è costituito il programma EXE e le loro effetive dimensioni, e quale, tra questi, è il segmento di codice di partenza.
Il caricamento rilocante dinamico di MsDos per i files EXE avviene in questo modo:
a. Il loader di MsDos legge lo header del file EXE e cerca tante zone di Memoria libere (e contigue) di ampiezza 64Kb (segmenti), quanti ne
sono elencati nello header, a partire da indirizzi di paragrafo. Uno di questi sarà l’indirizzo di paragrafo (e il relativo segmento) di avvio del
programma (es. 10B0h:0000h), mentre un altro sarà l’indirizzo di paragrafo (e il relativo segmento) dei Dati del programma.
b. A partire dall’indirizzo di paragrafo dei Dati, MsDos scrive il PSP del programma EXE.
c. A partire dall’indirizzo di paragrafo del segmento di avvio del programma, il loader ricopia il file EXE nei vari segmenti individuati
precedentemente, avendo cura di correggere gli indirizzi che ne sono contenuti in accordo con la posizione dei valori nei segmenti di
memoria libera individuati precedentemente (rilocazione su caricamento).
d. Infine imposta il registro di segmento del codice (CS) al valore (seg) dell’indirizzo di avvio del programma (es. 10B0h), il registro di
segmento dei dati (DS) al valore (seg) del segmento del PSP, il registro SP a FFFFh, il registro IP a 0000h e i rimanenti registri a 0.
L’impostazione del registro IP a 0000h coincide con l’impostazione del Program Counter (nel nostro caso CS:IP = 10B0h:0000h), quindi
l’esecuzione viene di fatto ceduta al programma.
Come è evidente, il caricamento di un file EXE è più laborioso e richiede più tempo. Come si può notare, IP viene posto inizialmente a 0, a
differenza del valore iniziale che assume nel caso di caricamento di file COM, dato che il PSP, nei files EXE, è allocato sull’indirizzo di paragrafo dei
dati e non del codice.
API , INTERRUZIONI SW E SERVIZI
Quando MsDos è operativo, esso fornisce una interfaccia pubblica a numerose funzioni fondamentali che consentono ai programmatori di accedere
velocemente al video, alla tastiera, ai dischi e a tutti i dispositivi installati dal sistema, evitando di conoscere i dettagli e le complessità dell’accesso
all’hardware di I/O.
Queste funzioni, in generale, sono dette A P I (A p p l i c a t i o n P r o g r a m m a b l e I n t e r f a c e ) del Sistema Operativo.
Per MsDos le API di sistema sono scritte in Assembly e invocabili tramite una speciale istruzione dell’ISA x-86 denominata INT o i n t e r r u z i o n e
s o f w a r e . Per i sistemi operativi Win32 (Windows) e Linux, invece, le API sono scritte in linguaggio C, e sono radicalmente differenti.
Tutte le 256 interruzioni del Sistema Operativo sono numerate da 0 a 255 (0-FFh), e si distinguono in i n t e r r u z i o n i h a r d w a r e (le prime
16, da 0 a Fh), i n t e r r u z i o n i s o f t w a r e d e l B I O S (da 10h a 1Fh), i n t e r r u z i o n i s o f t w a r e d i M s D o s (da 20h a 2Fh). Le
successive, da 30h a 3Fh non sono documentate, mentre le rimanenti (da 40h a FFh) sono disponibili per usi definibili dall’utente.
Molto spesso le interruzioni forniscono più di una funzione d’uso, cosicchè è necessario selezionare una precisa sottofunzione di una interruzione
specifica, prima di invocarla. Ogni sottofunzione di una interruzione è anche detta s e r v i z i o d e l l ’ i n t e r r u z i o n e , ed è adeguatamente
numerata in modo che la selezione sia univoca. Il numero di sottofunzione di una interruzione va sempre specificato nel semiregistro A H prima
dell’invocazione dell’interruzione.
Il funzionamento delle interruzioni, che sono in realtà dei sottoprogrammi (o subroutine) è molto semplice: quando il chiamante invoca l’interruzione
di numero X, il sistema calcola l’indirizzo assoluto X * 4, si reca in memoria a questo indirizzo (0000h:X * 4), legge i quattro byte consecutivi a
questo indirizzo, imposta il Program Counter CS:IP con il valore di questi 4 byte e cede il controllo, alla cieca, al programma che si trova a
quell’indirizzo, programma detto anche routine associata all’interruzione (o I S R , I n t e r r u p t s e r v i c e R o u t i n e ).
Una speciale istruzione, che deve sempre essere presente come ultima istruzione di una ISR, consente di reimpostare il Program Counter
all’indirizzo successivo a quello dell’invocazione, così l’esecuzione ritorna automaticamente al chiamante esattamente nel punto in cui era stata
interrotta.
Infatti l’inizio della memoria di MsDos (memoria di Sistema), contiene la tavola dei vettori di Interruzione (I D T , I n t e r r u p t D e s c r i p t o r
T a b l e ), cioè una serie di 256*4 = 1024 byte che corrispondono agli indirizzi di 256 sottoprogrammi già pronti in memoria, le routine di
interruzione.
La differenza tra interruzioni software e interruzioni hardware riguarda solo il modo in cui vengono invocate: se l’interruzione è software, è il
programmatore che la invoca con l’istruzione x-86 I N T che ha messo nel codice del suo programma. La chiamata è detta s i n c r o n a .
Se l’interruzione è hardware, la chiamata viene fatta automaticamente dalla CPU - in questo caso si parla di e c c e z i o n e , o dal dispositivo di I/O
a cui quell’interruzione è associata. La chiamata è detta a s i n c r o n a .
.doc - 1684Kb - 12/set/2008 - 45 / 82
S3Abacus – Architetture/Asm
Bisogna dire che non tutte le interruzioni software possiedono una ISR all’indirizzo della IDT corrispondente. In alcuni casi presso quell’indirizzo
risiedono strutture dati dette T a b e l l e , contenenti dati necessari per la configurazione di determinati dispositivi.
Istruzione INT
Sintassi:
INT num
Scopo:
Viene avviata l’interruzione sw di numero num.
Esempi:
INT 20h
Nota:
INT 21h
INT 10h
In molte occasioni prima di usare l’istruzione INT, deve essere impostato nel registro AH il numero di sottoservizio
In generale, il meccanismo dell’interruzione così descritto consente di v i r t u a l i z z a r e l’accesso all’hardware, che è la parte più complessa
della programmazione per qualsiasi ambiente.
La possibilità di modificare la IDT, e quindi le varie ISR associate, consente di caricare in memoria il codice più aggiornato, o più efficiente o più
adeguato per ogni dispositivo di I/O disponibile anche in futuro, lasciando intatte le chiamate dei programmi alle funzioni di gestione. La IDT può
essere aggiornata sia in fase di caricamento del Sistema Operativo – così come fa MsDos con i files IO.SYS, MSDOS.SYS e COMMAND.COM, sia
caricando ‘al volo’ codice specifico in moduli speciali detti d r i v e r – che in MsDos hanno estensione .SYS, sia operando la modifica della IDT
durante l’esecuzione di un programma utente (cioè a r u n t i m e , badando però di risistemare le impostazioni al termine dell’esecuzione).
In ogni caso le API del Bios (10h-1Fh) sono sempre disponibili nella loro forma originale, garantite dalla immodificabilità del FW su Bios (benchè
IO.SYS ne intercetti le chiamate e le normalizzi).
VIDEO E TASTIERA CON LE INTERRUZIONI SW DEL BIOS
Le istruzioni assembler necessarie per accedere alla c o n s o l e (video e tastiera) sono necessarie per poter scrivere un qualsiasi programma
significativo.
Le principali funzioni per accedere al video e alla tastiera sono contenute nel sottoinsieme delle interruzioni software del Bios, in particolare la 10h
(e sue sottofunzioni) per il Video e la 16h (e sue sottofunzioni) per la tastiera.
Le funzioni di accesso alla console fornite dalle interruzioni del Bios sono importanti perché sempre disponibili fin dall’avvio del calcolatore, essendo
scritte in FW.
n. INT
Descrizione
10h
11h
12h
13h
14h
15h
16h
17h
18h
19h
1Ah
1Bh
1Ch
1Dh
1Eh
1Fh
Funzioni per la gestione del Video
Determinazioni dotazione del computer
Determinazioni della memoria
Funzioni per la gestione dei dischi
Funzioni per la gestione delle porte seriali
Funzioni per la gestione estesa del sistema
Funzioni per la gestione della Tastiera
Funzioni per la gestione della stampante
Caricatore del Basic IBM (obsoleta)
Esecuzione del Bootstrap da disco
Funzioni per la gestione dell'orologio in tempo reale
Procedura utente per la gestione della tastiera
Procedura utente per la gestione del timer di Sistema
Tabella di inizializzazione del video
Tabella dei parametri dei floppy disk
Tabella dei caratteri video
INT 10h Sottofunzione 0Eh – Stampa di un carattere sullo schermo
MOV AL, 30h
MOV AH, 0Eh
INT 10h
; codice Ascii del carattere da stampare a video (es. 30h è lo zero, o ‘0’)
; sottofunzione
; interruzione sw del Bios gestione Video
Il carattere da stampare a schermo va sempre fornito con il suo codice Ascii, pertanto la stampa di singoli numeri decimali (da 0 a 9) deve essere
sempre normalizzata, aggiungendo 48 (o 30h) al numero da stampare.
Si può indicare il codice Ascii di un qualsiasi carattere indicandolo tra singoli apici, così come in linguaggio C.
INT 16h
MOV AH, 00h
INT 16h
Sottofunzione 00h – Input di un carattere da tastiera
; sottofunzione
; interruzione sw del Bios gestione Tastiera
; in AL il codice Ascii del carattere premuto, in AH il codice di scansione
Naturalmente l’esecuzione di questa interruzione blocca il flusso del programma in esecuzione, che rimane in attesa di un carattere digitato dalla
tastiera. Non appena un carattere viene premuto, la routine ritorna avendo memorizzato in AL il codice Ascii del carattere premuto.
VIDEO E TASTIERA CON LE INTERRUZIONI SW DI MSDOS
Le stesse funzioni di accesso alla console possono essere richieste tramite l’interruzione software di MsDos denominata INT 21h.
.doc - 1684Kb - 12/set/2008 - 46 / 82
S3Abacus – Architetture/Asm
L’effetto è sostanzilamente identico, anche se bisogna ricordare che tali routines non sono disponibili fin dall’avvio del sistema, ma solo allorquando
MsDos è completamente caricato. Per i programmi utente questa distinzione non è rilevante.
INT 21h
MOV DL, 30h
MOV AH, 02h
INT 21h
Sottofunzione 02h
– Stampa di un carattere sullo schermo
; codice Ascii del carattere da stampare a video (es. 30h è lo zero, o ‘0’)
; sottofunzione
; interruzione sw di MsDos
Il carattere da stampare a schermo va sempre fornito con il suo codice Ascii, pertanto la stampa di singoli numeri decimali (da 0 a 9) deve essere
sempre normalizzata, aggiungendo 48 (o 30h) al numero da stampare.
Si può indicare il codice Ascii di un qualsiasi carattere indicandolo tra singoli apici, così come in linguaggio C.
INT 21h
MOV AH, 01h
INT 21h
Sottofunzione 01h
– Input di un carattere da tastiera
; sottofunzione
; interruzione sw di MsDos
; in AL il codice Ascii del carattere premuto
Naturalmente l’esecuzione di questa interruzione blocca il flusso del programma in esecuzione, che rimane in attesa di un carattere digitato dalla
tastiera. Non appena un carattere viene premuto, la routine ritorna avendo memorizzato in AL il codice Ascii del carattere premuto.
A differenza della sua gemella del Bios, questa funzione mostra a video il carattere premuto.
TERMINARE I PROGRAMMI IN MSDOS
Ogni programma scritto per MsDos in assembler x-86 quando termina deve avvisare il Sistema Operativo tramite una interruzione sw specifica.
In questo modo il SO riacquisisce il controllo del calcolatore correttamente, riconfigurandosi opportunamente per riprendere la sessione di lavoro in
attesa del lancio di un nuovo programma eseguibile da parte dell’utente. Tramite l’avviso di terminazione, che deve essere sempre l’ultima
istruzione assembler di ogni programma sia EXE, sia COM, il programma può avvisare il SO sullo stato della propria terminazione, indicando, ad
esempio, eventuali terminazioni anomale o, più frequentemente, una terminazione regolare.
INT 20h
INT 20h
– Terminazione di un programma COM
; interruzione sw di MsDos Terminazione programma COM
L’interruzione non necessita di alcun parametro, ma non consente di avvisare MsDos sullo stato di terminazione.
INT 21h
MOV AL, 00h
MOV AH, 4Ch
INT 21h
Sottofunzione 4Ch
– Terminazione di un programma EXE
; codice di terminazione. Se 0 = terminazione regolare
; sottofunzione
; interruzione sw di MsDos Terminazione programma EXE
L’interruzione di terminazione dei file eseguibili EXE consente di avvisare MsDos sullo stato di terminazione, inseribile nel semiregistro AL. MsDos
può valutare questo valore usando l’istruzione ERRORLEVEL, magari in un comando batch.
Se si volesse usare lo stato di terminazione anche per un file eseguibile COM, si può usare questa interruzione.
.doc - 1684Kb - 12/set/2008 - 47 / 82
S3Abacus – Architetture/Asm
ASSEMBLY CON DEBUG
Il modo più immediato di scrivere programmi in Assembly per x-86 è tramite il programma di Sistema Operativo D E B U G . C O M fornito su tutte le
piattaforme Microsoft tramite la Shell di MsDos (invocabile a sua volta usando il comando c m d ).
Con questo programma si scrivono programmi assembly x-86 in formato COM e con indirizzi assoluti numerici, imponendo una rigorosa disciplina
nella scrittura delle istruzioni e mostrando realmente la pratica della programmazione x-86.
Inoltre, il fatto che DEBUG.COM sia sempre disponibile su ogni piattaforma Microsoft x-86 (ovvero da MsDos fino a Win32), ne garantisce la
continuità didattica e la reperibilità immediata per gli studenti.
COMANDI
Debug è un ambiente a carattere, con un prompt (il trattino) che attende un comando dell’utente.
Tutti i numeri utilizzati con Debug sono sempre in formato esadecimale; per usare una diversa rappresentazione va specificato il formato in coda al
numero.
I comandi dell’utente sono singoli caratteri, di facile comprensione. In evidenza, i comandi principali.
Debug non è c a s e s e n s i t i v e , ovvero non distingue se il comando è scritto in maiuscolo o in minuscolo, comportandosi allo stesso modo in
entrambi i casi..
Nome
Sigla
Parametri
Descrizione
Assembla
Confronta
Dump
Immetti
Riempi
Vai
Esadecimale
Input
Carica
Muovi
Nomina
Output
Procedi
Esci
Registro
Cerca
Traccia
Disassembla
Scrivi
A
C
D
E
F
G
H
I
L
M
N
O
P
Q
R
S
T
U
W
[indirizzo]
intervallo indirizzo
[intervallo]
indirizzo [elenco]
intervallo elenco
[=indirizzo] [indirizzi]
valore1 valore2
Porta
[indirizzo] [unità] [primosettore] [numero]
intervallo indirizzo
[nomefile] [elencoargomenti]
porta byte
[=indirizzo] [numero]
[registro]
intervallo elenco
[=indirizzo] [valore]
[intervallo]
[indirizzo] [unità] [primosettore] [numero]
Consente di scrivere il programma
Esplora la memoria
Assegna il nome al programma
Esce da Debug
Esplora e/o imposta i registri
Consente di modificare il programma
Salva su disco il programma
ESPLORARE LA MEMORIA
Con il Comando D (D u m p ) Debug consente di esplorare la memoria principale, da 0 a FFFFFh.
Naturalmente la modalità d’uso e rappresentazione del comando prevede la sintassi con indirizzi segmentati seg:ofs e i numeri rappresentanti gli
indirizzi e i contenuti delle locazioni sono sempre espressi in formato esadecimale.
Al prompt di Debug (il trattino) si può quindi usare D [intervallo], potendo specificare l’indirizzo di partenza da cui analizzare la memoria o un
intervallo:
d
Mostra la memoria a partire dall’attuale indirizzo contenuto in CS:IP
d 0:0
Mostra la memoria a partire dall’indirizzo 0h:0h
d 0:0 100
Mostra la memoria a partire dall’indirizzo 0h:0h e per 256 celle (100h = 256)
C:\>debug
-d
0CE2:0100
0CE2:0110
0CE2:0120
0CE2:0130
0CE2:0140
0CE2:0150
0CE2:0160
0CE2:0170
-d 0:0
0000:0000
0000:0010
0000:0020
0000:0030
0000:0040
0000:0050
0000:0060
0000:0070
-q
C:\>
4D
43
57
41
50
45
44
5C
00
4F
53
4E
52
7E
41
6F
00
4D
5C
44
4F
31
54
6C
5F
53
53
2E
46
5C
41
6C
00
50
59
43
49
41
3D
61
B4
45
53
4F
4C
4C
43
72
02
43
54
4D
45
4C
3A
69
CD-21
3D-43
45-4D
00-41
3D-43
55-53
5C-44
5C-44
FE
3A
33
4C
3A
45
4F
41
C2
5C
32
4C
5C
7E
43
54
E2
57
5C
55
44
31
55
49
F8
34
43
53
4F
00
4D
41
CD
00
4F
45
43
41
45
50
20
D1
4D
52
55
50
7E
7E
6F
0C
4D
53
4D
50
31
31
M.._....!..... o
COMSPEC=C:\W4...
WS\SYSTEM32\COMM
AND.COM.ALLUSERS
PROFILE=C:\DOCUM
E~1\ALLUSE~1.APP
DATA=C:\DOCUME~1
\ollari\DATIAP~1
68
8B
46
6E
A9
B0
0E
AD
10
01
07
00
08
09
0C
06
A7
70
0C
8D
0C
0C
0C
0C
00
00
02
03
02
02
02
02
8B
B9
EC
88
A4
0D
14
A4
01
06
06
00
09
02
0C
F0
70
0C
97
8D
0C
DB
0C
00
00-16
02-40
05-3A
03-A2
02-AA
02-C4
02-1F
F0-37
00
07
00
00
09
09
0C
05
8D
0C
8D
8D
0C
0C
0C
0C
03
02
03
03
02
02
02
02
8B
FF
54
FF
5D
8B
AD
61
01
03
00
03
04
05
06
90
70
0C
8D
0C
0C
0C
0C
00
00
02
03
02
02
02
02
C0
h.....p.......p.
..p.....@.......
F.......:...T...
n...............
............]...
................
................
........7...a...
.doc - 1684Kb - 12/set/2008 - 48 / 82
S3Abacus – Architetture/Asm
Le videate del comando D sono costituite normalmente da 8 righe così strutturate:
•
Sezione di sinistra: mostra l'indirizzo segmentato della prima locazione di memoria riportata nella sezione centrale. L’indirizzo avanza di
10h, dato che su ogni riga sono rappresentate 16 celle di memoria da un byte l’una.
•
Sezione centrale: elenca il contenuto delle 16 locazioni consecutive, a partire da quella indirizzata dalla prima sezione. Per maggior
chiarezza la sequenza dei 16 byte di ciascuna riga è divisa in due gruppi da 8 byte divisi da un trattino.
•
Sezione destra: mostra le stesse 16 celle di memoria in formato Ascii stampabile, cioè escludendo i simboli con codici Ascii da 00h a 1Fh
(caratteri di controllo) e da 80h a FFh (caratteri Ascii estesi). Se il carattere non è stampabile, viene sostituito da un punto.
Se il comando D viene ripetuto, viene riportata a video la successiva serie di 128 byte, e così ad oltranza.
Nel secondo blocco dell’esempio, i 128 byte riportati sono i 32 indirizzi (128 / 4 = 32) delle prime 32 interruzioni di MsDos. Infatti, alla base della
memoria (cfr. Memoria) si trova la IDT (cfr. API , Interruzioni).
CONSULTARE I REGISTRI
Con il Comando R Debug consente di visualizzare il contenuto e lo stato dei registri x-86, nonché impostarne il valore.
Naturalmente i valori visualizzati sono espressi in esadecimale, mentre il registro dei Flags è mostrato attraverso delle sigle convenzionali.
Al prompt di Debug (il trattino) si può quindi usare R [registro], specificando un registro se si vuole impostarne il valore. Infine, si può modificare un
singolo bit del registro dei flag con il comando rf, specificando la lista dei nuovi valori (consultare la tabella sottostante):
r
Visualizza lo stato dei registri
r CX
Imposta il registro CX
r f
Imposta uno o più bit del registro dei Flag
C:\>debug
-r
AX=0000 BX=0000
DS=0CED ES=0CED
0CED:0100 27
-r cx
CX 0000
:12
-r
AX=0000 BX=0000
DS=0CED ES=0CED
0CED:0100 27
-rf
NV UP EI PL NZ NA
-rf
NV UP EI PL NZ NA
-r
AX=0000 BX=0000
DS=0CED ES=0CED
0CED:0100 27
-q
CX=0000 DX=0000
SS=0CED CS=0CED
DAA
SP=FFEE
IP=0100
BP=0000 SI=0000 DI=0000
NV UP EI PL NZ NA PO NC
CX=0012 DX=0000
SS=0CED CS=0CED
DAA
SP=FFEE
IP=0100
BP=0000 SI=0000 DI=0000
NV UP EI PL NZ NA PO NC
SP=FFEE
IP=0100
BP=0000 SI=0000 DI=0000
NV UP EI PL ZR NA PO NC
PO NC
-pe
PE NC
-zr po
CX=0012 DX=0000
SS=0CED CS=0CED
DAA
Si nota che oltre ai registri, il comando R mostra sempre l’indirizzo, l’op. code e il codice mnemonico dell’istruzione puntata dal Program Counter
(CS:IP).
Per quanto riguarda il registro Flags, vengono mostrati solo otto stati di otto bit significativi, con una coppia di caratteri.
La decodifica delle coppie di caratteri è la seguente:
Flag
Overflow
Direzione
Interruzione
Segno
Zero
Ausiliario
Parità
Carry
Codice Debug
OV
DN
EI
NG
ZR
AC
PE
CY
bit
1
1
1
1
1
1
1
1
Descrizione
si
decremento
abilitato
negativo
si
si
pari
si
Codice Debug
NV
UP
DI
PL
NZ
NA
PO
NC
Bit
0
0
0
0
0
0
0
0
Descrizione
no
incremento
disabilitato
positivo
no
no
dispari
no
SCRIVERE UN PROGRAMMA
La sequenza di passi per scrivere un programma x-86 per Msdos in formato COM con Debug è la seguente:
1.
2.
3.
4.
5.
Comando A – scrivere le istruzioni (e d i t )
Terminare la fase di edit con un invio su una riga vuota
Comando R – impostare la lunghezza del programma
Comando N – Assegnare il nome al programma
Comando W – Salvare il programma su disco
Il comando A deve essere specificato con l’[indirizzo] impostato a 100 (esadecimale), per rispettare la regola di MsDos circa il PSP (cfr. Formato
degli eseguibili e Rilocazione): la prima istruzione di un programma .COM deve sempre trovarsi all’indirizzo 100 (h).
Quindi si digiti A 100 per cominciare la fase di edit.
Ad ogni pressione del tasto Enter (Invio) l’istruzione è confermata e si può scrivere la successiva.
.doc - 1684Kb - 12/set/2008 - 49 / 82
S3Abacus – Architetture/Asm
Debug riporta sempre l’indirizzo delle istruzioni digitate.
Per terminare l’editazione del programma, bisogna premere Enter su una riga vuota. Debug, a questo punto, mostra il prompt (il trattino).
Controllando l’indirizzo dell’ultima istruzione scritta, sottraendo l’indirizzo iniziale (sempre 100h), si calcola facilmente la dimensione, in byte, del
programma scritto. Questa dimensione va specificata nel registro CX tramite il comando R, nella forma R CX. Si digita la dimensione e la si
conferma con Enter. Debug mostra di nuovo il prompt.
Ora si può assegnare il nome al programma con il comando N. Il nome deve rispettare le regole per gli identificatori di MsDos, ovvero non può
essere più lungo di 8 caratteri e non può contenere caratteri speciali (es. lo spazio), né iniziare con una cifra. Va sempre specificata l’estensione
COM. Quindi il comando da utilizzare è il seguente (se si sceglie pippo come nome): N PIPPO.COM. Digitato Enter, Debug mostra di nuovo il
prompt (il trattino).
Per salvare il programma su disco – nella cartella corrente, basta usare il comando W senza argomenti. Debug risponde con la quantità di byte
scritti su disco e mostra il prompt.
Ora si può uscire (comando Q) e lanciare il programma dal prompt di MsDos.
IL PROGRAMMA PIÙ CORTO DEL MONDO
Il programma più corto del mondo per x-86 è un programma eseguibile COM che si termina.
Nel dettaglio, tutti i passi da usare con Debug per scrivere questo programma.
C:\>
C:\>debug
-a 100
0CE1:0100 int 20
0CE1:0102
-r cx
CX 0000
:2
-n corto.com
-w
Scrittura di 00002 byte in corso
-q
Prompt di MsDos
Lancio del programma Debug da Msdos
Edit del programma a partire dall’indirizzo 100h
Unica riga di codice: terminazione di file .COM
Riga vuota: termine dell’edit
Comando per l’impostazione della dimensione
Visualizzazione del contenuto attuale del registro CX da parte di Debug
Impostazione della dimensione (100h-102h=2h)
Assegnazione del nome (corto.com)
Comando per la scrittura del file di programma su disco
Visualizzazione della quantità di byte scritti da parte di Debug
Uscita da debug
C:\>corto
Lancio del programma corto.com da MsDos
Nessun output del programma
Prompt di MsDos
C:\>
Si noti come, su ogni riga digitata, Debug riporti l’indirizzo seg:ofs dell’istruzione, calcolando automaticamente l’ampiezza, in byte, dell’istruzione
utilizzata. Il valore della parte segmento dell’indirizzo non è significativo, mentre la parte offset dell’indirizzo è l’esatta posizione in memoria che
quell’istruzione avrà nel segmento scelto dal caricatore di MsDos una volta lanciato il programma.
Istruzione NOP
Sintassi:
NOP
Scopo:
Non fa nulla. Usata per scopi di servizio, p.e. per allineare una sequenza di istruzioni.
Esempi:
NOP
Nota:
A volte viene inserita NOP se si vuole aumentare di una unità l’indirizzo del codice in esecuzione. In altri casi per rallentare l’esecuzione.
Per verificare il programma scritto ed eventualmente modificarlo, si usano i comandi L e U dopo aver specificato il nome del programma da
manipolare. Verrà aggiunta una sola istruzione NOP al codice precedente, per mostrare il modo in cui Debug consente di modificare un
programma:
C:\>
C:\>debug
-n corto.com
-l
-u 100 L2
0D5C:0100 CD20
INT 20
-a 100
0D5C:0100 nop
0D5C:0101 int 20
0D5C:0103
-r cx
CX 0002
:3
-w
Scrittura di 00003 byte in corso
-q
C:\>
Prompt di MsDos
Lancio del programma Debug da Msdos
Assegnazione del nome (corto.com)
Caricamento del file da disco (nella cartella corrente)
Disassemblaggio, a partire dall’indirizzo 100h, per 2 byte (dimensione del file)
Codice disassemblato, con indirizzo (0100h), op. code (CD20h) e codice mnemonico (INT 20)
Modifica dell’istruzione all’indirizzo 100h
Nuova istruzione (NOP = non fa nulla)
Terminazione di file .COM
Riga vuota: termine dell’edit
Comando per l’impostazione della dimensione
Visualizzazione del contenuto attuale del registro CX da parte di Debug
Impostazione della dimensione (100h-103h=3h)
Comando per la scrittura del file di programma su disco
Visualizzazione della quantità di byte scritti da parte di Debug
Uscita da debug
Prompt di MsDos
In alternativa, il file di programma da disassemblare può essere caricato direttamente citandolo come argomento di Debug:
C:\>
C:\>debug corto.com
-u 100 L2
0D5C:0100 CD20
INT 20
-a 100
Prompt di MsDos
Lancio del programma Debug da Msdos con caricamento del file corto.com
S3Abacus – Architetture/Asm
.doc - 1684Kb - 12/set/2008 - 50 / 82
0D5C:0100 nop
0D5C:0101 int 20
0D5C:0103
-r cx
CX 0002
:3
-w
Scrittura di 00003 byte in corso
-q
C:\>
VIDEO E TASTIERA
Un programma che si limita a richiedere l’immissione di un carattere e a stampare la stringa “CIAO” può essere scritto nel seguente modo:
C:\>
C:\>debug
-a 100
0CED:0100 mov ah,0
0CED:0102 int 16
0CED:0104 mov al,43
0CED:0106 mov ah,0e
0CED:0108 int 10
0CED:010A mov al,49
0CED:010C mov ah,0e
0CED:010E int 10
0CED:0110 mov al,41
0CED:0112 mov ah,0e
0CED:0114 int 10
0CED:0116 mov al,4f
0CED:0118 mov ah,0e
0CED:011A int 10
0CED:011C int 20
0CED:011E
-n ciao.com
-r cx
CX 0003
:1e
-w
Scrittura di 0001e byte in corso
-q
C:\>
Prompt di MsDos
Lancio del programma Debug da Msdos
Edit del programma a partire dall’indirizzo 100h
Sottofunzione 00h di INT 16h, Input di un carattere da Tastiera
Lancio interruzione sw Bios 16h
Codice Ascii da stampare (43h = ‘C’)
Sottofunzione 0Eh di INT 10h, Stampa carattere sullo Schermo
Lancio interruzione sw Bios 10h
Codice Ascii da stampare (49h = ‘I’)
Sottofunzione 0Eh di INT 10h, Stampa carattere sullo Schermo
Lancio interruzione sw Bios 10h
Codice Ascii da stampare (41h = ‘A’)
Sottofunzione 0Eh di INT 10h, Stampa carattere sullo Schermo
Lancio interruzione sw Bios 10h
Codice Ascii da stampare (4Fh = ‘O’)
Sottofunzione 0Eh di INT 10h, Stampa carattere sullo Schermo
Lancio interruzione sw Bios 10h
Terminazione di file .COM
Riga vuota: termine dell’edit
Assegnazione del nome (ciao.com)
Comando per l’impostazione della dimensione
Visualizzazione del contenuto attuale del registro CX da parte di Debug
Impostazione della dimensione (100h-11Eh=1Eh)
Comando per la scrittura del file di programma su disco
Visualizzazione della quantità di byte scritti da parte di Debug
Uscita da debug
Prompt di MsDos
STRUTTURE DI CONTROLLO
Le principali strutture di controllo utilizzate in assembler x-86, oltre alla sequenza, sono la c o n d i z i o n e (se-allora) e l’i t e r a z i o n e (ripeti n
volte), rispettivamente implementate dalle istruzioni di s a l t o c o n d i z i o n a t o (J* indirizzo) e dall’istruzione l o o p (LOOP* indirizzo).
Le istruzioni di salto condizionato, che spostano l’esecuzione all’indirizzo in esse specificato, sono numerose, e vanno utilizzate, di norma, subito
dopo l’istruzione di confronto C M P , cosicchè i flag del registro omonimo risultano impostati in base al rapporto tra i due operandi dell’istruzione di
confronto (che in realtà coincide con una sottrazione).
In base allo stato dei singoli flag del registro dei Flags, infatti, le varie istruzioni di salto condizionato effettuano il salto all’indirizzo specificato o
meno.
Istruzione CMP
Sintassi:
CMP op1, op2
Scopo:
Viene eseguita la sottrazione op1 - op2 e impostati i Flags opportuni in base all’esito della sottrazione.
Esempi:
CMP AL,2
CMP AX, BX
MOV [BX], AL
Nota:
L’istruzione viene utilizzata in base alle regole generali della sintassi x-86 (cfr. Sintassi e indirizzamenti). Naturalmente op1 e op2
rimangono invariati dopo la CMP.
Istruzione J*
Sintassi:
J* indirizzo
Scopo:
Il flusso dell’esecuzione si sposta su indirizzo se la condizione sui flags previste dalla J* sono verificate, altrimenti il
flusso dell’esecuzione prosegue regolarmente in sequenza. Questo gruppo di istruzioni sono dette s a l t i
c o n d i z i o n a t i , e si usano spesso dopo l’istruzione CMP per sfruttarne le modifiche di stato dei flag
Esempi:
JE 109h
JG 110h
JB 112h
Nota:
Se l’esito del confronto CMP precedente ha impostato il flag di Zero, allora i due operandi di CMP sono uguali e la JE salta all’indirizzo
109h. Analogamente per gli altri due casi, se gli operandi sono, rispettivamente, il primo maggiore del secondo
(numeri con segno), il primo minore del secondo (numeri senza segno).
Ricordare che indirizzo non può rappresentare una differenza maggiore di 128 rispetto all’indirizzo in cui si trova l’istruzione J*.
.doc - 1684Kb - 12/set/2008 - 51 / 82
S3Abacus – Architetture/Asm
Una tabella di riferimento per consultare velocemente il comportamento delle istruzioni di salto condizionato, è la seguente:
Istruzione
JE,JZ
JNE, JNZ
JA,JNBE
JAE,JNB,JNC
JB,JC,JNAE
JBE,JNA
JG,JNLE
JGE,JNL
JL,JNGE
JLE,JNG
JNO
JNP,JPO
JNS
JO
JP,JPE
JS
ZeroF
1
0
0
CarryF
1
0
SegnoF
OF
PF
Operatore
=
≠
0
0
1
1
1
=
=
≠
≠
0
1
=
=
≠
≠
0
>
>=
<
<=
>
>=
<
<=
0
1
1
Descrizione
Salta se uguali
Salta se diversi
Salta se maggiore, senza segno
Salta se maggiore o uguale, senza segno
Salta se minore, senza segno
Salta se minore o uguale, senza segno
Salta se maggiore, con segno
Salta se maggiore o uguale, con segno
Salta se minore, con segno
Salta se minore o uguale, con segno
Salta se non c’è overflow
Salta se c’e’ non c’e’ parità (ovvero c’è parità dispari)
Salta se non c’e’ segno
Salta se c’è overflow
Salta se c’e’ parità (pari)
Salta se c’e’ segno
Istruzione JMP
Sintassi:
JMP indirizzo
Scopo:
Sposta il flusso dell’esecuzione all’indirizzo
Esempi:
JMP 120h
Nota:
JMP CS:120h
L’istruzione viene usata per spostare il flusso dell’esecuzione ad un indirizzo specifico in modo incondizionato. L’istruzione viene detta
s a l t o i n c o n d i z i o n a t o e non ha i limiti di estensione del salto condizionato. Può infatti essere specificato un
indirizzo completo seg:ofs.
Ecco come si presenta un codice Debug che attende un tasto, se il tasto è lo zero (0), viene stampato a schermo una zeta maiuscola, altrimenti una
n minuscola:
Prompt di MsDos
C:\>
Lancio del programma Debug da Msdos
C:\>debug
Edit del programma a partire dall’indirizzo 100h
-a 100
Sottofunzione 00h di INT 16h, Input di un carattere da Tastiera
0d53:0100 mov ah,00
Lancio interruzione sw Bios 16h
0d53:0102 int 16
Confronto carattere in input (AL) con carattere zero (30h = ‘0’)
0d53:0104 cmp al,30
Se uguali, salta all’indirizzo 110h ove si stamperà ‘Z’
0d53:0106 je 0110
Altrimenti si stampa il carattere ‘n’ (6eh = ‘n’)
0d53:0108 mov al,6e
Sottofunzione 0Eh di INT 10h, Stampa carattere sullo Schermo
0d53:0100 mov ah,0e
Lancio interruzione sw Bios 10h
0d53:010c int 10
Salta alla fine del programma
0d53:010e jmp 0116
Si stampa il carattere ‘Z’ (5ah = ‘Z’)
0d53:0110 mov al,5a
Sottofunzione 0Eh di INT 10h, Stampa carattere sullo Schermo
0d53:0112 mov ah,0e
Lancio interruzione sw Bios 10h
0d53:0114 int 10
Terminazione di file .COM
0d53:0116 int 20
Riga vuota: termine dell’edit
0d53:0118
Assegnazione del nome
-n cmpj.com
Comando per l’impostazione della dimensione
-r cx
Visualizzazione del contenuto attuale del registro CX da parte di Debug
CX 001e
Impostazione della dimensione (100h-118h=18h)
:18
Comando per la scrittura del file di programma su disco
-w
Visualizzazione della quantità di byte scritti da parte di Debug
Scrittura di 00018 byte in corso
Uscita da debug
-q
Prompt di MsDos
C:\>
Istruzione INC
Sintassi:
INC dest
Scopo:
L’istruzione INC incrementa di una unità dest, che può essere un registro o una locazione di memoria.
Esempi:
INC AX
Nota:
INC [102]
INC DL
Non modifica il registro dei Flags, pertanto se servisse valutarli, si può usare l’equivalente ADD dest,1 (cfr. istruzione ADD). La sua duale
è DEC, che ha la stessa sintassi e decrementa di una unità dest.
Istruzione LOOP
Sintassi:
LOOP indirizzo
Scopo:
All’esecuzione di LOOP la CPU decrementa di una unità il registro contatore CX; se CX è diverso da zero, il flusso
passa all’istruzione posta ad indirizzo, altrimenti il flusso dell’esecuzione prosegue regolarmente in sequenza.
Esempi:
LOOP 110h
.doc - 1684Kb - 12/set/2008 - 52 / 82
S3Abacus – Architetture/Asm
Nota:
Naturalmente l’iterazione automatica di LOOP funziona solo se, prima del blocco da ripetere – chiuso da LOOP, si imposta il registro CX
con il numero delle iterazioni desiderate. LOOP salta quasi sempre all’indietro, ovvero indirizzo è quasi sempre una
locazione di memoria precedente a LOOP, ma seguente all’impostazione di CX.
Ecco un codice per debug che stampa le 26 lettere minuscole dell’elenco alfabetico inglese
C:\>
C:\>debug
-a 100
0d53:0100 mov cx,1a
0d53:0103 mov dl,61
0d53:0105 mov ah,02
0d53:0107 int 21
0d53:0109 inc dl
0d53:010b loop 0105
0d53:010d int 20
0d53:010f
-n alfabe.com
-r cx
CX 0018
:f
-w
Scrittura di 0000f byte in corso
-q
C:\>
Prompt di MsDos
Lancio del programma Debug da Msdos
Edit del programma a partire dall’indirizzo 100h
Numero di iterazioni: 26 (1ah = 26)
Codice Ascii della a minuscola (61h = ‘a’)
Sottofunzione 02h di INT 21h, Stampa carattere sullo Schermo
Lancio interruzione sw MsDos 21h
Incrementa di una unità il codice Ascii
Ripeti CX volte dall’indirizzo 105h, quindi prosegui
Terminazione di file .COM
Riga vuota: termine dell’edit
Assegnazione del nome
Comando per l’impostazione della dimensione
Visualizzazione del contenuto attuale del registro CX da parte di Debug
Impostazione della dimensione (100h-10Fh=Fh)
Comando per la scrittura del file di programma su disco
Visualizzazione della quantità di byte scritti da parte di Debug
Uscita da debug
Prompt di MsDos
EDITARE UN PROGRAMMA
La scrittura di programmi con Debug è complicata, soprattutto per quanto riguarda il calcolo degli indirizzi, ad esempio, per le istruzioni di salto.
Per rendere più agevole l’uso di Debug, si può utilizzarlo sfruttando i simboli di ridirezione di MsDos, ottenendo così la possibilità di poter agire su
un codice sorgente in un file di testo.
La scrittura di un file sorgente per Debug è semplice.
•
Avviare la scrittura di un file di testo (es. alfabe.txt) tramite un editor di testo (es. notepad alfabe.txt)
C:\>notepad alfabe.txt
•
•
Ogni istruzione va riportata su una riga, e la prima riga deve essere sempre il comando di Assemblaggio (es. a 100).
Dopodichè vanno specificate le righe di codice, magari scrivendo indirizzi temporanei (es. sempre 100) laddove non è possibile, in un
primo tempo, sapere il valore preciso dell’indirizzo da specificare nel programma (p r i m a p a s s a t a ).
Il codice va terminato, come al solito, con una riga vuota, quindi si riportano i comandi di Debug soliti per il salvataggio del file eseguibile
COM, specificando una dimensione temporanea abbondante (es. 100).
Ricordare di lasciare sempre una riga vuota al termine del file.
•
•
Editando un file per debug, si può proficuamente dotare il testo di c o m m e n t i , cioè di righe che non verranno prese in considerazione
dal programma Debug, e che contengono indicazioni di aiuto alla comprensione del listato.
I commenti si possono porre su righe che abbiano come primo carattere il p u n t o e v i r g o l a (;)
Il programma precedente, scritto su file di testo (es. alfabe.txt), appare nel seguente modo:
a 100
mov cx,1a
mov dl,61
mov ah,2
int 21
inc dl
loop 100
int 20
; 100h = indirizzo temporaneo
n alfabe.com
rcx
100
w
q
•
Nota: 100h = dimensione temporanea
Per ottenere il file eseguibile (es. alfabe.com), è ora sufficiente usare la seguente ridirezione al prompt di MsDos:
C:\>debug < alfabe.txt > alfabe.lst
Se il codice è corretto, su disco viene salvato regolarmente il file eseguibile (es. alfabe.com) e un secondo file di tipo l i s t i n g (es.
alfabe.lst) che ha la seguente forma:
-a 100
0CE2:0100
0CE2:0103
0CE2:0105
0CE2:0107
mov
mov
mov
int
cx,1a
dl,61
ah,2
21
.doc - 1684Kb - 12/set/2008 - 53 / 82
S3Abacus – Architetture/Asm
0CE2:0109 inc dl
0CE2:010B loop 100
0CE2:010D int 20
0CE2:010F
-n alfabe.com
-rcx
CX 0000
:100
-w
Scrittura di 00100 byte in corso
-q
•
Se nella prima passata si erano riportati indirizzi temporanei, consultando il file di listing si possono agevolmente desumere gli indirizzi
reali, nonché la dimensione effettiva del programma. Con una s e c o n d a p a s s a t a sul file sorgente e una nuova operazione di
ridirezione, il programma viene messo a punto definitivamente. Ecco come appare dopo la messa a punto:
a 100
mov cx,1a
mov dl,61
mov ah,2
int 21
inc dl
loop 105
int 20
; 105h = indirizzo effettivo, desunto dal file di listing
n alfabe.com
rcx
f
w
q
•
Nota: fh = dimensione effettiva, desunta dal file di listing
Infine, si ottiene il file eseguibile definitivo (es. alfabe.com) riutilizzando di nuovo la ridirezione al prompt di MsDos:
C:\>debug < alfabe.txt > alfabe.lst
L’intera sequenza per editare, mettere a punto e generare il file eseguibile Com a partire da un file di testo TXT è la seguente:
C:\>
C:\>notepad alfabe.txt
alfabe.txt
C:\>debug < alfabe.txt > alfabe.lst
consultare
C:\>notepad alfabe.lst
C:\>debug < alfabe.txt > alfabe.lst
alfabe.com
C:\>alfabe
Abcdefghijklmnopqrstuvwxyz
C:\>
OUTPUT
; creazione del file di testo contenente il programma
; prima passata: creazione del file di listing alfabe.lst da
; apertura del file di listing per consentire la messa a punto
; seconda passata: creazione del file eseguibile
; esecuzione del programma alfabe.com
; output del programma
AREA DATI E AREA CODICE
Naturalmente le API di MsDos forniscono gli strumenti per memorizzare dati singoli (variabili), array di caratteri (stringhe), array di numeri e l’I/O di
stringhe sullo schermo e dalla tastiera.
L’allocazione di dati in memoria avviene tramite una p s e u d o i s t r u z i o n e , ovvero una indicazione contenuta nel codice affinchè il dato da
allocare sia semplicemente posto in memoria – per distinguerlo dall’ op. code di una istruzione.
Le pseudoistruzioni non generano alcun codice macchina, ma servono solo per indicare come deve comportarsi un traduttore (nel nostro caso
Debug). Le pseudoistruzioni per allocare dati in memoria sono:
PseudoIstruzione D*
Sintassi:
D*
Scopo:
Alloca il dato all’indirizzo corrente
Esempi:
DB 0
Nota:
dato
DB 41h
DB ‘A’
DB ?
DB 10 DUP (0)
Nel primo caso viene allocato un byte in memoria (0), nel secondo un byte che rappresenta il codice Ascii della A maiuscola, nel terzo
caso un modo equivalente al secondo e nel terzo si allocano due byte contigui.
Nel caso DB ? si alloloca un byte senza inizializzarlo (il valore in quella cella sarà casuale), mentre in DB 10 DUP (0)
si indica l’allocazione di 10 byte tutti inizializzati a 0.
E’ disponibile anche DW (alloca due byte) DD (4 byte) DQ (8 byte) e DT (10 byte), queste ultime spesso usate per
memorizzare numeri in virgola fissa per i calcoli con il coprocessore matematico.
Ovviamente l’area di memoria destinata a contenere i dati (variabili e array) non deve essere eseguita come codice. Essa è detta a r e a D a t i ,
e deve essere separata dall’a r e a C o d i c e , che contiene le istruzioni da eseguire.
S3Abacus – Architetture/Asm
.doc - 1684Kb - 12/set/2008 - 54 / 82
Nel modello COM, area Dati e area Codice risiedono nello stesso segmento, cioè in locazioni di memoria contigue. Se, come spesso si opta, l’area
Dati viene posta all’inizio della zona della memoria del programma, è necessario porre una istruzione iniziale di salto incondizionato per saltare
l’area Dati e avviare correttamente l’area Codice.
In altri casi si può optare allocando l’area Dati immediatamente dopo l’area di Codice.
Si veda il seguente esempio, in cui si allocano 8 byte contenenti i codici Ascii della stringa ‘HAL’, per stamparli sullo schermo:
a 100
jmp 105
db 'H'
db 'A'
db 'L'
mov bx,102
mov cx,3
mov dl,[bx]
mov ah,2
int 21
inc bx
loop 10b
int 20
; Istruzione iniziale per saltare l’area dati e indirizzare la prima istruzione dell’area codice (mov bx,102)
; Area Dati. Si allocano tre byte, I tre codici Ascii della stringa ‘HAL’
; Area Codice. In BX l’indirizzo del primo byte dell’area Dati
; Contatore del ciclo a 3 (3 caratteri da stampare)
; Indirizzamento indiretto. In DL il codice Ascii che si trova in area Dati all’indirizzo specificato in BX
; Sottofunzione 02h di MsDos, stampa carattere
; Interruzione sw MsDos
; Incremento dell’indirizzo in area Dati: La prossima cella contiene il prossimo codice Ascii da stampare
; Iterazione a partire dall’istruzione mov dl,[bx]
n hal.com
rcx
16
w
q
OUTPUT
C:\>hal
HAL
C:\>
Le tre locazioni di memoria così allocate, possono ospitare a tutti gli effetti delle variabili. Infatti in quelle locazioni i dati possono essere cambiati a
runtime per memorizzare altri valori. Nell’esempio, le tre locazioni vengono manipolate, aggiungendo una unità ad ogni cella, ottenendo i tre cosici
Ascii della stringa ‘IBM’, che poi verrà stampata a schermo:
a 100
jmp 105
db 'H'
db 'A'
db 'L'
mov bx,102
mov cx,3
mov al,[bx]
inc al
mov [bx],al
inc bx
loop 10b
mov bx,102
mov cx,3
mov dl,[bx]
mov ah,2
int 21
inc bx
loop 11a
int 20
n ibm.com
rcx
25
w
q
;
;
;
;
;
;
;
;
contatore a 3 (3 locazioni di memoria da manipolare)
lettura della variabile (locazione di memoria puntata da BX)
incremento di una unità
salvataggio della variabile (nella locazione di memoria puntata da BX)
prossima variabile in memoria
iterazione
indirizzo della prima locazione di memoria, per stampare a schermo
contatore a 3 (3 stampe da effettuare)
OUTPUT
C:\>ibm
IBM
C:\>
ALLINEAMENTO
Quando si alloca l’area Dati prima dell’area Codice, non sempre la prima istruzione di codice può essere collocata immediatamente dopo l’ultimo
byte dell’area Dati. Infatti il microprocessore interpreta i dati come codice e raggruppa i byte dei dati in base alle combinazioni di op. code che essi
formano ‘inconsapevolmente’.
Per evitare questa situazione, che impedisce l’avvio del codice, bisogna allineare i dati, ovvero aggiungere in coda all’area Dati alcuni byte che
consentano alla CPU di interpretare correttamente la prima istruzione di codice.
Si veda questa situazione nel codice che segue, che effettua l’input di tre caratteri, proponendo un prompt a forma di punto interrogativo, e indica a
video il maggiore con la stringa ‘il max vale:‘.
.doc - 1684Kb - 12/set/2008 - 55 / 82
S3Abacus – Architetture/Asm
In una prima versione, i dati della stringa causano il disallineamento del codice:
C:\>debug
-u 100
0D54:0100
0D54:0102
0D54:0103
0D54:0104
0D54:0107
0D54:0109
0D54:010B
0D54:010C
0D54:010D
0D54:0111
0D54:0112
0D54:0114
0D54:0116
m3.com
EB0A
49
6C
206D61
7820
7661
6C
65
3AB402B2
3F
CD21
B401
CD21
JMP
DEC
DB
AND
JS
JBE
DB
DB
CMP
AAS
INT
MOV
INT
010E
CX
6C
[DI+61],CH
0129
016C
6C
65
DH,[SI+B202]
21
AH,01
21
JMP 10E
49 (=’i’)
6C (=’l’)
20 (=’ ‘) 6D (=’m’) 61 (=’a’)
78 (=’x’) 20 (=’ ‘)
76 (=’v’) 61 (=’a’)
6C (=’l’)
65 (=’e’)
3A (=’:’) B402 (=MOV AH,02) B2
3F (=MOV AL,3F)
CD21 INT 21
B401 MOV AH,01
CD21 INT 21
Come si può notare, la sequenza di byte allocati in memoria che rappresentano la stringa ‘il max vale:’, non vengono correttamente allineati.
Infatti all’indirizzo seg:010D il byte 3A (il codice Ascii dei due punti) viene compattato dell’op. code dell’istruzione CMP DH,[SI+B202], inglobando
la prima effettiva istruzione di codice (MOV AH,02) e facendo fallire il salto iniziale ad essa (JMP 10E).
Aggiungendo un solo byte al termine dell’area Dati, con la pseudoistruzione DB 0, il codice viene riallineato e il salto incondizionato deve essere
aggiornato con una unità in più:
C:\>debug
-u 100
0D54:0100
0D54:0102
0D54:0103
0D54:0104
0D54:0107
0D54:0109
0D54:010B
0D54:010C
0D54:010D
0D54:010F
0D54:0111
0D54:0113
0D54:0115
0D54:0117
m3.com
EB0A
49
6C
206D61
7820
7661
6C
65
3A00
B402
B23F
CD21
B401
CD21
JMP
DEC
DB
AND
JS
JBE
DB
DB
CMP
MOV
MOV
INT
MOV
INT
010F
CX
6C
[DI+61],CH
0129
016C
6C
65
AL,[BX+SI]
AH,02
DL,3F
21
AH,01
21
JMP 10F
49 (=’i’)
6C (=’l’)
20 (=’ ‘) 6D (=’m’) 61 (=’a’)
78 (=’x’) 20 (=’ ‘)
76 (=’v’) 61 (=’a’)
6C (=’l’)
65 (=’e’)
3A (=’:’) 00 (=per l’allineamento)
B402 MOV AH,02
B23F MOV AL,3F
CD21 INT 21
B401 MOV AH,01
CD21 INT 21
Il programma completo:
a 100
JMP 10f
DB 'Il max vale:'
db 0
MOV AH,02
MOV DL,3f
INT 21
MOV AH,01
INT 21
MOV BX,10e
MOV [BX],AL
MOV AH,02
MOV DL,3f
INT 21
MOV AH,01
INT 21
CMP AL,[BX]
JL 12e
MOV [BX],AL
MOV AH,02
MOV DL,3f
INT 21
MOV AH,01
INT 21
CMP AL,[BX]
JL 13e
MOV [BX],AL
MOV CX,0d
max
MOV AH,02
MOV BX,102
MOV DL,[BX]
; Si salta l’area Dati per arrivare all’istruzione MOV AH,02
; area Dati
; byte di allineamento. Usato anche come variabile per contenere il massimo
; sottofunzione per la stampa di un carattere
; codice Ascii del punto interrogativo (il prompt prima dell’input)
; Interruzione sw Msdos stampa un carattere
; sottofunzione per l’input di un carattere da tastiera
; Interruzione sw Msdos
; in BX l’indirizzo del byte di memoria che fungerà da variabile (il byte di allineamento)
; memorizzazione dell’input nella variabile
; secondo prompt per il secondo input
; secondo input
; confronto tra l’input e la variabile in memoria
; se inferiore, non memorizzo (salto all’istruzione MOV AH,02)
; altrimenti salvo nella variabile il nuovo massimo
; terzo prompt per il terzo input
; terzo input
; confronto tra l’input e la variabile in memoria
; se inferiore, non memorizzo (salto all’istruzione MOV CX,0d)
; altrimenti salvo nella variabile il nuovo massimo
; contatore a 13 (0dh=13) per i 12 caratteri della stringa da stampare più il byte della variabile che contiene il
; sottofunzione di stampa carattere
; indirizzo iniziale dei caratteri da stampare
; in DL il carattere da stampare, prelevato dalla memoria
S3Abacus – Architetture/Asm
INT 21
INC BX
LOOP 146
INT 20
.doc - 1684Kb - 12/set/2008 - 56 / 82
; interruzione MsDos
; prossimo indirizzo per il prossimo carattere
; iterazione dalla istruzione MOV AH,02
n m3.COM
rcx
4f
w
q
C:\>m3
?1?5?3Il max vale:5
C:\>m3
?6?4?3Il max vale:6
C:\>m3
?1?2?9Il max vale:9
C:\>
OUTPUT
S3Abacus – Architetture/Asm
.doc - 1684Kb - 12/set/2008 - 57 / 82
ASSEMBLY CON TASM
Naturalmente la programmazione dell’assembler x-86 con Debug ha senso solo per ragioni didattiche.
Nella realtà, aldilà di brevi frammenti di codice di test, Debug non viene utilizzato per scrivere programmi, e i programmatori si affidano a veri e
propri ambienti di sviluppo su linea di comando, i più noti dei quali sono T A S M (Borland) e M A S M (Microsoft). Un ambiente freeware molto
apprezzato è anche N A S M , in ogni caso tutti prodotti ormai facilmente reperibili senza alcun costo.
La scelta di TASM deriva dal fatto che la sintassi MASM è correttamente interpretata da TASM, mentre non è vero il viceversa.
Come si è verificato utilizzando Debug, la principale difficoltà nello scrivere programmi in assembler è la gestione degli indirizzi e il loro ricalcolo ad
ogni modifica del programma. Inoltre tali ambienti offrono la possibilità di utilizzare decine di pseudoistruzioni e direttive che rendono la
programmazione assembler molto efficace e veloce.
La gestione semplificata degli indirizzi viene ottenuta da questi ambienti tramite l’uso delle e t i c h e t t e (label) al posto degli indirizzi numerici, e
del concetto di d o p p i a p a s s a t a dell’assemblatore, che è il programma che analizza il file sorgente contenente il codice assembler e le
pseudoistruzioni.
Con la doppia passata l’assemblatore traduce correttamente tutte le etichette nei corrispondendi indirizzi numerici senza che il programmatore
debba più preoccuparsene, cosicchè nei files sorgenti scritti per questi ambienti, l’uso degli indirizzi numerici è praticamente abolito.
L’etichetta, inoltre, rende il codice molto più comprensibile, dato che gli identificatopri di etichetta sono scelti dal programmatore e possono
descrivere, tramite il loro nome, la funzione svolta dall’indirizzo simbolico che rappresentano.
STRUTTURA DEI PROGRAMMI
Come si è visto in precedenza, i programmi contengono, generalmente, almeno due aree distinte di istruzioni: l’area del Codice e l’area Dati.
Questo vale per qualsiasi programma scritto in qualsiasi linguaggio.
Oltre a queste due aree, i programmi ne contengono altre, altrettanto impostanti: a r e a d i S t a r t u p , a r e a d e l l o S t a c k e a r e a
dello Heap
Area di Startup
E’ la zona iniziale di ogni programma, generata da un programma speciale denominato l i n k e r (correlatore) che si incarica di scrivere le prime
istruzioni da eseguire subito dopo che il Sistema Operativo avvia il processo a partire dal programma su disco. Questo codice deve essere coerente
con le regole di esecuzione del Sistema Operativo, da cui riceve il controllo, e deve essere in grado di avviare la prima istruzione eseguibile scritta
dal programmatore (e n t r y p o i n t ) in modo corretto, fornendo i dati di avvio ereditati dal Sistema Operativo, come ad esempio i p a r a m e t r i
s u r i g a d i c o m a n d o . Solo i files eseguibili .COM non possiedono un’area di Startup.
Area di Codice
E’ la zona del programma che contiene le istruzioni da eseguire durante l’esecuzione, cioè durante il r u n t i m e del programma.
L’area di codice è scritta espressamente dal programmatore tramite le regole della sintassi.di un linguaggio di programmazione, nel nostro caso le
regole della programmazione assembler x-86. Essa si trova immediatamente dopo l’area di Startup, dalla quale eredita il controllo all’avvio
dell’esecuzione del programma.
Area Dati
E’ la zona in cui il programmatore alloca i dati tramite istruzioni presenti nell’area Codice. Qui trovano posto le v a r i a b i l i g l o b a l i
s t a t i c h e dei programmi, cioè quelle locazioni di memoria disponibili durante tutto il runtime. Questa area è sia di lettura che di scrittura, a
differenza dell’area di Codice e di Startup che sono esclusivamente aree di lettura.
Area dello Heap
E’ una zona opzionale, in cui durante il runtime, il programmatore, tramite istruzioni ben precise, alloca temporaneamente della memoria per far
posto a variabili la cui dimensione è accertabile solo durante l’esecuzione (es. la dimensione di una stringa in input). E’ anche detta m e m o r i a
d i n a m i c a , dato che la sua dimensione non è prefissata e può essere anche allocata e deallocata più volte durante il runtime. La gestione
dell’area di Heap viene ottenuta tramite istruzioni in area Codice, che richiedono i servizi di allocazione/deallocazione al Sistema Operativo.
Area dello Stack
E’ una zona gestita automaticamente dai compilatori su tipiche tecniche di programmazione scritte in area di Codice dal programmatore, come la
dichiarazione di v a r i a b i l i l o c a l i e il p a s s a g g i o d i p a r a m e t r i a l l e p r o c e d u r e : a fronte di questi costrutti, i compilatori
gestiscono l’area di Stack in modo trasparente al programmatore, allocando e deallocando le variabili locali e i parametri passati alle procedure.
Solo nella programmazione in Assembler è possibile gestire direttamente l’area di Stack con opportune istruzioni.
CICLO DI VITA DI UN PROGRAMMA
La creazione, lo sviluppo, l’esecuzione e la messa a punto di un programma, generalmente, segue un certo numero di fasi caratterizzate da attività
specifiche, tempi specifici, applicazioni di supporto specifiche, errori specifici e file specifici.
E’ possibile sintetizzare il ciclo di vita di un programma tramite un diagramma e una tabella che riportano fasi, tempi, applicazioni, files ed errori
relativi ad ogni fase.
.doc - 1684Kb - 12/set/2008 - 58 / 82
S3Abacus – Architetture/Asm
EDIT
ASSEMBLAGGIO
DESIGN
ASSEMBLING
DesignTime
Editor
Sorgenti
MAIN.ASM
LIB1.ASM
LIB2.ASM
CORRELAZIONE
Von Neumann
CARICAMENTO
ESECUZIONE
MESSA A PUNTO
LOADING
RUNTIME
DEBUGGING
LINKING
CompileTime
Assemblatore
Linker
Oggetto
Eseguibile
MAIN.O BJ
LIB1.OBJ
LIB2.OBJ
LoadTime
SistemaOp erativo
Processo
RunTime
DebugTime
Debugger
MAIN.EXE
Fase di Edit
Il programmatore scrive il testo del programma (sorgente) con la sintassi di un linguaggio di programmazione. Spesso i programmi sono costituiti da
più sorgenti, ma solo uno contiene l’e n t r y p o i n t del programma. I rimanenti sono denominati l i b r e r i e d i c o d i c e .
Il programma usato in questa fase è un E d i t o r , spesso integrato in un I D E (Integrated Development Equipment). L’attività del programmatore in
questa fase è detta D e s i g n T i m e . Gli errori più frequenti a Design Time riguardano il formato dei files (es. i files devono essere rigorosamente
files di testo), la loro irreperibilità o la loro corruzione.
Fase di Compilazione
Una volta completato un modulo sorgente, esso deve essere assemblato, ovvero le istruzioni e le pseudoistruzioni presenti nei sorgenti in
linguaggio simbolico ad alto livello, devono essere trasformate in assembler, a basso livello. Ogni file sorgente quindi viene ridotto, da un
programma di supporto denominato a s s e m b l a t o r e , a un file binario corrispondente (detto anche file o g g e t t o ).
Dopo l’assemblaggio, è necessaria la c o r r e l a z i o n e o l i n k i n g , ad opera di un secondo programma a supporto, denominato l i n k e r .
Il linker collega tutti i files oggetto in uno solo, e genera il file eseguibile (t a r g e t della compilazione), aggiungendovi, nella sua parte iniziale, la
porzione di Startup (cfr. Struttura dei programmi) e l’eventuale Header (cfr. Formato degli eseguibili e Rilocazione). Le due fasi di assemblaggio e
linking sono spesso riunite in un unico passo, denominato C o m p i l e T i m e .
Durante il Compile Time si possono verificare i tipici errori di sintassi (del linguggio scelto) che sono sempre segnalati dai programmi compilatori,
sottoforma di errori e/o warning.
Fase di Caricamento
Una volta su disco, il programma eseguibile deve essere caricato in memoria dal Sistema Operativo per essere poi trasformato in processo.
Il SO legge lo header del file eseguibile e carica in memoria il programma, dopodichè cede il controllo al codice di Startup dell’eseguibile. Spesso i
SO creano una zona di memoria di collegamento tra programma e Sistema Operativo (cfr. PSP in Formato degli eseguibili e Rilocazione) prima di
cedere il controllo.
Tipici errori della fase di L o a d T i m e sono errati formati degli header, o l’impossibilità del caricamento per scarsità di memoria.
Fase di Esecuzione
Il R u n t i m e è il tempo durante il quale il processo opera in CPU, dalla prima istruzione di codice all’ultima prevista dal programmatore. Tipici
errori di RunTime sono le divisioni per zero, i loop infiniti, le terminazioni anomale per mancanza o incongruenza delle risorse richieste dal
programma.
Fase di messa a Punto
Il Runtime può anche essere avviato tramite un programma speciale, denominato d e b u g g e r .
In questo caso il debugger carica ed esegue il programma nelle modalità impostate dal programmatore, ad esempio p a s s o p a s s o (per
verificare il flusso dell’esecuzione) o tramite b r e a k p o i n t , ovvero sospensioni dell’esecuzione su istruzioni critiche, per eplorare lo stato di
registri, variabili e memoria (w a t c h ). Tutto ciò al fine di individuare le cause di eventuali malfunzionamenti riscontrati al Runtime.
CICLO DI VITA DI UN PROGRAMMA TASM
Allo stesso modo, l’ambiente di sviluppo TASM prevede le fasi tipiche del ciclo di vita di un programma, fornendo i relativi programmi di supporto.
Bisogna tener presente che, malgrado si sia scelto di sviluppare direttamente in Assembler, e quindi in apparenza potrebbe sembrare inutile una
fase di compilazione, l’uso delle fondamentali due passate per risolvere il problema degli indirizzi numerici, rende necessaria la presenza di un
programma assemblatore e un programma linker.
I sorgenti per TASM possono essere scritti con un qualsiasi editor di testo (es. Notepad di Windows), avendo cura di usare sempre l’estensione
.ASM per ogni modulo sorgente; il programma assemblatore si chiama TASM.EXE e il programma linker si chiama TLINK.EXE. Il programma
debugger, infine, si chiama Turbo Debugger (TD.EXE).
In particolare, per usare TASM, sono sufficienti i seguenti files, reperibili anche all’interno delle cartelle dei compilatori Borland C 3.1 e/o Turbo
Pascal 7.0: TASM.EXE, TLINK.EXE, TD.EXE, DPMILOAD.EXE, DPMIMEM.DLL
Limitandoci per ora a programmi composti da un unico file sorgente (es. PIPPO.ASM), scritto per ottenere un eseguibile di tipo COM (es.
PIPPO.COM), assemblaggio, linking e debugging si avviano nei seguenti modi:
.doc - 1684Kb - 12/set/2008 - 59 / 82
S3Abacus – Architetture/Asm
; l’assemblatore tasm.exe genera il file oggetto pippo.obj dal file sorgente
C:\>tasm pippo
pippo.asm
C:\>tlink pippo /t
pippo.obj
C:\>pippo
C:\>td pippo.com
a punto
; il linker tlink.exe genera il file eseguibile pippo.com dal file oggetto
; MsDos carica in memoria il file eseguibile pippo.com per l’esecuzione…
; … oppure il debugger td.exe carica il file eseguibile pippo.com per la messa
Si noti come tasm e tlink non abbiano bisogno che venga specificata l’estensione dei file in ingresso, rispettivamente pippo.asm per tasm,
pippo.obj per tlink.
La fase di correlazione operata da tlink viene eseguita con il parametro /t per indicare a tlink di non creare lo header del file. Infatti gli eseguibili
di tipo COM sono gli unici che mancano sia di Header che di area di Startup.
Se il file sorgente contiene errori di sintassi, l’assemblatore mostra un messaggio esplicito che indica il tipo di errore e la riga su cui è stato rilevato.
E’ ovvio che in presenza di errori, il file oggetto non viene creato, interrompendo la fase di creazione del file eseguibile.
Allo stesso modo, ma più raramente, il linker mostra un messaggio d’errore nel caso di incongruenza nella correlazione..
Nell’esempio, una sequenza corretta di creazione e lancio di un programma assembler con TASM (il programma che calcola il massimo di tre
caratteri, riscritto per TASM):
OUTPUT
C:\>tasm m3
Turbo Assembler Version 3.1 Copyright (c) 1988, 1992 Borland International
Assembling file:
Error messages:
Warning messages:
Passes:
Remaining memory:
m3.asm
None
None
1
452k
C:\>tlink m3 /t
Turbo Link Version 5.1 Copyright (c) 1992 Borland International
C:\>m3
?1?5?3Il max vale:5
C:\>
In quest’altro caso, una esempio che riporta una tipica videata di errore riscontrato durante l’assemblaggio, causato da un errato uso dell’istruzione
MOV DL,0a3fh (in DL possono essere memorizzati solo byte, non word):
C:\>tasm m3
Turbo Assembler
OUTPUT
Version 3.1
Copyright (c) 1988, 1992 Borland International
Assembling file:
m3.asm
**Error** m3.asm(22) Constant too large
Error messages:
1
Warning messages: None
Passes:
1
Remaining memory: 452k
C:\>
Si noti che l’errore si trova sulla riga 22 del codice sorgente, come indicato dal messaggio d’errore.
Infine, una tipica videata del debugger TD, applicato al file m3.com:
C:\>td m3.com
OUTPUT
File Edit View Run Breakpoints Data Options Window Help
READY
╔═[■]═CPU 80486═══════════════════════════════════════════════╤═══════1════[↕]═╗
║ cs:0100►EB0E
jmp
0110 ↓
│ ax 0000
│c=0║
║ cs:0102 90
nop
│ bx 0000
│z=0║
║ cs:0103 49
dec
cx
│ cx 0000
│s=0║
║ cs:0104 6C
insb
│ dx 0000
│o=0║
║ cs:0105 206D61
and
[di+61],ch
│ si 0000
│p=0║
║ cs:0108 7820
js
012A
│ di 0000
│a=0║
║ cs:010A 7661
jbe
016D
│ bp 0000
│i=1║
║ cs:010C 6C
insb
│ sp FFFE
│d=0║
║ cs:010D 653A00
cmp
al,gs:[bx+si]
│ ds 547F
│
║
║ cs:0110 B402
mov
ah,02
│ es 547F
│
║
║ cs:0112 B23F
mov
dl,3F
│ ss 547F
│
║
║ cs:0114 CD21
int
21
│ cs 547F
│
║
║ cs:0116 B401
mov
ah,01
│ ip 0100
│
║
║ cs:0118 CD21
int
21
│
│
║
║ cs:011A BB0F01
mov
bx,010F
│
│
║
╟─────────────────────────────────────────────────────────────┼────────────┴───╢
║ ds:0100 EB 0E 90 49 6C 20 6D 61 Ù♫ÉIl ma
▲ ss:0000 20CD ║
║ ds:0108 78 20 76 61 6C 65 3A 00 x vale:
■ ss:FFFE►0000 ║
.doc - 1684Kb - 12/set/2008 - 60 / 82
S3Abacus – Architetture/Asm
║ ds:0110 B4 02 B2 3F CD 21 B4 01 ┤☻▓?═!┤☺
▒ ss:FFFC 0000 ║
║ ds:0118 CD 21 BB 0F 01 88 07 B4 ═!╗☼☺ê•┤
▒ ss:FFFA 0000 ║
║ ds:0120 02 B2 3F CD 21 B4 01 CD ☻▓?═!┤☺═
▼ ss:FFF8 0000 ║
╚═════════════════════════════════════════════════════════════╧═══════════════─┘
F1-Help F2-Bkpt F3-Mod F4-Here F5-Zoom F6-Next F7-Trace F8-Step F9-Run F10-Menu
C:\>
SCRIVERE UN PROGRAMMA EXE
Come si è visto, l’assembler x-86 prevede che I programmi siano sviluppati, attraverso le aree tipiche di un programma quali area di Codice, area
Dati e area di Stack, all’interno di aree di memoria contenute in segmenti, cioè zone di memoria ampie 64 Kbytes. Per gli eseguibili di tipo COM,
tutte le tre aree tipiche risiedono nello stesso segmento, che è anche l’unico segmento possibile per questo tipo di programmi; inoltre gli eseguibili di
tipo COM non possiedono l’area di Startup, essendo caricati in memoria dal loader di Sistema Operativo con caricamento rilocante statico (cfr.
Formato degli eseguibili e Rilocazione: EXE e COM).
Gli eseguibili di tipo EXE, i più comuni e diffusi, hanno invece la necessità che le tre aree siano assegnate a rispettivi segmenti in memoria,
potendo, anche in questo caso, essere lo stesso segmento di memoria, ma tuttavia con la necessità di essere esplicitati espressamente nel codice
così da poter permettere il caricamento rilocante dinamico.
Ecco allora come si presenta un generico programma sorgente x-86 per MsDos destinato a diventare un eseguibile di tipo EXE (il solito programma
per il calcolo del massimo tra tre caratteri in input).
Il codice presenta numerose e t i c h e t t e (in blu, per evitare l’uso di indirizzi numerici) e p s e u d o i s t r u z i o n i (in verde, per indicare
all’assemblatore le intenzioni del programmatore).
SEG_DATI segment
; definizione del segmento per l’area Dati
MSG DB "Il max vale:"
VAR DB 0
ends
SEG_STACK segment stack
; definizione del segmento per l’area di Stack
ends
SEG_CODICE segment
; definizione del segmento per l’area Codice
assume cs:SEG_CODICE,
ss:SEG_STACK,
ds:SEG_DATI,
es:SEG_DATI
; associazione dei nomi dei segmenti ai registri di segmento
START:
; etichetta che segnala l’inizio dell’area Codice
; impostazioni a runtime dei valori di segmento ai registri di segmento
mov
mov
mov
mov
mov
mov
mov
int
mov
int
lea
mov
mov
mov
int
mov
int
cmp
jl
mov
SALTA1:
mov
mov
int
mov
int
cmp
jl
mov
SALTA2:
mov
ax, SEG_DATI
ds,ax
es, ax
ax, SEG_STACK
ss, ax
ah,02
dl,3fh
21h
ah,01
21h
bx,VAR
[bx],al
ah,02
dl,3fh
21h
ah,01
21h
al,[bx]
SALTA1
[bx],al
ah,02
dl,3fh
21h
ah,01
21h
al,[bx]
SALTA2
[bx],al
cx,0dh
; impostazioni a runtime dei valori di segmento ai registri di segmento
; codice del programma
; riferimento di una etichetta contenente un indirizzo di memoria in area Dati
; riferimento di una etichetta contenente un indirizzo di memoria in area Codice
.doc - 1684Kb - 12/set/2008 - 61 / 82
S3Abacus – Architetture/Asm
CICLO:
ends
lea bx,MSG
mov ah,02
mov dl,[bx]
int 21h
inc bx
loop CICLO
mov ax, 4c00h
int 21h
; chiusura dell’etichetta che segnalava l’inizio dell’area Codice e chiusura del file
end START
Le tre etichette principali SEG_DATI, SEG_STACK e SEG_CODICE danno il nome ai tre segmenti di memoria di questo programma, segmenti
inizializzati dalla pseudoistruzione segment/ends. In effetti questo programma non usa una area dello Stack, quindi in linea di principio non
sarebbe necessario specificarla.
Nell’area Codice, infine, la pseudoistruzione assume cs:,ds:,ss:,es: consente di associare le tre aree ai registri di segmento appropriati.
Si noti come i registro Extra (ES) sia impostato sullo stesso valore del registro dati DS.
Infine, come istruzioni effettive, bisogna impostare i registri di segmento con i valori indicati tramite le pseudoistruzioni.
Si noti come non sia possibile impostare un registro di segmento direttamente, ma solo tramite un registro d’appoggio (in questo caso AX).
La direttiva end a fine file è necessaria affinchè l’assemblatore sappia con precisione quando il file sorgente è terminato.
Molto interessante l’uso delle etichette VAR, MSG, SALTA1, SALTA2 e CICLO esse consentono di evitare l’uso di indirizzi numerici, lasciando il
compito di calcolarli adeguatamente all’assemblatore.
Le etichette che segnalano indirizzi all’interno dell’area codice sono dette l a b e l , e devono terminare con i due punti (:). Quando le etichette sono
usate nel codice, invece, si parla di r i f e r i m e n t o all’etichetta.
Attenzione
A. Il nome di fantasia di una label può apparire una sola volta nel codice (mentre i riferimenti sono liberi), altrimenti l’assemblatore non saprebbe a
quale indirizzo associarne il nome.
B. La distanza tra l’etichetta e ogni suo riferimento tramite un salto condizionato rimane vincolata a un massimo di 128 byte. Ciò deriva dal limite
progettuale delle istruzioni di salto condizionato.
Entrambi questi limiti possono essere risolti dall’assemblatore attraverso speciali direttive LOCALS e JUMPS (cfr. Direttive per la programmazione e
Librerie)
Infine, l’etichetta START, con la sua chiusura a fine sorgente, è necessaria affinchè l’assemblatore possa considerare terminata l’area di Codice del
programma (si tratta di una specifica che indica il termine del modulo principale che contiene il punto di ingresso dell’area Codice di un programma)
Le etichette sono identificativi ideati dal programmatore, ovvero nomi di fantasia. Esse non possono né cominciare con un numero, né riportare
spazi o caratteri speciali. Va da sé che il programmatore usi nomi significativi per esse, magari in base a indicazioni precise che vengono dette
regole di naming.
La compilazione di un sorgente destinato a diventare un file eseguibile di tipo EXE:
C:\>tasm pippo
pippo.asm
C:\>tlink pippo
pippo.obj
; l’assemblatore tasm.exe genera il file oggetto pippo.obj dal file sorgente
; il linker tlink.exe genera il file eseguibile pippo.exe dal file oggetto
MODELLI DI MEMORIA
Questo sorgente genera quindi un file EXE in cui area Dati, area Codice e area dello Stack risiedono su tre segmenti separati.
Questo programma, pertanto, potrà essere ampio al più 64KB + 64KB = 128 KB, di cui 64 KB riservati al codice, e 64KB riservati ai dati.
Per superare questi limiti, il programmatore deve usare in modo spesso anche complesso le direttive segment/ends e assume all’interno del
codice, così da ottenere programmi che possano operare su più segmenti di codice e più segmenti di dati.
Per evitare la gestione esplicita di queste direttive di segmento, l’assemblatore TASM mette a disposizione una pseudoistruzione molto efficace che
semplifica l’uso delle direttive di segmento: con la pesudoistruzione M O D E L , infatti, il programmatore può decidere il modello di memoria
desiderato per il proprio programma, senza più preoccuparsi di gestire i segmenti.
Direttiva
MODEL
Sintassi:
.MODEL tipo
Scopo:
L’assemblatore regola la generazione dei segmenti di Codice, Dati e Stack in base al tipo indicato
T I N Y , un solo segmento comune per area Codice, Dati e Stack. Dedicato ai programmi eseguibili di tipo COM
S M A L L , un segmento per area Codice, un segmento per area Dati e Stack, solo per eseguibili di tipo EXE
M E D I U M , più segmenti per l’area Codice, un solo segmento per area Dati e area Stack.
C O M P A C T , più segmenti per l’area Dati, un solo segmento per area Codice e area Stack.
L A R G E , più segmenti per l’area Codice, più segmenti per l’area di Dati, più segmenti per l’area di Stack.
H U G E , come LARGE, con la possibilità che un dato contiguo possa essere maggiore di un segmento (es. un array >
64KB).
Esempi:
.MODEL TINY
.MODEL SMALL
.MODEL LARGE
.doc - 1684Kb - 12/set/2008 - 62 / 82
S3Abacus – Architetture/Asm
Nota:
In realtà esiste un sesto modello di memoria, denominato FLAT, che non prevede segmentazione e usato solo nelle piattaforme a 32bit.
Ecco come diventa lo stesso sorgente di poco fa in sintassi semplificata:
; direttiva di segmento per il modello di memoria desiderato
; direttiva per la definizione del segmento dell’area di Stack
; direttiva per la definizione del segmento dell’area Dati
.MODEL SMALL
.STACK
.DATA
MSG DB "Il max vale:"
VAR DB 0
; direttiva per la definizione del segmento dell’area Codice
.CODE
mov ax,@DATA
mov ds,ax
mov ah,02
mov dl,3fh
int 21h
mov ah,01
int 21h
lea bx,VAR
mov [bx],al
mov ah,02
mov dl,3fh
int 21h
mov ah,01
int 21h
cmp al,[bx]
jl SALTA1
mov [bx],al
SALTA1:
mov ah,02
mov dl,3fh
int 21h
mov ah,01
int 21h
cmp al,[bx]
jl SALTA2
mov [bx],al
SALTA2:
mov cx,0dh
lea bx,MSG
CICLO:
mov ah,02
mov dl,[bx]
int 21h
inc bx
loop CICLO
mov ax, 4c00h
int 21h
end
; impostazioni a runtime del valore di segmento per l’area Dati
; codice del programma
; uso di una etichetta contenente un indirizzo di memoria in area Dati
; uso di una etichetta contenente un indirizzo di memoria in area Codice
Le direttive .MODEL, .DATA, .STACK e .CODE sono dette d i r e t t i v e d i s e g m e n t o s e m p l i f i c a t e , e rendono i sorgenti assembly
molto più semplici da gestire, evitando al programmatore lo sforzo di definire i vari segmenti del programma in modo esplicito.
L’unica accortezza da ricordare è il caricamento esplicito del segmento Dati tramite l’etichetta di sistema @DATA
SCRIVERE UN PROGRAMMA COM
Per imparare la programmazione assembly x-86 è più che sufficiente sviluppare programmi eseguibili di formato COM, che sono anche più semplici
nella struttura, evitando di usare pseudoistruzioni e istruzioni per la definizione e il caricamento dei registri di segmento.
Spesso, infatti, anche i programmi eseguibili di formato EXE vengono ridotti a COM, per sfruttarne la semplicità e la velocità di caricamento, con un
apposito applicativo del SO, denominato E X E 2 B I N .
Ecco i codici sorgenti del solito programma che calcola il massimo di tre caratteri in input, nella versione con direttive di segmento e direttive di
segmento semplificate:
Sorgente per file COM
Sorgente per file COM
Con direttive di segmento
Con direttive di segmento semplificate
SEG_UNICO segment
assume CS:SEG_UNICO
; unico segmento per Area Dati e
Codice
assume DS:SEG_UNICO
ORG 100H
; il codice inizia a 100h, e non a
.CODE
; modello di memoria per files
COM
; definizione area Codice e Dati
ORG 100h
; il codice inizia a 100h e non a
.MODEL TINY
.doc - 1684Kb - 12/set/2008 - 63 / 82
S3Abacus – Architetture/Asm
zero
zero
START: jmp MAIN
; si salta l’area Dati
START:
MSG DB "Il max vale:"
VAR DB 0
; area Dati
MSG DB "Il max vale:"
VAR DB 0
; area Dati
MAIN:
; area Codice
MAIN:
; area Codice
SALTA1:
SALTA2:
CICLO:
mov
mov
int
mov
int
lea
mov
mov
mov
int
mov
int
cmp
jl
mov
ah,02
dl,3fh
21h
ah,01
21h
bx, VAR
[bx],al
ah,02
dl,3fh
21h
ah,01
21h
al,[bx]
SALTA1
[bx],al
mov
mov
int
mov
int
cmp
jl
mov
ah,02
dl,3fh
21h
ah,01
21h
al,[bx]
SALTA2
[bx],al
SALTA1:
SALTA2:
mov cx,0dh
lea bx,msg
CICLO:
mov ah,02
mov dl,[bx]
int 21h
inc bx
loop CICLO
int 20h
; fine segmento unico
; fine programma
ends
end START
jmp MAIN
mov
mov
int
mov
int
lea
mov
mov
mov
int
mov
int
cmp
jl
mov
ah,02
dl,3fh
21h
ah,01
21h
bx, VAR
[bx],al
ah,02
dl,3fh
21h
ah,01
21h
al,[bx]
SALTA1
[bx],al
mov
mov
int
mov
int
cmp
jl
mov
ah,02
dl,3fh
21h
ah,01
21h
al,[bx]
SALTA2
[bx],al
; si salta l’area Dati
mov cx,0dh
lea bx,msg
mov ah,02
mov dl,[bx]
int 21h
inc bx
loop CICLO
int 20h
end START
; fine programma
Si noti che, dovendo risiedere dati e codice nello stesso segmento, come sempre per i files eseguibili COM, la prima istruzione di codice deve
saltare l’area Dati con un salto incondizionato.
Direttiva
ORG
Sintassi:
ORG valore
Scopo:
La fondamentale direttiva O R G (Origine) indica all’assemblatore che l’origine degli indirizzi del segmento deve valere
valore e non zero.
Esempi:
ORG 100h
Nota:
Si impone che il Location Counter dell’assemblatore inizi il conteggio degli indirizzi a partire dal valore 100h e non dal valore 0. Tipico
caso dell’assemblaggio dei files .COM.
Con la direttiva ORG 100h si impone che gli indirizzi dell’eseguibile di tipo COM comincino dal 256 byte (=100h) del segmento di codice, per saltare
l’area del PSP (cfr. Formato degli eseguibili e Rilocazione)
La compilazione di un sorgente destinato a diventare un eseguibile di tipo COM deve ricordare al linker di non immettere l’area di startup
nell’eseguibile tramite l’o p z i o n e / t , pertanto i sorgenti di questo tipo devono essere compilati nel seguente modo:
C:\>tasm pippo
pippo.asm
C:\>tlink pippo /t
pippo.obj
; l’assemblatore tasm.exe genera il file oggetto pippo.obj dal file sorgente
; il linker tlink.exe genera il file eseguibile pippo.com dal file oggetto
S3Abacus – Architetture/Asm
.doc - 1684Kb - 12/set/2008 - 64 / 82
ASSEMBLY X-86 AVANZATO
.doc - 1684Kb - 12/set/2008 - 65 / 82
S3Abacus – Architetture/Asm
MACRO
Come sempre accade nella programmazione, speciali valori sono così importanti da meritarsi un nome proprio, così da poterli velocemente
individuare all’interno del codice sorgente. Assegnare il nome ad un valore è altresì fondamentale per questioni di manutenzione del codice. Infatti,
se il valore dovesse essere modificato, l’uso di un nome simbolico consente di modificare il valore solo una volta, avendo usando solo il nome del
valore all’interno del codice. Assegnare un nome a un valore significa definire una m a c r o c o s t a n t e .
MACRO COSTANTI
Si veda questo breve codice che stampa a schermo la cifra 0 e, a capo, la cifra 1:
.MODEL TINY
.CODE
ORG 100h
START:
mov ah,02
mov dl,'0'
int 21h
mov ah,02
mov dl,0dh
int 21h
mov dl,0ah
int 21h
mov ah,02
mov dl,'1'
int 21h
int 20h
end START
; il codice Ascii dello zero (30h) può essere scritto con questa sintassi derivata dal C: 30h = ‘0’
; stampa a video il carattere zero
; il codice Ascii 0dh (=13d) è il Carriage Return (CR). Sposta il cursore all’inizio della riga corrente
; il codice Ascii 0ah (=10d) è il Line Feed (LF). Sposta il cursore nella riga sottostante
; stampa a video il carattere uno
OUTPUT
C:\>acapo
0
1
C:\>
La stampa a schermo dei caratteri Ascii speciali 0dh (=13d) e 0ah (=10d), detti rispettivamente CR (C a r r i a g e R e t u r n ) e LF (L i n e F e e d ),
provoca l’effetto dell’ “andare a capo”.
Siccome si tratta di valori speciali, usati per un compito dedicato, è buona norma nominarli e usare, nel codice sorgente, il loro nome.
Nominare un valore, significa creare una costante Macro, cioè un nome simbolico associato ad un valore: quando l’assemblatore incontra quel
nome simbolico nel sorgente, sostituisce il simbolo con il valore corrispondente (e s p a n s i o n e d e l l a m a c r o ).
PseudoIstruzione EQU
Sintassi:
nome EQU espressione
Scopo:
Crea il nome che sarà sostituito con espressione durante l’assemblaggio.
Esempi:
CR EQU 0dh
Nota:
RIGA EQU 80
COLONNA EQU 25
SCHERMO EQU RIGA*COLONNA
Normalmente i simboli delle costanti macro sono scritti in maiuscolo.
Si noti che l’assemblatore, durante la prima passata, può ricalcolare valori costanti tramite operatori aritmetici (+,-,*,/)
e sostituire, al simbolo, il valore costante ricalcolato. La pseudoistruzione EQU è del tutto equivalente alla direttiva
#define del linguaggio C.
Ovviamente la pseudoistruzione EQU non genera alcuna riga di codice macchina, essendo una direttiva. L’assemblatore si limita a sostituire ai
simboli individuati nel sorgente, i rispettivi valori costanti durante il compile time. Per questo motivo le costanti EQU vanno citate prima dell’inizio
del codice.
Il programma sottostante è equivalente al precedente; usa EQU per indicare costanti speciali. L’output rimane invariato
; Direttive EQU per CR e LF
CR EQU 13
LF EQU 10
.MODEL TINY
.CODE
ORG 100h
START:
mov
mov
int
mov
mov
int
ah,02
dl,'0'
21h
ah,02
dl,CR
21h
; il simbolo CR sarà sostituito con il valore 13 (=0dh)
.doc - 1684Kb - 12/set/2008 - 66 / 82
S3Abacus – Architetture/Asm
mov
int
mov
mov
int
int
end START
dl,LF
21h
ah,02
dl,'1'
21h
20h
; il simbolo LF sarà sostituito con il valore 10 (=0ah)
; stampa a video il carattere uno
MACRO DI CODICE
Dovendo stampare diverse righe di zeri e uni, nel codice dovremmo usare varie volte le sei righe di codice che stampano a schermo un ‘acapo’.
Il codice ripetuto appesantisce il sorgente e lo rende meno leggibile, cosicchè è possibile riunire un blocco di codice sorgente e assegnargli un
nome simbolico, citando il solo nome nel codice. In questo caso si parla di m a c r o d i c o d i c e .
Come prima, durante la prima passata, l’assemblatore, quando incontra il nome simbolico di una macro di codice, sostituisce ad essa l’intero blocco
di codice corrispondente (e s p a n s i o n e d e l l a m a c r o ), operazione di nuovo eseguita a compile time.
PseudoIstruzione MACRO / ENDM
Sintassi:
nome MACRO
(codice)
ENDM
Scopo:
Crea il blocco di (codice) che sarà sostituito al simbolo nome durante l’assemblaggio.
Esempi:
BEEP MACRO
mov ah,2
mov dl, 7
int 21h
ENDM
Nota:
ACAPO MACRO
mov ah,2
mov dl, 13
int 21h
mov dl, 10
Int 21h
ENDM
Negli esempi, una macro BEEP che emette un suono (infatti il codice Ascii speciale 7 non emette simboli sullo schermo, ma un breve
beep). Quindi una macro ACAPO che emette un acapo sullo schermo.
Si veda il seguente codice, che stampa una sequenza di zeri e uni su quattro righe:
ACAPO
mov
mov
int
mov
int
ENDM
MACRO
ah,2
dl, 13
21h
dl, 10
21h
; Definizione della macro, con nome simbolico ACAPO
; terminazione del blocco macro
.MODEL TINY
.CODE
ORG 100h
START:
mov ah,02
mov dl,'0'
int 21h
ACAPO
compongono
mov ah,02
mov dl,'1'
int 21h
ACAPO
mov ah,02
mov dl,'0'
int 21h
ACAPO
mov ah,02
mov dl,'1'
int 21h
int 20h
end START
C:\>macapo
0
1
0
1
C:\>
; Uso della macro. In questo punto la macro ACAPO verrà espansa nelle 5 istruzioni che la
; Uso della macro. Altre 5 istruzioni espanse
; Uso della macro. Altre 5 istruzioni espanse
OUTPUT
.doc - 1684Kb - 12/set/2008 - 67 / 82
S3Abacus – Architetture/Asm
MACRO CON PARAMETRI E ETICHETTE
Le macro di codice diventano veramente interessanti se utilizzate con parametri, ovvero se dotate di argomenti che possono essere variabili nel
momento dell’uso.
La seguente è una macro che stampa un carattere sullo schermo, indicato al momento dell’uso:
STAMPACAR MACRO carattere
mov ah, 2
mov dl, carattere
int 21h
ENDM
Il suo uso e’ intuibile:
STAMPACAR ‘P’
Se invece una macro dovesse contenere una o più etichette, si presenterebbe il problema dell’uso ripetuto dell’etichetta (cfr. Scrivere un
programma EXE), dato che la macro viene espansa nel codice del programma e il nome dell’etichetta verrà ripetuto tante volte quante volte la
macro è usata.
Per ovviare a questo problema si usa una direttiva dedicata L O C A L che consente di dichiarare le etichette utilizzate nella macro, lasciando il
compito all’assemblatore di gestirne correttamente la ripetizione.
Direttiva
LOCAL
Sintassi:
LOCAL nome
Scopo:
Impone all’assemblatore di trasformare il simbolo nome usato in una macro in un simbolo univoco per ogni
espansione della macro.
Nota:
La direttiva va posta all’inizio del blocco di codice della macro.
Si osservi questo codice che acquisisce un carattere in input dopo aver mostrato un rudimentale prompt (‘?’), e presenta una macro che stampa un
carattere, ma solo se numerico:
STAMPACARNUM MACRO regchar
LOCAL NONOK
cmp regchar, '0'
jl NONOK
cmp regchar, '9'
jg NONOK
mov ah, 2
mov dl, regchar
int 21h
NONOK:
macro
ENDM
; regchar è il parametro della macro, un registro a 8bit
; l’etichetta NONOK deve essere dichiarata con la direttiva LOCAL
; la dichiarazione dell’etichetta impedisce l’errore di duplicazione, nel caso di più usi della
.MODEL TINY
.CODE
ORG 100h
START:
mov ah,2
mov dl, '?'
int 21h
mov ah,0
int 16h
STAMPACARNUM al
int 20h
end START
; stampa a video del carattere ? come prompt per l’input
; input di un carattere da tastiera, senza echo. Il carattere digitato ritorna in AL
; AL è il valore del parametro della macro
C:\>mparam
?1
C:\>
(si è digitato il carattere 1, che viene regolarmente stampato a schermo)
OUTPUT
.doc - 1684Kb - 12/set/2008 - 68 / 82
S3Abacus – Architetture/Asm
STACK
Solo con la programmazione assembly il programmatore può utilizzare espressamente la zona di memoria dello Stack. Ricordiamo che tutti i
linguaggi ad alto livello usano lo Stack, ma in modo trasparente al programmatore, per allocare/deallocare le variabili locali, far transitare i parametri
alle procedure e gestire gli indirizzi di andata e ritorno delle subroutines.
Per velocizzare tutti questi processi, lo Stack assume la forma di una s t r u t t u r a d a t i a P i l a (o L I F O , Last In, First Out). L’immissione di
un valore nello stack si appoggia sull’ultimo valore presente nello stack, in modo tale che l’ultimo valore immesso, sempre in cima alla pila, sia
immediatamente accessibile. Per raggiungere i valori sotterrati nella pila è necessario scaricare quelli che lo ricoprono, come quando si vuole
prendere un piatto in mezzo ad una pila di piatti.
Per gestire velocemente le operazioni di scrittura (inserimento) e lettura (prelevamento) dallo stack, l’ISA x-86 prevede istruzioni specifiche
(rispettivamente P U S H e P O P ) e automatismi specifici su alcuni registri: lo Stack Pointer SP contiene sempre e automaticamente l’indirizzo
dell’ultimo elemento sulla cima dello stack.
Lo stack x-86 è organizzato a word (due byte), ovvero ogni elemento in pila è sempre ampio due byte.
Lo stack x-86 inizia (ha la base) sempre alla fine di un segmento di memoria, ovvero l’indirizzo del primo elemento di uno stack ha sempre valore di
offset pari a FFFEh.
In altre parole, all’avvio di un qualsiasi programma eseguibile (EXE o COM) il registro SP contiene sempre il valore FFFEh.
Ciò significa che la pila dello stack x-86 cresce diminuendo gli indirizzi (dello Stack Pointer SP) di due unità alla volta per ogni elemento.
Questa scelta è opportuna, dato che lo stack si amplia a runtime senza controllo: se si perde il controllo dello stack e lo si riempie indefinitamente
(S t a c k O v e r f l o w ), vengono sovrascritte locazioni di memoria del programma, ma non del Sistema Operativo.
Le istruzioni per la gestione esplicita dello stack sono:
Istruzione PUSH
Sintassi:
PUSH sorgente
Scopo:
Decrementa SP di due unità e pone sorgente sullo Stack all’indirizzo contenuto in SP.
Esempi:
PUSH AX
Nota:
PUSH 7 ; non per l’8086, dal 80186+
PUSH [BX]
Nel primo caso il contenuto di AX viene posto sulla cima dello stack.
Sorgente non può essere un valore immediato, almeno nell’8086/88, ma può essere una locazione di memoria,
purchè ampia due byte
Istruzione POP
Sintassi:
POP destinazione
Scopo:
Preleva una word dallo Stack, dall’indirizzo contenuto in SP, e la deposita in destinazione, quindi incrementa SP di
due unità.
Esempi:
POP AX
Nota:
POP [BX]
POP VAR
Nel primo caso, il valore a due byte in cima allo stack viene posto in AX.
Negli altri casi, il valore in cima allo stack viene posto direttamente in memoria, occupando due celle contigue a
partire dagli indirizzi contenuti, rispettivamente, in BX e VAR.
Altre istruzioni oramai necessarie per lo sviluppo del codice d’esempio, sono le principali istruzioni aritmetiche:
Istruzione ADD
Sintassi:
ADD destinazione, sorgente
Scopo:
Effettua la somma (anche con segno) tra destinazione e sorgente.
Il risultato viene collocato in sorgente.
destinazione non può essere un immediato, ma può essere una zona di memoria.
Esempi:
ADD BX, 256
ADD BX, CX
ADD VAR, 12
Istruzione SUB
Sintassi:
SUB minuendo, sottraendo
Scopo:
Sottrae da minuendo il sottraendo (anche con segno).
Il risultato viene collocato in minuendo.
minuendo non può essere un immediato, ma può essere una zona di memoria.
Esempi:
SUB BX, 256
SUB BX, CX
SUB VAR, 12
.doc - 1684Kb - 12/set/2008 - 69 / 82
S3Abacus – Architetture/Asm
Istruzione MUL
Sintassi:
MUL moltiplicatore
Scopo:
Effettua la moltiplicazione senza segno tra:
AL e moltiplicatore, se moltiplicatore è a 8 bit, oppure tra
AX e moltiplicatore, se moltiplicatore è a 16 bit.
Nel primo caso colloca in AX il risultato, nel secondo caso in DX:AX
moltiplicatore non può essere un immediato, ma può essere una cella di memoria.
Esempi:
Nota:
MUL CH
MUL VAR
MUL SI
Attenzione: se il risultato è maggiore del contenitore, saranno impostati i flag di Overflow o di Carry, altrimenti azzerati.
Istruzione DIV
Sintassi:
DIV divisore
Scopo:
Effettua la divisione senza segno tra:
AX e divisore, se divisore è a 8 bit, oppure tra
DX:AX e divisore, se divisore è a 16 bit.
Nel primo caso colloca in AL il quoziente e in AH il resto, nel secondo caso in AX il quoziente e in DX il resto.
divisore non può essere un immediato, ma può essere una cella di memoria.
Esempi:
DIV BL
Nota:
DIV VAR
DIV SI
Attenzione: se il quoziente non sta nel contenitore, avviene un errore di overflow o di divisione per zero.
Es., MOV AX,0A100; MOV BL,2; DIV BL; genera un errore perché A100h / 2 = 5080h, che non sta in un byte.
Si consulti ora questo codice, che stampa in binario il valore memorizzato nella variabile VAR allocata in memoria. Si usa l’istruzione DIV (divisione)
per memorizzare i resti delle divisioni per due, memorizzarli sullo stack, quindi riprenderli per stampare le cifre binarie.
.MODEL TINY
.CODE
ORG 100h
START:
jmp MAIN
VAR DW 00A1h
; area Dati; il valore VAR (A1h) sarà convertito in binario
MAIN:
mov ax,VAR
mov bl,2
mov cx,0
; il dividendo in AX
; il divisore in BL
; conterà il numero di divisioni, cioè il numero di cifre binarie
div bl
push ax
inc cx
mov ah,0
cmp al,0
jne ANCORA
;
;
;
;
;
;
ANCORA:
STAMPA:
pop dx
mov dl,dh
add dl,'0'
mov ah,2
int 21h
loop STAMPA
int 20h
end START
divisione per 2, in AH il resto, in AL il risultato
salvataggio del resto e del risultato sullo stack
conteggio del numero delle cifre binarie
annullamento del resto, rimarrà solo il risultato per la prossima divisione
il risultato è zero?
se no, si continua la divisione per due
; si preleva dallo stack il valore, tra cui il resto della divisione per due
; si mette il resto (0 o 1) in DL per la stampa a schermo
; si aggiunge il codice Ascii dello zero per ottenere il codice Ascii del numero (‘0’ o ‘1’) corrispondente
; stampa a schermo della cifra binaria
; ancora cifre da stampare?
OUTPUT
C:\>bin
10100001
C:\>
Lo stack di questo programma si riempie nel seguente modo, a seguito di otto chiamate PUSH AX.
Nella colonna in grigio, i resti, a fianco i risultati
Stack SP valori
ss:FFEE ►
ss:FFF0
0100
0001
Descrizione
8va divisione: 01h / 2 = 00h, resto 1
7ma divisione: 02h / 2 = 01h, resto 0
.doc - 1684Kb - 12/set/2008 - 70 / 82
S3Abacus – Architetture/Asm
ss:FFF2
ss:FFF4
ss:FFF6
ss:FFF8
ss:FFFA
ss:FFFC
ss:FFFE
0102
0005
000A
0014
0028
0150
0000
6ta divisione: 05h / 2 = 02h, resto
5ta divisione: 0Ah / 2 = 05h, resto
4ta divisione: 14h / 2 = 0Ah, resto
3za divisione: 28h / 2 = 14h, resto
2da divisione: 50h / 2 = 28h, resto
1ma divisione: A1h / 2 = 50h, resto
Valore iniziale dello Stack Pointer
1
0
0
0
0
1
Bisogna ricordare che le operazioni sullo stack devono sempre essere b i l a n c i a t e , ovvero lo Stack Pointer (SP) deve sempre tornare al valore
di partenza alla fine del programma. Le istruzioni PUSH e POP, pertanto, devono essere eseguite lo stesso numero di volte.
.doc - 1684Kb - 12/set/2008 - 71 / 82
S3Abacus – Architetture/Asm
PROCEDURE
L’uso delle macro di codice semplifica notevolmente la scrittura dei programmi assembly ed è vivamente consigliata. Un effetto collaterale dell’uso
delle macro di codice è l’espansione del codice sorgente e del codice eseguibile, cioè il suo incremento in quantità. Ciò significa anche maggior
memoria principale utilizzata. Le macro di codice, inoltre, rallentano il tempo di compilazione e, soprattutto, non possono adeguarsi circa situazioni
che avvengono solo a run time.
Una soluzione a questi problemi è l’uso di p r o c e d u r e (o subroutines) che solo apparentemente svolgono un compito analogo alla macro.
Una procedura è ancora un blocco di codice con un nome simbolico, ma stavolta il nome della procedura non è un simbolo ma l’indirizzo della sua
prima istruzione in memoria. Le procedure, infatti, sono allocate in memoria, in uno spazio privato, e devono essere chiamate a runtime dal
codice del programma (o da altre procedure).
Ciò significa che il blocco di codice di una procedura non viene ripetuto nel sorgente ad ogni occorrenza del suo nome, ma solo usato dal
chiamante: il blocco di codice di una procedura è unico e allocato in memoria, cioè le procedure operano a runtime.
Le procedure, pertanto, non incrementano il codice sorgente ed eseguibile del programma, e quindi risparmiano anche nell’uso della memoria
principale, rispetto alle macro di codice. Inoltre, operando a runtime, possono crearsi veri e propri ambienti autonomi di elaborazione, es. mediante
la creazione, sempre a runtime, di zone di memoria private, dette v a r i a b i l i l o c a l i .
L’unico effetto collaterale di una procedura, rispetto alle macro di codice, è una maggior lentezza nell’esecuzione, dato che il codice chiamante deve
preparare la memoria (di solito lo stack) per avviare la procedura, e la procedura, a sua volta, deve ripristinare la memoria al suo termine e prima di
ritornare al chiamante. Queste operazioni sono dette m e c c a n i s m o d i c h i a m a t a e ritorno della procedura.
DEFINIZIONE DI PROCEDURA
Le procedure vanno definite con una sintassi molto simile a quella delle macro di codice, anche se la collocazione delle procedure deve essere
posta necessariamente nell’area codice, prima del programma principale, o dopo.
PseudoIstruzione PROC / ENDP
Sintassi:
nome PROC
(codice)
(RET)
ENDP
Scopo:
Definisce il blocco di (codice) che sarà chiamato tramite l’istruzione CALL nome. Al termine bisogna ridare il controllo
al chiamante con (RET)
Esempi:
BEEP PROC
mov ah,2
mov dl, 7
int 21h
ret
ENDP
Nota:
ACAPO PROC
mov ah,2
mov dl, 13
int 21h
mov dl, 10
Int 21h
ret
ENDP
I due esempi sono simili a quelli riportati nella sintassi delle direttive MACRO/ENDM, ma il blocco di codice termina con l’istruzione RET
per completare il meccanismo di chiamata.
Di fianco a PROC si può aggiungere il modificatore F A R se la chiamata avviene da un segmento di codice differente
da quello che contiene la procedura (per i modelli di memoria MEDIUM, LARGE e HUGE)
Una procedura deve essere chiamata dal codice del programma e quindi deve ritornare al chiamante per consentirgli il regolare flusso di
esecuzione. L’Isa x-86 preve due istruzioni caratteristiche per gestire il meccanismo di chiamata:
Istruzione CALL
Sintassi:
CALL target
Scopo:
L’istruzione CALL esegue le seguenti operazioni:
1) salva nello stack l’i n d i r i z z o d i r i t o r n o ;
2) trasferisce il controllo all’operando target tramite un salto incondizionato.
L’indirizzo di ritorno è l’indirizzo dell’istruzione successiva a quella di CALL.
Esempi:
CALL ACAPO
Nota:
CALL word ptr [BX]
Nel primo caso la CALL ACAPO può essere vista come l’unione delle due PUSH IP; JMP ACAPO, ricordando che il nome di una
procedura è il suo indirizzo in memoria. Nel secondo caso un esempio di chiamata dinamica, ovvero una chiamata
che assume valore solo a runtime (in base al valore attuale di BX). Word ptr serve per indicare all’assemblatore che
la locazione puntata da BX riguarda due byte contigui a partire dall’indirizzo contenuto in BX.
Istruzione RET
Sintassi:
RET
Scopo:
L’istruzione RET assume che l’indirizzo di ritorno si trovi attualmente in cima allo stack.
Essa esegue le seguenti operazioni:
1) preleva dallo stack dell’indirizzo di ritorno
2) salto all’indirizzo di ritorno.
.doc - 1684Kb - 12/set/2008 - 72 / 82
S3Abacus – Architetture/Asm
Esempi:
Nota:
RET
La RET, che va sempre posta come ultima istruzione di un blocco di procedura, esegue, praticamente, le seguenti istruzioni: POP
indirizzoritorno/JMP indirizzoritorno, oppure, con una sola istruzione logica: POP IP
In entrambi i casi, se la procedura è di tipo FAR – cioè si trova in un segmento di codice differente da quello del chiamante, sia CALL che RET,
invece di salvare/rileggere solo la parte offset dell’indirizzo del program counter (indirizzo di ritorno su due byte), salvano e rileggono sia la parte
seg che la parte offset dell’indirizzo (indirizzo di ritorno su quattro byte) in modo del tutto trasparente al programmatore.
MECCANISMO DI CHIAMATA
Si veda il seguente esempio che illustra il meccanismo di chiamata e ritorno di una procedura tramite lo stack.
La colonna in grigio mostra gli indirizzi effettivi delle righe di codice.
.MODEL TINY
.CODE
ORG 100h
cs:0100
l’area Dati
cs:0103
cs:0105
cs:0107
cs:0109
cs:010B
cs:010D
cs:010E
cs:0110
cs:0112
cs:0114
ritorno
cs:0117
cs:0119
cs:011B
cs:011D
START:
jmp MAIN
; All’avvio si deve saltare il codice delle procedure
ACAPO PROC
; La procedura deve stare nell’area di codice, ma deve essere saltata all’avvio, così come si salta
mov
mov
int
mov
int
ret
ENDP
ah,2
dl, 13
21h
dl, 10
21h
MAIN:
mov ah,02
mov dl,'0'
int 21h
call ACAPO
; L’istruzione RET è necessaria per far funzionare il meccanismo di ritorno
; La procedura ACAPO deve essere chiamata esplicitamente con l’istruzione CALL, per garantire il
mov ah,02
mov dl,'1'
int 21h
int 20h
end START
OUTPUT
C:\>pacapo
0
1
C:\>
Lo stack del programma subisce il seguente movimento (tre passi, compreso lo stato iniziale):
Stack SP valori
Descrizione
ss:FFFA
ss:FFFC
ss:FFFE ►
....
....
0000
Valore iniziale dello Stack Pointer
ss:FFFA
ss:FFFC ►
ss:FFFE
....
0117
0000
Indirizzo di ritorno, sulla CALL ACAPO
Valore iniziale dello Stack Pointer
ss:FFFA
ss:FFFC
ss:FFFE ►
....
....
0000
Dopo la RET nella procedura ACAPO
PRESERVARE I REGISTRI
L’utilizzo delle procedure comporta un effetto collaterale abbastanza grave, detto i n t e r f e r e n z a : i registri usati dalla procedura sovrascrivono il
contenuto precedentemente salvato in quei registri dal chiamante, con l’effetto che al ritorno della procedura il chiamante non ritrova più i valori
precedentemente salvati nei registri.
Per evitare l’interferenza, la procedura deve preservare i registri in ingresso, ovvero salvare il contenuto dei registri che essa stessa userà al suo
interno, salvandoli ordinatamente sullo stack, per poi ripristinarli ordinatamente appena prima di ritornare il controllo al chiamante (appena prima
dell’istruzione RET).
La preservazione dei registri può essere effettuata puntualmente, salvando sullo stack solo i registri usati dalla procedura, o in modo completo
sfruttando due apposite istruzioni x-86, PUSHA e POPA che, rispettivamente, salvano sullo stack e riprendono dallo stack tutti i registri (ma solo per
l’x-86 a partire dall’80186, con l’esclusione, quindi, dell’8086/88).
S3Abacus – Architetture/Asm
.doc - 1684Kb - 12/set/2008 - 73 / 82
Il codice precedente, dotato di preservazione dei registri, appare come segue (l’output non cambia):
.MODEL TINY
.CODE
ORG 100h
START:
jmp MAIN
ACAPO PROC
push ax
push dx
mov ah,2
mov dl, 13
int 21h
mov dl, 10
int 21h
pop dx
pop ax
ret
ENDP
; si preservano i soli registri AX e DX, gli unici usati dalla procedura, inviandoli sullo stack
; si ricaricano i registri preservati, in ordine inverso, per restituirli invariati al chiamante
MAIN:
mov ah,02
mov dl,'0'
int 21h
call ACAPO
mov ah,02
mov dl,'1'
int 21h
int 20h
end START
C:\>pacapo
0
1
C:\>
OUTPUT
.doc - 1684Kb - 12/set/2008 - 74 / 82
S3Abacus – Architetture/Asm
PASSAGGIO DI PARAMETRI
Le procedure diventano realmente fondamentali quando permettono il passaggio dei parametri, ovvero possono svolgere il proprio compito sulla
base di valori che il chiamante decide a runtime.
In realtà si è già usato un sistema di passaggio di parametri, ad esempio durante l’uso delle interruzioni sw: valorizzare un registro prima della
chiamata all’istruzione INT significa passare – t r a m i t e r e g i s t r o – un parametro alla routine dell’interruzione sw.
Il passaggio dei parametri tramite registri è molto veloce e semplice, ma ha molte limitazioni, prima di tutto la quantità dei registri disponibili.
Le procedure, per linguaggi ad alto e a basso livello come l’assembly, usano in realtà lo stack per passare i parametri e, quando serve, per ritornarli
al chiamante.
L’idea è semplice: il chiamante, prima di chiamare la procedura con la consueta istruzione CALL, deposita sullo stack i valori che intende passare
alla procedura. La procedura, prima di iniziare il suo compito, preleva dallo stack i parametri e li usa al suo interno.
Per ritornare valori dalla procedura al chiamante, si usa lo stesso meccanismo.
In questo caso il passaggio di parametri si dice t r a m i t e l o s t a c k .
Il passaggio di parametri tramite lo stack deve tener presente che, sullo stack, come ultimo valore, verrà sempre posto l’indirizzo di ritorno della
procedura– ad opera dell’istruzione CALL. Pertanto la procedura dovrà prelevare i parametri senza eliminare dalla cima dello stack l’indirizzo di
ritorno, che dovrà essere usato dall’istruzione RET per ritornare correttamente al chiamante.
Esistono varie tecniche per passare i parametri sullo stack. Le più diffuse prendono il nome di c d e c l (usata dal linguaggio C e derivati) e
s t d c a l l (usata dal linguaggio Pascal e dalle API di alcuni SO).
In questa sezione vedremo un passaggio di parametri alle procedure abbastanza simile allo stile del C o cdecl, che usa il registro BP (Base Pointer)
per prelevare i dati sullo stack senza modificare il registro SP (Stack Pointer). Si ricorda che il registro BP ha la proprietà di indirizzare in memoria,
cioè di contenere indirizzi di memoria.
1.
2.
3.
4.
5.
6.
7.
8.
Prima di tutto il chiamante deve porre nello stack i parametri richiesti dalla procedura. L’operazione si effettua con la consueta istruzione
PUSH, ripetuta tante volte quanti sono i parametri da passare.
Quindi si effettua la chiamata normalmente, con l’istruzione CALL. Essa immetterà sulla cima dello stack, come di consueto, l’indirizzo di
ritorno.
La procedura, a sua volta, deve immediatamente salvare sullo stack il registro BP, dato che verrà usato e sovrascritto per prelevare i
parametri.
Quindi il registro BP deve essere impostato con il valore dello Stack pointer SP, mediante una istruzione MOV: in questo modo BP punta
alla cima dello stack.
Ora i parametri possono essere prelevati uno a uno tramite BP, avendo cura di ricordare che il primo parametro è profondo 4 byte nello
stack: infatti i primi due byte in cima alla pila riportano il valore di BP (appena memorizzato), e i successivi due byte riportano il valore
dell’indirizzo di ritorno. Ogni parametro si scosta di due byte, pertanto a BP+4 corrisponde il valore del primo parametro, a BP+6 il valore
del secondo parametro, a BP+8 il valore del terzo parametro, e così via.
Ora può essere scritto il codice della procedura, comprese le eventuali istruzioni per preservare i registri.
Infine, appena prima dell’istruzione RET, va ripristinato il registro BP, che se tutto è stato svolto correttamente, si trova attualmente in
cima allo stack. Una volta prelevato il valore originale di BP, l’indirizzo di ritorno è disponibile in cima alla pila per l’istruzione RET.
Il chiamante, quando riprende il controllo, si ritrova i parametri ancora sullo stack, per cui deve ripristinare lo stato dello stack
deallocandoli, cioè facendo tornare lo Stack Pointer SP al valore originario. Ciò è semplice, tramite una istruzione ADD: si aggiungono
allo Stack Pointer tante ‘doppiette’ quanti sono i parametri (es., per 3 parametri: ADD SP,6). Una delle maggiori differenze tra la tecnica
cdecl e stdcall consiste nel fatto che cdecl impone che sia il chiamante a deallocare i parametri dallo stack, mentre in stdcall è la
procedura a farlo.
Bisogna ricordare che il salvataggio immediato di BP – e il suo successivo ripristino, è fondamentale benchè BP non sia di norma usato dai moduli
che chiamano le procedure. Infatti una procedura può – e spesso lo fa, chiamarne un’altra al suo interno (c h i a m a t a a n n i d a t a ), alla quale
passare parametri. Se BP non fosse preservato, le chiamate annidate non funzionerebbero.
Il seguente codice usa una procedura a cui viene passato sullo stack il codice Ascii da stampare a schermo. Siccome lo stack usa elementi a 16 bit,
il codice Ascii (8bit) viene enucleato nella parte bassa del registro AX, che e’ a 16 bit.
.MODEL TINY
.CODE
ORG 100h
cs:0100
cs:0103
cs:0104
parametro
cs:0106
cs:0109
cs:010B
cs:010D
cs:010E
cs:010F
cs:0112
cs:0113
cs:0116
START:
jmp MAIN
STAMPACAR PROC
push bp
mov bp,sp
mov
mov
int
pop
ret
ENDP
dx,[bp+4]
ah,2
21h
bp
MAIN:
mov al,'?'
push ax
call STAMPACAR
add sp,2
; si preserva BP sullo stack, come prima istruzione della procedura
; si memorizza lo stack Pointer in BP, in modo che BP possa servire pre reperire il
; ecco il parametro, profondo 4 byte dentro lo stack (il codice Ascii del ?)
; ripristino di BP. Ora sullo stack c’è l’indirizzo di ritorno, così che RET funzioni a dovere
; passaggio del parametro sullo stack (il codice Ascii del ?, in AL all’interno di AX)
; deallocazione dello stack. Un parametro, una “doppietta”
.doc - 1684Kb - 12/set/2008 - 75 / 82
S3Abacus – Architetture/Asm
cs:0119
int 20h
end START
OUTPUT
C:\>pparam
?
C:\>
Seguendo il listato del programma, si può seguire l’andamento dello stack per ogni istruzione che lo modifica implicitamente (come CALL e RET) o
esplicitamente come PUSH, POP e ADD SP,2.
Stack SP valori
Descrizione
ss:FFF8
ss:FFFA
ss:FFFC
ss:FFFE ►
....
....
....
0000
Valore iniziale dello Stack Pointer
ss:FFF8
ss:FFFA
ss:FFFC ►
ss:FFFE
....
....
003F
0000
PUSH AX; 3Fh è il codice Ascii del carattere ?
Valore iniziale dello Stack Pointer
ss:FFF8
ss:FFFA ►
ss:FFFC
ss:FFFE
....
0116
003F
0000
CALL STAMPACAR; 116h è l’indirizzo di ritorno
PUSH AX
Valore iniziale dello Stack Pointer
ss:FFF8 ►
ss:FFFA
ss:FFFC
ss:FFFE
0000
0116
003F
0000
PUSH BP; in BP c’era il valore 0
CALL STAMPACAR
PUSH AX
Valore iniziale dello Stack Pointer
Nella procedura ora si pone in BP lo Stack Pointer, con mov bp,sp, cioè BP = FFF8h.
All’indirizzo BP + 4 = FFFCh, c’è l’indirizzo del parametro sullo stack, cosicchè mov dx,[bp+4] pone in DX il valore 003Fh, cioè in DL il
codice Ascii (3Fh) del punto interrogativo.
ss:FFF8
ss:FFFA ►
ss:FFFC
ss:FFFE
0000
0116
003F
0000
ss:FFF8
ss:FFFA
ss:FFFC ►
ss:FFFE
0000
0116
003F
0000
ss:FFF8
ss:FFFA
ss:FFFC
ss:FFFE ►
....
....
....
0000
POP BP; ripristinato BP (0000h)
RET; caricato l’indirizzo di ritorno (0116h)
ADD SP, 2 e valore iniziale dello Stack Pointer
.doc - 1684Kb - 12/set/2008 - 76 / 82
S3Abacus – Architetture/Asm
VARIABILI LOCALI
Una delle proprietà fondamentali delle procedure è la possibilità di crearsi un ambiente di memoria privato con il quale interagire per completare
compiti anche abbastanza articolati. L’area di memoria privata di una procedura è allocata sullo stack e deallocata appena prima del ritorno al
chiamante.
Le variabili che prendono posto nell’area privata delle procedure sono dette v a r i a b i l i l o c a l i o v a r i a b i l i a u t o m a t i c h e .
Come visto in precedenza, una volta preso il controllo, una procedura memorizza la cima dello stack in BP per poter prelevare eventuali parametri
sotterrati nella pila.
Per creare memoria alle variabili locali, bisogna invece estendere lo stack al di sopra della cima, di tante “doppiette” quante sono le variabili locali
da creare. Così, utilizzando sempre BP come base, si raggiungeranno le variabili locali con sottrazioni di “doppiette”: in BP-2 ci sarà l’indirizzo della
prima variabile locale, in BP-4 l’indirizzo della seconda, in BP-6 l’indirizzo della terza, e così via.
Al termine, l’area delle variabili locali deve essere deallocata dalla procedura, riportando lo stack Pointer al suo valore originale.
1.
2.
3.
4.
La procedura, dopo aver memorizzato in BP la cima dello stack, lo amplia opportunamente sottraendo allo Stack Pointer SP tante
doppiette quante sono le variabili locali da usare, es. SUB SP,4, alloca due variabili locali da due byte l’una (o quattro variabili locali da un
byte l’una).
Ora la procedura può scrivere nella variabile locale con la consueta MOV, indicando l’indirizzo della variabile tramite BP, es. MOV [BP-2],
AX, mette nella prima variabile locale il valore del registro AX
Allo stesso modo la procedura può leggere le variabili locali, usando sempre BP per indirizzarle, es. MOV DL, byte ptr [BP-4] pone nel
registro DL la variabile locale di ampiezza un byte dalla seconda area di memoria allocata sullo stack.
Al termine, la zona delle variabili locali viene deallocata riportando lo Stack pointer SP al valore originale che ora è contenuto in BP (es.
MOV SP, BP).
Come esempio vediamo una versione di listato molto simile a quello usato per il passaggio di un parametro. In questo caso si passa alla procedura
una cifra ed essa ne stamperà il simbolo Ascii sullo schermo, dopo aver usato una variabile locale per memorizzare la base dei codici Ascii
numerici, cioè il codice Ascii di zero (30h):
.MODEL TINY
.CODE
ORG 100h
START:
jmp MAIN
cs:0100
STAMPANUM PROC
cs:0103
push bp
cs:0104
mov bp,sp
cs:0106
mov dx,[bp+4]
cs:0109
sub sp,2
cs:010C
mov byte ptr [bp-2],30h
cs:0110
add dx,[bp-2]
ottenere il simbolo
cs:0113
mov ah,2
cs:0115
int 21h
cs:0117
mov sp,bp
cs:0119
pop bp
cs:011A
ret
cs:0113 ENDP
MAIN:
mov al,9
push ax
call STAMPANUM
add sp,2
int 20h
end START
cs:011B
cs:011E
cs:011F
cs:0122
cs:0125
; consueta predisposizione dello stack frame per il prelevamento del parametro
; allocazione della variabile locale
; scrittura della variabile locale, con il valore 30h
; lettura della variabile locale, tramite l’istruzione ADD. Si somma il codice Ascii dello zero per
; deallocazione della variabile locale
; preparazione del parametro, in questo caso il numero nove (e non il suo codice Ascii)
; passaggio del parametro sullo stack
; deallocazione dello stack
OUTPUT
C:\>varloc
9
C:\>
Seguendo il listato del programma, si può seguire l’andamento dello stack all’atto dell’allocazione e deallocazione dell’area di memoria locale:
Stack SP valori
ss:FFF6
ss:FFF8
ss:FFFA
ss:FFFC
ss:FFFE ►
....
....
....
....
0000
ss:FFF6
ss:FFF8
....
....
Descrizione
Valore iniziale dello Stack Pointer
.doc - 1684Kb - 12/set/2008 - 77 / 82
S3Abacus – Architetture/Asm
ss:FFFA
ss:FFFC ►
ss:FFFE
....
0009
0000
PUSH AX; 9h è il codice Ascii del carattere zero
Valore iniziale dello Stack Pointer
ss:FFF6
ss:FFF8
ss:FFFA ►
ss:FFFC
ss:FFFE
....
....
0122
0009
0000
CALL STAMPANUM; 122h è l’indirizzo di ritorno
PUSH AX
Valore iniziale dello Stack Pointer
ss:FFF6
ss:FFF8 ►
ss:FFFA
ss:FFFC
ss:FFFE
....
0000
0122
0009
0000
PUSH BP; in BP c’era il valore 0
CALL STAMPANUM
PUSH AX
Valore iniziale dello Stack Pointer
ss:FFF6 ►
ss:FFF8
ss:FFFA
ss:FFFC
ss:FFFE
....
0000
0122
0009
0000
SUB SP, 2; spostando SP di due unità, si alloca un elemento sullo stack
PUSH BP; in BP c’era il valore 0
CALL STAMPANUM
PUSH AX
Valore iniziale dello Stack Pointer
ss:FFF6 ►
ss:FFF8
ss:FFFA
ss:FFFC
ss:FFFE
..30
0000
0122
0009
0000
MOV byte ptr [bp-2],30h; si scrive nella variabile locale
PUSH BP; in BP c’era il valore 0
CALL STAMPANUM
PUSH AX
Valore iniziale dello Stack Pointer
ss:FFF6 ►
ss:FFF8
ss:FFFA
ss:FFFC
ss:FFFE
..30
0000
0122
0009
0000
ADD DX,[bp-2]; si legge nella variabile locale
PUSH BP; in BP c’era il valore 0
CALL STAMPANUM
PUSH AX
Valore iniziale dello Stack Pointer
ss:FFF6
ss:FFF8 ►
ss:FFFA
ss:FFFC
ss:FFFE
..30
0000
0122
0009
0000
questa locazione ora non è più valida
MOV SP, BP; si dealloca la variabile locale ripristinando lo Stack Pointer
CALL STAMPANUM
PUSH AX
Valore iniziale dello Stack Pointer
ss:FFF6
ss:FFF8
ss:FFFA ►
ss:FFFC
ss:FFFE
..30
0000
0122
0009
0000
ss:FFF6
ss:FFF8
ss:FFFA
ss:FFFC ►
ss:FFFE
..30
0000
0122
0009
0000
ss:FFF6
ss:FFF8
ss:FFFA
ss:FFFC
ss:FFFE ►
....
....
....
....
0000
POP BP; ripristinato BP (0000h)
RET; caricato l’indirizzo di ritorno (0116h)
ADD SP, 2 e valore iniziale dello Stack Pointer
NOTAZIONI PER IL PASSAGGIO DI PARAMETRI E LE VARIABILI LOCALI
Per rendere il codice assembly più leggibile e semplice da utilizzare, è spesso conveniente utilizzare uno stile che fa uso di qualche macro costante
per poter servirsi di nomi simbolici al posto delle notazioni che indirizzano brutalmente lo Stack, sia per quanto riguarda la gestione dei parametri,
che la gestione delle variabili locali.
In questo modo il listato precedente assume la seguente forma (il programma eseguibile è assolutamente identico):
.MODEL TINY
.CODE
ORG 100h
START:
jmp MAIN
STAMPANUM PROC
Parametro EQU word ptr [BP+4] ; Il nome simbolico Parametro equivale alla zona dello stack che contiene il primo parametro
Variabile EQU byte ptr [BP-2] ; Il nome simbolico Variabile equivale alla zona dello stack che contiene la prima variabile locale
push bp
mov bp,sp
S3Abacus – Architetture/Asm
mov
sub
mov
add
mov
int
mov
pop
ret
ENDP
MAIN:
dx,Parametro
sp,2
Variabile,30h
dx,Variabile
ah,2
21h
sp,bp
bp
mov al,9
push ax
call STAMPANUM
add sp,2
int 20h
end START
.doc - 1684Kb - 12/set/2008 - 78 / 82
; Uso del nome simbolico Parametro per recuperare il parametro
; Uso del nome simbolico Variabile per scrivere la variabile locale
; Uso del nome simbolico Variabile per leggere la variabile locale
.doc - 1684Kb - 12/set/2008 - 79 / 82
S3Abacus – Architetture/Asm
DIRETTIVE PER LA PROGRAMMAZIONE E LIBRERIE
Per una efficiente programmazione assembly, è necessario utilizzare alcune direttive all’assemblatore per superare alcuni limiti architetturali – come
il problema della distanza tra etichetta e riferimento per i salti condizionati o per rendere più agevole la scrittura dei programmi – come ad esempio
evitare di pianificare l’uso univoco dei nomi delle etichette.
Inoltre è fondamentale conoscere il modo in cui più moduli sorgenti concorrono per generare un file eseguibile, tecnica necessaria per i progetti sw
che intendono avvalersi di moduli di libreria.
SALTI LUNGHI, DIRETTIVA JUMPS
Per evitare di incorrere nel problema del salto lungo, cioè quando la distanza tra riferimento e etichetta supera i 128 bytes, è sufficiente citare una
direttiva iniziale all’assemblatore, la direttiva J U M P S :
Direttiva
JUMPS
Sintassi:
JUMPS
Scopo:
Impone all’assemblatore di trasformare il codice di eventuali salti a distanze superiori di 128 bytes, in un codice
equivalente in grado di superare tale limite ed effettuare anche salti lunghi.
Nota:
La direttiva va posta all’inizio del modulo sorgente, subito dopo la direttiva che indica l’inizio dell’area Codice (.CODE). Spesso si usa
anche quando non si è certi della presenza di salti lunghi nel codice. La direttiva vale solo per l’assemblatore TASM.
La direttiva JUMPS si limita a trasformare il salto condizionato in una struttura di salto che utilizza un salto incondizionato JMP come supporto per
raggiungere l’etichetta distante più di 128 byte dal suo riferimento. Infatti l’istruzione di salto incondizionato JMP non ha limiti di distanza tra
riferimento e etichetta.
DUPLICAZIONE DI ETICHETTE, DIRETTIVA LOCALS
All’interno delle procedure spesso si vorrebbero usare etichette con nomi uguali in procedure diverse, soprattutto per indicare zone logiche del
codice equivalenti (es. FINE, OK, ecc.). Questo genera un errore dell’assemblatore, che necessita di nomi univoci per le etichette in tutta l’area di
Codice. Per evitare di pianificare uno schema di naming univoco per le etichette da usare nelle procedure, si può usare una direttiva speciale
(L O C A L S ) e una notazione che rendono libero il programmatore nella scelta dei nomi:
Direttiva
LOCALS
Sintassi:
LOCALS
Scopo:
Impone all’assemblatore di trasformare le etichette scritte con un prefisso speciale @@nome univoche aldilà della
rimanente parte del nome
Nota:
La direttiva va posta all’inizio del modulo sorgente, subito dopo la direttiva che indica l’inizio dell’area Codice (.CODE).
La direttiva vale solo per l’assemblatore TASM.
In definitiva, un codice che usa tali direttive, e che stampa due stringhe con due procedure analoghe, è il seguente:
.MODEL TINY
.CODE
JUMPS
LOCALS
ORG 100h
; Direttiva per evitare il limite del salto lungo (in questo codice però non ce ne sono)
; Direttiva per usare etichette con nomi uguali (tramite il prefisso @@)
START:
jmp MAIN
MSG_1 DB "Sistemi Abacus",0
MSG_2 DB "Classe 3a$"
; Stringa ASCIIZ (termina con uno zero)
; Stringa che termina con il carattere speciale $ (come nel servizio MsDos)
PROC STAMPAASCIIZ
push bp
mov bp,sp
mov bx,[bp+4]
@@ANCORA:
mov dl,[bx]
cmp dl,0
je @@FATTO
mov ah,2
int 21h
inc bx
jmp @@ANCORA
@@FATTO:
pop bp
ret
ENDP
; procedura che stampa a schermo stringhe ASCIIZ (indirizzo passato sullo stack)
; ecco le etichette con il prefisso @@ che consentono nomi uguali in accordo con LOCALS
.doc - 1684Kb - 12/set/2008 - 80 / 82
S3Abacus – Architetture/Asm
; procedura che stampa a schermo stringhe terminanti con $ (indirizzo passato sullo stack)
PROC STAMPADOLLARO
push bp
mov bp,sp
mov bx,[bp+4]
@@ANCORA:
mov dl,[bx]
cmp dl,'$'
je @@FATTO
mov ah,2
int 21h
inc bx
jmp @@ANCORA
@@FATTO:
pop bp
ret
ENDP
MAIN:
; ecco le etichette con il prefisso @@ che consentono nomi uguali in accordo con LOCALS
lea ax,msg_1
push ax
call STAMPAASCIIZ
add sp,2
mov ah,2
mov dl, 10
int 21h
lea ax,msg_2
push ax
call STAMPADOLLARO
add sp,2
int 20h
end START
OUTPUT
C:\>jumpslcl
Sistemi Abacus
Classe 3a
C:\>
LIBRERIE, DIRETTIVE INCLUDE, PUBLIC ED EXTRN
Come per i linguaggi ad alto livello, programmare in assembly diventa veramente proficuo quando si possono usare moduli di l i b r e r i a , cioè files
sorgenti o binari che contengono procedure o definizioni di utilità generale, utilizzabili nei programmi senza dover, ogni volta, riscrivere la soluzione
di problemi già risolti.
Lo sviluppo dei programmi con lo stile del p r o g e t t o e tramite moduli di libreria è una pratica oramai consolidata nel mondo della
programmazione.
Un progetto è l’insieme di più moduli sorgenti (a volte anche moduli binari), di cui uno solo contiene il punto di ingresso del programma e, tutti gli
altri, sono detti moduli di libreria. La compilazione di un progetto è la compilazione di ogni modulo, e la loro unione tramite linker nel t a r g e t del
progetto, solitamente un file eseguibile.
Naturalmente un progetto deve affrontare il problema dei rapporti tra i moduli i quali possono essere, alternativamente, sia c l i e n t che s e r v e r
di funzioni presenti in altri moduli: sono client se citano elementi presenti in altri moduli; sono server se contengono definizioni citate da altri moduli.
Il modulo principale, invece, è l’unico che è sempre e solo un modulo client.
Il modo più semplice per realizzare il rapporto tra il modulo principale e altri moduli server è tramite la direttiva I N C L U D E .
Con questa direttiva, usata dal modulo principale, si indica all’assemblatore di aprire da disco il file argomento della direttiva (modulo server) ed
espanderlo nel modulo principale (modulo client) “così com’è” a partire dalla posizione in cui si trova la direttiva INCLUDE nel modulo principale. Il
processo è del tutto paragonabile a quello di una macro di codice. In questo caso la libreria (modulo server) è detta l i b r e r i a d i c o d i c e .
Direttiva
INCLUDE
Sintassi:
INCLUDE nomefile
Scopo:
Impone all’assemblatore di cercare il file nomefile, aprirlo ed espanderlo riga per riga nella posizione corrente.
Nota:
La direttiva può essere posta in qualsiasi zona del sorgente; il nome del file può essere indicato anche con il percorso. Solitamente i files
assembly d’inclusione hanno estensione .INC.
mainincl.asm
modulo client (principale)
.MODEL TINY
.CODE
ORG 100h
acapo.inc
modulo server (libreria di codice)
CR EQU 13
LF EQU 10
.doc - 1684Kb - 12/set/2008 - 81 / 82
S3Abacus – Architetture/Asm
START:
jmp MAIN
INCLUDE acapo.inc
; qui sarà espanso il file
acapo.inc
MAIN:
mov ah,02
mov dl,'0'
int 21h
call ACAPO
mov ah,02
mov dl,'1'
int 21h
int 20h
end START
ACAPO
mov
mov
int
mov
PROC
ah,2
dl, CR
21h
dl, LF
int 21h
ret
ENDP
Il progetto si compila come se fosse composto da un unico file, il file principale (mainincl.asm). Il file server acapo.inc deve essere raggiungibile
(nell’esempio, è nella cartella corrente):
C:\>tasm mainincl
C:\>tlink mainincl /t
C:\>mainincl
0
1
C:\>
Più spesso il programmatore usa librerie binarie, ovvero moduli server che vengono assemblati autonomamente e collegati ai moduli client durante
la fase di linking.
I moduli client devono dichiarare in testa al codice quali simboli tratti da moduli esterni verranno usati (direttiva E X T R N ), in modo che
l’assemblatore non cada in errore incontrando simboli mai definiti.
A sua volta il server deve dichiarare quali simboli possono essere utilizzati da altri moduli (direttiva P U B L I C ) in modo che l’assemblatore e il linker
sappia come effettuare il collegamento.
Direttiva
EXTRN
Sintassi:
EXTRN nome:tipo
Scopo:
Indica all’assemblatore che un certo simbolo nome non è definito nel modulo sorgente attuale, bensì in uno esterno.
tipo può essere NEAR o FAR se nome è il nome di una procedura; può essere BYTE o WORD se nome è l’etichetta
in un’area dati.
Nota:
La direttiva può essere posta in testa al modulo client, per mettere in evidenza la lista di simboli esterni al sorgente, detti anche
dipendenze.
Per quanto riguarda i nomi delle procedure, il tipo è sempre NEAR se il modello di memoria scelto è TINY, SMALL e COMPACT; FAR
negli altri casi.
Ogni direttiva EXTRN dovrebbe essere associata ad una duale direttiva PUBLIC contenuta in un modulo esterno.
Direttiva
PUBLIC
Sintassi:
PUBLIC nome
Scopo:
Indica all’assemblatore che un certo simbolo nome può essere utilizzato da moduli esterni.
Nota:
La direttiva può essere posta in testa al modulo server, per mettere in evidenza la lista di simboli pubblici che il modulo offre ai moduli
client.
Naturalmente ogni nome in ogni direttiva PUBLIC del modulo deve corrispondere ad una effettiva etichetta nel modulo (funzione o dato).
Lo stesso progetto di poco fa, implementato con libreria binaria:
mainlib.asm
(modulo client principale)
libreria.asm
(modulo server di libreria)
EXTRN ACAPO: NEAR
PUBLIC ACAPO
.MODEL TINY
.CODE
ORG 100h
.MODEL TINY
.CODE
START:
mov ah,02
mov dl,'0'
int 21h
call ACAPO
mov ah,02
ACAPO
mov
mov
int
mov
int
ret
PROC
ah,2
dl, 13
21h
dl, 10
21h
.doc - 1684Kb - 12/set/2008 - 82 / 82
S3Abacus – Architetture/Asm
mov dl,'1'
int 21h
int 20h
end START
ACAPO ENDP
end
In questo caso il processo di compilazione è radicalmente differente rispetto all’uso delle libreire sorgenti tramite la direttiva INCLUDE.
I due moduli sono assemblabili autonomamente, e danno luogo a due files oggetto .OBJ.
Sarà il linker a effettuare il collegamento tra i due moduli binari, come dalla seguente sintassi:
C:\>tasm mainlib
mainlib.obj
C:\>tasm libreria
libreria.obj
C:\>tlink mainlib libreria /t
C:\>mainlib
0
1
C:\>
; assemblaggio modulo client (principale). Genera
; assemblaggio modulo server (libreria). Genera
; correlazione (linking) dei moduli. Genera mainlib.exe
MAKEFILE
Nel caso della compilazione di progetti con librerie binarie, risulta molto utile utilizzare l’utility M A K E in dotazione con Borland C (file MAKE.EXE).
Il programma Make accetta in input un file di testo provvisto delle regole di compilazione di un progetto, ed esegue ordinatamente tutti i passi
necessari per la sua compilazione, assemblando i vari moduli sorgenti (client e server) e linkandoli adeguatamente.
Un makefile quindi è un file di testo scritto con una determinata sintassi, spesso di nome makefile (senza estensione), che viene dato in input al
programma make.exe.
Se il processo è esente da errori, l’output di make sarà il file target (solitamente un file eseguibile) e tutti i files intermedi del caso (solitamente
files .obj).
Il makefile mainlib.mak per il progetto precedente, risulterebbe quindi come il seguente (le righe che iniziano con # sono commenti):
#uso: make –f mainlib.mak
.AUTODEPEND
mainlib.exe:
TLINK mainlib.obj libreria.obj /t
libreria.obj: libreria.asm
TASM libreria.ASM,libreria.OBJ
mainlib.obj: mainlib.asm
TASM mainlib.ASM,mainlib.OBJ
Il processo di make, infine, si avvia nel seguente modo:
C:\>make -f mainlib.mak
MAKE Version 3.6 Copyright (c) 1992 Borland International
Available memory 15728640 bytes
TLINK mainlib.obj libreria.obj /t
Turbo Link Version 5.1 Copyright (c) 1992 Borland International
C:\>
Non è il caso di approfondire il discorso sui makefile, che non rientra negli obiettivi di questo testo. In ogni caso si tratta di un argomento di grande
importanza per tutti i linguaggi di programmazione, anche ad alto livello.