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.