Assembly Base con MASM
Capitolo 21: Istruzioni per il controllo della CPU
Abbiamo visto che tutte le CPU della famiglia 80x86, quando operano
in modalità reale, si servono di un registro FLAGS a 16 bit, il quale
assume la struttura mostrata in Figura 21.1; inoltre, per garantire la compatibilità
verso il basso, le CPU 80386 e superiori dispongono di un registro
EFLAGS a 32 bit, la cui WORD meno significativa coincide
con FLAGS.
Nei precedenti capitoli è stato spiegato che quando si accende il computer, qualunque
CPU della famiglia 80x86 viene inizializzata in modalità di emulazione
dell'8086 (modalità reale 8086); questa fase di inizializzazione
coinvolge anche il registro FLAGS, con il flag TF che viene posto a
0, il flag IF che viene posto a 1, il flag CF che viene
posto a 0, etc.
Questi ed altri aspetti, nel loro insieme, concorrono a determinare la modalità
operativa della CPU, cioè il comportamento che la CPU adotta per
eseguire determinate istruzioni; è necessario tenere presente, infatti, che
esistono particolari istruzioni la cui esecuzione produce effetti dipendenti
dal contenuto di uno o più campi del registro FLAGS.
In base a quanto già sappiamo, i flags OF, SF, ZF, AF,
PF e CF, vengono modificati direttamente dalla CPU subito
dopo l'esecuzione di una operazione logico aritmetica; analizzando il contenuto
di tali flags, il programmatore può ricavare un quadro dettagliato sul risultato
prodotto dall'operazione stessa.
I flags DF, IF e TF, invece, possono essere modificati dal
programmatore, in modo da alterare la modalità operativa predefinita della
CPU; in questo capitolo analizzeremo proprio quelle istruzioni che
permettono al programmatore di "pilotare" il comportamento predefinito della
CPU.
21.1 L'istruzione CLC
Con il mnemonico CLC si indica l'istruzione Clear Carry Flag (azzeramento
del flag CF); l'unica forma lecita per questa istruzione è:
CLC
Il codice macchina dell'istruzione CLC è formato dal solo opcode
F8h=11111000b; quando la CPU incontra questo codice macchina, pone
CF=0.
In gergo informatico il termine inglese clear (pulire, rendere lucido),
applicato ad un valore numerico, significa azzerare il valore stesso; "pulire" il
Carry Flag significa quindi porre CF=0, così come "pulire" il
registro AX significa porre AX=0.
Quando il valore numerico è formato da un solo bit, si utilizzano anche i termini
set/reset; il termine set (inteso come, abilitare), applicato ad un bit
significa porre il bit a 1, mentre il termine reset (inteso come,
disabilitare), applicato ad un bit significa porre il bit a 0.
L'istruzione CLC si ripercuote su tutte quelle operazioni che vengono eseguite
tenendo conto del flag CF; in particolare, si possono citare ADC e
SBB.
Eseguendo, ad esempio, una istruzione CLC, una successiva istruzione del
tipo:
adc ax, bx
produce sicuramente il risultato:
AX = AX + BX + 0
21.1.1 Effetti provocati dall'istruzione CLC sugli operandi e sui flags
L'istruzione CLC non ha operandi; l'esecuzione dell'istruzione CLC
pone CF=0, lasciando inalterati tutti gli altri campi del registro
FLAGS.
21.2 L'istruzione STC
Con il mnemonico STC si indica l'istruzione Set Carry Flag (abilitazione
del flag CF); l'unica forma lecita per questa istruzione è:
STC
Il codice macchina dell'istruzione STC è formato dal solo opcode
F9h=11111001b; quando la CPU incontra questo codice macchina, pone
CF=1.
Analogamente a CLC, l'istruzione STC si ripercuote su tutte quelle
operazioni che vengono eseguite tenendo conto del flag CF; eseguendo, ad
esempio, una istruzione STC, una successiva istruzione del tipo:
adc ax, bx
produce sicuramente il risultato:
AX = AX + BX + 1
21.2.1 Effetti provocati dall'istruzione STC sugli operandi e sui flags
L'istruzione STC non ha operandi; l'esecuzione dell'istruzione STC
pone CF=1, lasciando inalterati tutti gli altri campi del registro
FLAGS.
21.3 L'istruzione CMC
Con il mnemonico CMC si indica l'istruzione Complement Carry Flag
(complemento a 1 del flag CF); l'unica forma lecita per questa
istruzione è:
CMC
Il codice macchina dell'istruzione CMC è formato dal solo opcode
F5h=11110101b; quando la CPU incontra questo codice macchina, pone
CF=NOT(CF).
In sostanza, il contenuto corrente di CF viene sottoposto ad un NOT
logico e il risultato così ottenuto diventa il nuovo contenuto dello stesso
CF; quindi, se CF=0, l'istruzione CMC pone CF=1, mentre
se CF=1, l'istruzione CMC pone CF=0.
21.3.1 Effetti provocati dall'istruzione CMC sugli operandi e sui flags
L'istruzione CMC non ha operandi; l'esecuzione dell'istruzione CMC
modifica il contenuto del solo flag CF, lasciando inalterati tutti gli altri
campi del registro FLAGS.
21.4 L'istruzione CLD
Con il mnemonico CLD si indica l'istruzione Clear Direction Flag
(azzeramento del flag DF); l'unica forma lecita per questa istruzione è:
CLD
Il codice macchina dell'istruzione CLD è formato dal solo opcode
FCh=11111100b; quando la CPU incontra questo codice macchina, pone
DF=0.
Il Direction Flag assume una importanza enorme per le istruzioni della
CPU destinate alla gestione delle stringhe (in questo caso, il termine
generico stringa si riferisce ad un qualunque vettore di elementi, tutti
della stessa natura); tali istruzioni, verranno analizzate in dettaglio nel
prossimo capitolo.
Attraverso le istruzioni per la gestione delle stringhe, la CPU permette
di eseguire numerose operazioni che coinvolgono due blocchi di memoria, chiamati
sorgente e destinazione; tali operazioni comprendono, in particolare,
la copia di informazioni da un blocco all'altro, il confronto tra due blocchi,
etc.
Come comportamento predefinito, la CPU referenzia il blocco sorgente
con DS:SI e il blocco destinazione con ES:DI; modificando
opportunamente il flag DF, il programmatore può chiedere alla CPU
di scorrere tali blocchi, in un verso o nell'altro.
Consideriamo, ad esempio, un trasferimento dati (copia) da un blocco all'altro;
dopo aver copiato il primo byte, dall'indirizzo DS:SI all'indirizzo
ES:DI, la CPU deve aggiornare i registri puntatori, in modo da
procedere con la copia del byte successivo. Se poniamo DF=0, la CPU
incrementa SI e DI di 1 dopo ogni byte copiato; se poniamo,
invece, DF=1, la CPU decrementa SI e DI di 1
dopo ogni byte copiato.
In fase di inizializzazione del computer, la CPU pone DF=0!
21.4.1 Effetti provocati dall'istruzione CLD sugli operandi e sui flags
L'istruzione CLD non ha operandi; l'esecuzione dell'istruzione CLD
pone DF=0, lasciando inalterati tutti gli altri campi del registro
FLAGS.
21.5 L'istruzione STD
Con il mnemonico STD si indica l'istruzione Set Direction Flag
(abilitazione del flag DF); l'unica forma lecita per questa istruzione è:
STD
Il codice macchina dell'istruzione STD è formato dal solo opcode
FDh=11111101b; quando la CPU incontra questo codice macchina, pone
DF=1.
Riprendendo l'esempio fatto per CLD, con DF=1 la CPU decrementa
SI e DI di 1 dopo ogni byte copiato; in tal caso, i due blocchi
vengono percorsi a ritroso.
21.5.1 Effetti provocati dall'istruzione STD sugli operandi e sui flags
L'istruzione STD non ha operandi; l'esecuzione dell'istruzione STD
pone DF=1, lasciando inalterati tutti gli altri campi del registro
FLAGS.
21.6 L'istruzione CLI
Con il mnemonico CLI si indica l'istruzione Clear Interrupt Flag
(azzeramento del flag IF); l'unica forma lecita per questa istruzione è:
CLI
Il codice macchina dell'istruzione CLI è formato dal solo opcode
FAh=11111010b; quando la CPU incontra questo codice macchina, pone
IF=0.
Nei precedenti capitoli abbiamo visto che se poniamo IF=0, la CPU
sospende l'elaborazione di tutte le maskable interrupts (interruzioni
mascherabili) legate a richieste provenienti dall'hardware del computer; tutte
le interruzioni mascherabili vengono messe in stato di attesa finché il flag
IF non viene riportato a 1!
Il contenuto del flag IF non ha, invece, alcun effetto sulle NMI
o non maskable interrupts (interruzioni non mascherabili); lo stesso
discorso vale per le interruzioni software generate dai programmi attraverso
l'istruzione INT.
Molto spesso, può presentarsi la necessità di sospendere temporaneamente
l'elaborazione, da parte della CPU, delle interruzioni mascherabili;
ciò accade, ad esempio, quando deve essere svolto un lavoro molto delicato,
che richiede la totale assenza di interferenze esterne.
Un caso emblematico, già esposto nel precedente capitolo, è rappresentato dal
metodo seguito dalla CPU per elaborare una richiesta di interruzione;
prima di chiamare la relativa ISR, la CPU mette in stato di attesa
tutte le interruzioni mascherabili. Al termine della ISR, la CPU
stessa riabilita l'elaborazione delle interruzioni mascherabili rimaste in
stato di attesa; se non venisse presa questa precauzione, una ISR che
sta rispondendo ad una richiesta di interruzione hardware, verrebbe a sua volta
interrotta da altre IRQ!
Anche il programmatore può avere la necessità di seguire un procedimento del
genere; nei precedenti capitoli abbiamo già analizzato degli esempi pratici
(inizializzazione della coppia SS:SP, installazione di una ISR
personalizzata, etc).
In base alle considerazioni appena esposte, risulta evidente che, sia il
programmatore, sia la CPU, per garantire il corretto funzionamento del
computer, devono tenere IF a zero per il minor tempo possibile!
In fase di inizializzazione del computer, la CPU pone IF=1!
21.6.1 Effetti provocati dall'istruzione CLI sugli operandi e sui flags
L'istruzione CLI non ha operandi; l'esecuzione dell'istruzione CLI
pone IF=0, lasciando inalterati tutti gli altri campi del registro
FLAGS.
21.7 L'istruzione STI
Con il mnemonico STI si indica l'istruzione Set Interrupt Flag
(abilitazione del flag IF); l'unica forma lecita per questa istruzione è:
STI
Il codice macchina dell'istruzione STI è formato dal solo opcode
FBh=11111011b; quando la CPU incontra questo codice macchina, pone
IF=1.
Per garantire un adeguato margine di sicurezza, le interruzioni mascherabili
vengono riabilitate dopo l'elaborazione dell'istruzione successiva alla
stessa STI; in analogia con CLI, anche STI non ha alcun
effetto sulle NMI e sulle software interrupts!
21.7.1 Effetti provocati dall'istruzione STI sugli operandi e sui flags
L'istruzione STI non ha operandi; l'esecuzione dell'istruzione STI
pone IF=1, lasciando inalterati tutti gli altri campi del registro
FLAGS.
21.8 Gestione del Trap Flag (TF)
Le CPU della famiglia 80x86, non forniscono alcuna istruzione
per la gestione diretta del Trap Flag; per abilitare o disabilitare
questo flag, possiamo servirci allora delle maschere di bit mostrate nel
precedente capitolo.
Analizzando la Figura 21.1, possiamo notare che il flag TF si trova in
posizione 8 nel registro FLAGS; di conseguenza, dobbiamo
predisporre le due maschere 0000000100000000b e 1111111011111111b.
A questo punto possiamo notare che:
FLAGS OR 000000010000000b
pone TF=1 e lascia inalterati tutti gli altri flags.
FLAGS AND 1111111011111111b
pone TF=0 e lascia inalterati tutti gli altri flags.
(Naturalmente, bisogna ricordare che il registro FLAGS non è direttamente
accessibile al programmatore).
Ponendo TF=1, sappiamo che la CPU genera una INT 01h per
ogni istruzione eseguita; tale situazione provoca una seria diminuzione delle
prestazioni del computer, per cui si consiglia di tenere TF a 1
solo in casi di assoluta necessità (ad esempio, debugging).
In fase di inizializzazione del computer, la CPU pone TF=0!
21.9 L'istruzione WAIT
Con il mnemonico WAIT si indica l'istruzione WAIT until the BUSY# pin is
high (attendere finché il coprocessore matematico è occupato); l'unica forma
lecita per questa istruzione è:
(i due mnemonici WAIT e FWAIT sono equivalenti).
Il codice macchina dell'istruzione WAIT è formato dal solo opcode
9Bh=10011011b; quando la CPU incontra questo codice macchina, si
pone in uno stato di attesa finché il pin BUSY# rimane a livello logico
1
Il segnale BUSY# viene gestito dal coprocessore matematico (FPU);
attraverso tale segnale, il coprocessore indica se ha terminato o meno
l'elaborazione di una istruzione o di una eccezione.
Nei precedenti capitoli abbiamo visto che le CPU, come tutti i circuiti
integrati, comunicano con il mondo esterno attraverso una serie di terminali
elettrici chiamati "piedini" o pin; uno di questi pin viene chiamato
BUSY# pin e connette elettricamente la CPU con la FPU.
Attraverso il BUSY# pin la CPU è in grado di conoscere, istante per
istante, lo stato attivo/inattivo della FPU. Quando la FPU è attiva
(cioè, quando sta eseguendo dei calcoli), il livello logico del BUSY# pin
vale 1; viceversa, quando la FPU è inattiva, il livello logico del
BUSY# pin vale 0.
In presenza di una istruzione WAIT, la CPU verifica il livello logico
presente nel BUSY# pin. Se il livello logico è 0, la CPU prosegue
normalmente il proprio lavoro; se, invece, il livello logico è 1, la CPU
sospende la propria attività in attesa che lo stesso livello logico venga riportato a
0 dalla FPU.
Le istruzioni della FPU utilizzano dei mnemonici che iniziano tutti per
F (Fast = veloce); ogni volta che la CPU incontra una di queste
istruzioni, la "smista" alla FPU che provvede ad effettuare le necessarie
elaborazioni. La FPU è in grado di eseguire il proprio lavoro in modo
indipendente dalla CPU; in certi casi, però, il programmatore può avere
bisogno di sincronizzare la FPU con la CPU. In generale, ciò
accade quando la FPU deve eseguire un calcolo, il cui risultato deve
essere poi salvato in un registro della CPU; in assenza di sincronizzazione,
può capitare che la CPU acceda in lettura/scrittura a tale registro, prima che
la FPU abbia salvato il risultato. In casi del genere, per ottenere la necessaria
sincronizzazione, si utilizza appunto l'istruzione WAIT.
Si tenga presente che l'istruzione WAIT era indispensabile con le vecchie
CPU della famiglia 80x86, dotate di FPU separata (esterna); se si
prova a disassemblare codice scritto molti anni fa, può capitare di notare la presenza
di una WAIT/FWAIT prima di ogni istruzione della FPU (certi assembler
provvedevano essi stessi ad inserire automaticamente le WAIT/FWAIT prima di
una qualunque istruzione iniziante per F).
Tutti i nuovi modelli di CPU 80x86, a partire dalla 80486 DX, sono
dotati di FPU incorporata e di appositi circuiti di sincronizzazione che
rendono superflua l'istruzione WAIT; in particolare, tali CPU
provvedono automaticamente a testare il pin BUSY# ogni volta che è
previsto uno scambio di dati con la FPU.
Tutti questi concetti vengono esposti in dettaglio in un apposito capitolo della
sezione Assembly Avanzato.
21.9.1 Effetti provocati dall'istruzione WAIT/FWAIT sugli operandi e sui flags
L'istruzione WAIT (o FWAIT) non ha operandi; l'esecuzione dell'istruzione
WAIT (o FWAIT) non modifica alcun campo registro FLAGS.