Appunti per Scuola e Università
humanisticheUmanistiche
Appunti e tesine di tutte le materie per gli studenti delle scuole medie riguardanti le materie umanistiche: dall'italiano alla storia riguardanti le materie umanistiche: dall'italiano alla storia 
sceintificheScientifiche
Appunti, analisi, compresione per le scuole medie suddivisi per materie scientifiche, per ognuna troverai appunti, dispense, esercitazioni, tesi e riassunti in download.
tecnicheTecniche
Gli appunti, le tesine e riassunti di tecnica amministrativa, ingegneria tecnico, costruzione. Tutti gli appunti di AppuntiMania.com gratis!
Appunti
informatica
CComputerDatabaseInternetJava
Linux unixReti


AppuntiMania.com » Informatica » Appunti di c » Gli interrupt: gestione

Gli interrupt: gestione




Visite: 1879Gradito:apreciate 4-stela [ Grande appunti ]
Leggi anche appunti:

Gli interrupt: gestione


Gli interrupt: gestione A pag. 119 e seguenti abbiamo visto come un programma C può

L'accessibilitÀ e la durata delle variabili


L'accessibilità e la durata delle variabili In C le variabili possono essere

Il cmos


Il CMOS Le macchine dotate di processore Intel 80286 o superiore dispongono
immagine di categoria



Scarica gratis Gli interrupt: gestione

Gli interrupt: gestione

A pag.  e seguenti abbiamo visto come un programma C può sfruttare gli interrupt, richiamandoli attraverso le funzioni di libreria dedicate allo scopo. Ora si tratta di entrare nel difficile, cioè raccogliere le idee su come scrivere interrupt, o meglio funzioni C in grado di sostituirsi o affiancarsi alle routine DOS e al BIOS nello svolgimento dei loro compiti.



Si tratta di una tecnica indispensabile a tutti i programmi TSR (Terminate and Stay Resident, vedere pag.  ) e sicuramente utile ai programmi che debbano gestire il sistema in profondità (ad es.: ridefinire la tastiera, schedulare operazioni tramite il timer della macchina, etc.).

La tavola dei vettori

Gli indirizzi (o vettori ) degli interrupt si trovano nei primi 1024 byte della RAM; vi sono 256 vettori, pertanto ogni indirizzo occupa 4 byte: dal punto di vista del C si tratta di puntatori far, o meglio, di puntatori a funzioni interrupt, per quei compilatori che definiscono tale tipo di dato

Per conoscere l'indirizzo di un interrupt si può utilizzare la funzione di libreria getvect() , che accetta, quale parametro, il numero dell'interrupt stesso e ne restituisce l'indirizzo. La sua 'controparte' setvect() consente di modificare l'indirizzo di un interrupt, prendendo quali parametri il numero dell'interrupt ed il suo nuovo indirizzo (un puntatore a funzione interrupt). Un'azione combinata getvect() setvect() consente dunque di memorizzare l'indirizzo originale di un interrupt e sostituirgli, nella tavola dei vettori, quello di una funzione user‑defined. L'effetto è che gli eventi hardware o software che determinano l'attivazione di quell'interrupt chiamano invece la nostra funzione. Una successiva chiamata a setvect() consente di ripristinare l'indirizzo originale nella tavola dei vettori quando si intenda 'rimettere le cose a posto'.

Fin qui nulla di particolarmente complicato; se l'utilità di getvect() e setvect() appare tuttavia poco evidente non c'è da preoccuparsi: tutto si chiarirà strada facendo. Uno sguardo più attento alla suddetta tavola consente di notare che, se ogni vettore occupa 4 byte, l'offset di un dato vettore rispetto all'inizio della tavola è ottenibile moltiplicando per quattro il numero del vettore stesso (cioè dell'interrupt corrispondente): ad esempio, il vettore dell'int 09h si trova ad offset  rispetto all'inizio della tavola (il primo byte della tavola ha, ovviamente, offset  ). Dal momento che la tavola si trova all'inizio della RAM, l'indirizzo del vettore dell'int 09h è  24h ). Attenzione: l'indirizzo di un vettore non è l'indirizzo dell'interrupt, bensì l'indirizzo del puntatore all'interrupt. Inoltre, i primi due byte (nell'esempio quelli ad offset  ) rappresentano l'offset del vettore, i due successivi (offset  ) il segmento, coerentemente con la tecnica backwords

Queste considerazioni suggeriscono un metodo alternativo per la manipolazione della tavola dei vettori. Ad esempio, per ricavare l'indirizzo di un interrupt si può moltiplicarne il numero per quattro e leggere i quattro byte a quell'offset rispetto all'inizio della RAM:

int intr_num;

int ptr;

void(interrupt *intr_pointer)();

.

intr_num = 9;

ptr = intr_num*4;

intr_pointer = (void(interrupt *)())*(long far *)ptr;

.

L'integer ptr, che vale  , è forzato a puntatore far a long far perché la tavola dei vettori deve essere referenziata con un segmento:offset, in cui segmento è sempre  long perché 0:ptr deve puntare ad un dato a 32 bit. L'indirezione di (long far *)ptr è il vettore (in questo caso dell'int 09h); il cast a puntatore ad interrupt rende la gestione del dato coerente con i tipi del C. Tale metodo comporta un vantaggio: produce codice compatto ed evita il ricorso a funzioni di libreria (parlando di TSR vedremo che, in quel genere di programmi, il loro utilizzo può essere fonte di problemi: pag.  ). Manco a dirlo, però, comporta anche uno svantaggio: l'istruzione

intr_pointer = (void(interrupt *)())*(long far *)ptr;

è risolta dal compilatore in una breve sequenza di istruzioni assembler. L'esecuzione di detta sequenza potrebbe essere interrotta da un interrupt, il quale avrebbe la possibilità di modificare il vettore che il programma sta copiando nella variabile intr_pointer, con la conseguenza che segmento e offset del valore copiato potrebbero risultare parti di due indirizzi diversi. Una soluzione del tipo

asm cli

intr_pointer = (void(interrupt *)())*(long far *)ptr;

asm sti

non elimina del tutto il pericolo, perche cli non blocca tutti gli interrupt: esiste sempre, seppure molto piccola, la possibilità che qualcuno si intrometta a rompere le uova nel paniere.

In effetti, l'unico metodo documentato da Microsoft per leggere e scrivere la tavola dei vettori è costituito dai servizi 25h e 35h dell'int 21h, sui quali si basano, peraltro, setvect() e getvect()

Int 21h, serv. 25h: Scrive un indirizzo nella tavola dei vettori

Input

AH

AL

DS:DX

25h

numero dell'interrupt

nuovo indirizzo dell'interrupt

Int 21h, serv. 35h: legge un indirizzo nella tavola dei vettori

Input

AH

AL

35h

numero dell'interrupt

Output

ES:BX

indirizzo dell'interrupt

Una copia della tavola dei vettori può essere facilmente creata così:

.

register i;

void(interrupt *inttable[256])();


for(i = 0; i < 256; i++)

inttable[i] = getvect(i);

.

Chi ama il rischio può scegliere un algoritmo più efficiente:

.

void(interrupt *inttable[256])();


asm

.

Una copia della tavola dei vettori può sempre far comodo, soprattutto a quei TSR che implementino la capacità di disinstallarsi (cioè rimuovere se stessi dalla RAM) una volta esauriti i loro compiti: al riguardo vedere pag.  e seguenti.

Questi dettagli sulla tavola dei vettori sono importanti in quanto un gestore di interrupt è una funzione che non viene mai invocata direttamente, né dal programma che la incorpora, né da altri, bensì entra in azione quando è chiamato l'interrupt da essa gestito. Perché ciò avvenga è necessario che il suo indirizzo sia scritto nella tavola dei vettori, in luogo di quello del gestore originale . Il programma che installa un nuovo gestore di interrupt solitamente si preoccupa di salvare il vettore originale: esso deve essere ripristinato in caso di disinstallazione del gestore, ma spesso è comunque utilizzato dal gestore stesso, quando intenda lasciare alcuni compiti alla routine di interrupt precedentemente attiva.

Le funzioni interrupt

Molti compilatori consentono di dichiarare interrupt le funzioni: interrupt è un modificatore che forza il compilatore a dotare quelle funzioni di alcune caratteristiche, ritenute importanti per un gestore di interrupt. Vediamo quali.

Il codice C

#pragma option -k-


void interrupt int_handler(void)


definisce una funzione, int_handler(), che non prende parametri, non restitusce alcun valore ed è di tipo interrupt. Il compilatore produce il seguente codice assembler

_int_handler proc far

push ax

push bx

push cx

push dx

push es

push ds

push si

push di

push bp

mov bp,DGROUP

mov ds,bp

.

pop bp

pop di

pop si

pop ds

pop es

pop dx

pop cx

pop bx

pop ax

iret

_int_handler endp

Si nota innanzitutto che la int_handler() è considerata far: in effetti, i vettori sono indirizzi a 32 bit, pertanto anche quello di ogni gestore di interrupt deve esserlo.

Inoltre la funzione è terminata da una IRET : anche questa è una caratteristica fondamentale di tutte le routine di interrupt, in quanto ogni chiamata ad interrupt (sia quelle hardware che quelle software, via istruzione INT) salva sullo stack il registro dei flag. L'istruzione IRET, a differenza della RET, oltre a trasferire il controllo all'indirizzo CS:IP spinto dalla chiamata sullo stack, preleva da questo una word (una coppia di byte) e con essa ripristina i flag.

Il registro DS è inizializzato a DGROUP : l'operazione non è indispensabile, ma consente l'accesso alle variabili globali definite dal programma che incorpora int_handler()

La caratteristica forse più evidente del listato assembler è rappresentata dal salvataggio di tutti i registri sullo stack in testa alla funzione, ed il loro ripristino in coda, prima della IRET. Va chiarito che non si tratta di una caratteristica indispensabile ma, piuttosto, di una misura di sicurezza: un interrupt non deve modificare lo stato del sistema, a meno che ciò non rientri nelle sue specifiche finalità . In altre parole, il compilatore genera il codice delle funzioni interrupt in modo tale da evitare al programmatore la noia di preoccuparsi dei registri , creandogli però qualche problema quando lo scopo del gestore sia proprio modificare il contenuto di uno (o più) di essi. E' palese, infatti, che modificando direttamente il contenuto dei registri non si ottiene il risultato voluto, perché il valore degli stessi in ingresso alla funzione viene comunque ripristinato in uscita. L'ostacolo può essere aggirato dichiarando i registri come parametri formali della funzione: il compilatore, proprio perché si tratta di una funzione di tipo interrupt, consente di accedere a quei parametri nello stack gestendo quest'ultimo in modo opportuno: quindi, in definitiva, permette di modificare effettivamente i valori dei registri in uscita alla funzione. Torniamo alla int_handler(): se in essa vi fosse, ad esempio, l'istruzione

_BX = 0xABCD;

il listato assembler sarebbe il seguente:

_int_handler proc far

push ax

push bx

push cx

push dx

push es

push ds

push si

push di

push bp

mov bp,DGROUP

mov ds,bp


mov bx,0ABCDh


pop bp

pop di

pop si

pop ds

pop es

pop dx

pop cx

pop bx

pop ax

iret

_int_handler endp

con l'ovvia conseguenza che la modifica apportata al valore di BX sarebbe vanificata dalla POP BX eseguita in seguito.



Ecco invece la definizione di int_handler() con un numero di paramateri formali (di tipo int o unsigned int) pari ai registri:

void interrupt int_handler(int Bp,int Di,int Si,int Ds,int Es,int Dx,

int Cx,int Bx,int Ax,int Ip,int Cs,int Flags)


Il listato assembler risultante dalla compilazione è:

_int_handler proc far

push ax

push bx

push cx

push dx

push es

push ds

push si

push di

push bp

mov bp,DGROUP

mov ds,bp

mov bp,sp ; serve ad accedere allo stack


mov [bp+14],0ABCDh ; non modifica BX, bensi' il valore nello stack


pop bp

pop di

pop si

pop ds

pop es

pop dx

pop cx

pop bx ; carica in BX il valore modificato

pop ax

iret

_int_handler endp

Fig.  : Lo stack dopo l'ingresso nel gestore di interrupt dichiarato con parametri formali.

Come si vede, l'obiettivo è raggiunto. In cosa consiste la peculiarità delle funzioni interrupt nella gestione dello stack per i parametri formali (vedere pag.  e dintorni)? Esaminiamo con attenzione ciò che avviene effettivamente: la chiamata ad interrupt spinge sullo stack i FLAGS CS e IP. Successivamente, la funzione interrupt copia, nell'ordine: AX BX CX DX ES DS SI DI e BP. La struttura dello stack, dopo l'istruzione MOV BP,SP è dunque quella mostrata in figura  . Proprio il fatto che la citata istruzione MOV BP,SP si trovi in quella particolare posizione, cioè dopo le PUSH dei registri sullo stack, e non in testa al codice preceduta solo da PUSH BP (posizione consueta nelle funzioni non interrupt), consente di referenziare i valori dei registri come se fossero i parametri formali della funzione. Se non vi fossero parametri formali, non vi sarebbe neppure (per effetto dell'opzione ‑k‑ ) l'istruzione MOV BP,SP : il gestore potrebbe ancora referenziare le copie dei regsitri nello stack, ma i loro indirizzi andrebbero calcolati come offset rispetto a SP e non a BP. Da quanto detto sin qui, e dall'esame della figura  , si evince inoltre che la lista dei parametri formali della funzione può essere allungata a piacere: i parametri addizionali consentono di accedere a quanto contenuto nello stack 'sopra' i flag. Si tratta di un metodo, del tutto particolare, di passare parametri ad un interrupt: è sufficiente copiarli sullo stack prima della chiamata all'interrupt stesso (come al solito, si consiglia con insistenza di estrarli dallo stack al ritorno dall'interrupt). Ecco un esempio:

.

_AX = 0xABCD;

asm push ax;

asm int 0x77;

asm add sp,2;

.

L'esempio è valido nell'ipotesi che il programma abbia installato un gestore dell'int 77h definito come segue:

void interrupt newint77h(int BP,int DI,int SI,int DS,int ES,int DX,int CX,int BX,

int AX,int IP,int CS,int FLAGS,int AddParm)


Il parametro AddParm può essere utilizzato all'interno della newint77h() e referenzia l'ultima word spinta sullo stack prima dei flag (cioè prima della chiamata ad interrupt): in questo caso ABCDh, il valore contenuto in AX. L'istruzione ADD SP,2 ha lo scopo di estrarre dallo stack la word di cui sopra, eventualmente modificata da newint77h()

Dal momento che l'ordine nel quale i registri sono copiati sullo stack è fisso, deve esserlo (ma inverso ) anche l'ordine nel quale sono dichiarati i parametri formali del gestore di interrupt. Si noti inoltre che non è sempre indispensabile dichiarare tutti i registri, da BP ai Flags, ma soltanto quelli che vengono effettivamente referenziati nel gestore, nonché quelli che li precedono nella dichiarazione stessa. Se, ad esempio, la funzione modifica AX e DX, devono essere dichiarati, nell'ordine, BP DI SI DS ES DX CX BX e AX; non serve (ma nulla vieta di) dichiarare IP CS e Flags. E' evidente che tutti i registri non dichiarati quali parametri del gestore possono essere referenziati da questo mediante lo inline assembly o gli pseudoregistri, ma il loro valore iniziale viene ripristinato in uscita. Esempio:

void interrupt int_handler(int Bp, int Di, int Si)


Consiglio da amico: è meglio non pasticciare con CS e IP, anche quando debbano essere per forza dichiarati (ad esempio per modificare i flag). I loro valori nello stack rappresentano l'indirizzo di ritorno dell'interrupt, cioè l'indirizzo della prima istruzione eseguita dopo la IRET: modificarli significa far compiere al sistema, al termine dell'interrupt stesso, un vero e proprio salto nel buio

Ancora un'osservazione: le funzioni interrupt sono sempre void . Infatti, dal momento che le funzioni non void, prima di terminare, caricano AX (o DX:AX) con il valore da restituire, il ripristino in uscita dei valori iniziali dei registri implica l'impossibilità di restituire un valore al processo chiamante. Esempio:

int interrupt int_handler(int Bp,int Di,int Si,int Ds,int Es,int Dx,int Cx,

int Bx,int Ax)


Il listato assembler risultante è:

_int_handler proc far

push ax

push bx

push cx

push dx

push es

push ds

push si

push di

push bp

mov bp,DGROUP

mov ds,bp

mov bp,sp ; serve ad accedere allo stack


mov [bp+14],0ABCDh ; non modifica BX, bensi' il valore nello stack

mov ax,1 ; referenzia comunque il registro, anche in presenza di

; parametri formali

pop bp

pop di

pop si

pop ds

pop es

pop dx

pop cx

pop bx ; carica in BX il valore modificato

pop ax ; ripristina il valore inziale di AX

iret

_int_handler endp

Il listato assembler evidenzia che l'istruzione return utilizza sempre (ovviamente) i registri e non le copie nello stack, anche qualora AX e DX siano gestibili come parametri formali.

Non si tratta, però, di un limite: si può anzi affermare che l'impossibilità di restituire un valore al processo chiamante è una caratteristica implicita delle routine di interrupt. Esse accettano parametri attraverso i registri, e sempre tramite questi restituiscono valori (anche più di uno). Inoltre, tali regole sono valide esclusivamente per gli interrupt software, che sono esplicitamente invocati da un programma (o dal DOS), il quale può quindi colloquiare con essi. Gli interrupt hardware, al contrario, non possono modificare il contenuto dei registri in quanto interrompono l'attività dei programmi 'senza preavviso': questi non hanno modo di utilizzare valori eventualmente loro restituiti . In effetti, le regole relative all'interfacciamento con routine (le cosiddette funzioni) mediante passaggio di parametri attraverso l'effettuazione di una copia dei medesimi nello stack, e restituzione di un unico valore in determinati registri (AX o DX:AX) sono convenzioni tipiche del linguaggio C; il concetto di interrupt, peraltro nato prima del C, è legato all'assembler.

Le funzioni far

Abbiamo detto che due sono le caratteristiche fondamentali di ogni gestore di interrupt: è una funzione far e termina con una istruzione IRET. Da ciò si deduce che non è indispensabile ricorrere al tipo interrupt per realizzare una funzione in grado di lavorare come gestore di interrupt: proviamo a immaginare una versione più snella della int_handler()

#pragma option -k-    // solito discorso: non vogliamo standard stack frame


void far int_handler(void)


La nuova int_handler() ha il seguente listato assembler:

_int_handler proc far

.

iret

_int_handler endp

La maggiore efficienza del codice è evidente. La gestione dei registri e dello stack è però lasciata interamente al programmatore, che deve provvedere a salvare e ripristinare quei valori che non debbono essere modificati. Inoltre, qualora si debba accedere a variabili globali, bisogna accertarsi che DS assuma il valore corretto (quello del segmento DGROUP del programma che ha installato il gestore), operazione svolta in modo automatico, come si è visto, dalle funzioni dichiarate interrupt

void far int_handler(void)


.

asm


Occorre poi resistere alla tentazione di restituire un valore al processo interrotto: benché la funzione sia far e non interrupt, le considerazioni sopra espresse sull'interfacciamento con gli interrupt mantengono pienamente la loro validità. Inoltre un'istruzione return provoca l'inserimento, da parte del compilatore, di una RET, che mette fuori gioco l'indispensabile IRET: il codice

int far int_handler(void)


diventa infatti

_int_handler proc far

.

mov ax,1

ret

iret

_int_handler endp

con la conseguenza di produrre un gestore che, al rientro, 'dimentica' sullo stack la word dei flag.

In un gestore far l'accesso ai registri può avvenire mediante lo inline assembly o gli pseudoregistri: in entrambi i casi ogni modifica ai valori in essi contenuti rimane in effetto al rientro nel processo interrotto. L'unica eccezione è rappresentata dai flag, ripristinati dalla IRET: il gestore di interrupt ha comunque a disposizione due metodi per aggirare l'ostacolo. Il primo consiste nel sostituire la IRET con una RET 2 : l'effetto è quello di forzare il compilatore ad addizionare  al valore di SP in uscita al gestore , eliminando così dallo stack la word dei flag. Il secondo metodo si risolve nel dichiarare i flag come parametro formale del gestore, con una logica analoga a quella descritta per le funzioni di tipo interrupt. In questo caso, però, la funzione è di tipo far



void far int_handler(int Flags)


e pertanto il compilatore produce:

_int_handler proc far

push bp

mov bp,sp

or word ptr [bp+6],1

iret

_int_handler endp

Fig.  : Lo stack dopo l'ingresso nel gestore far di interrupt dichiarato con parametri formali.

La figura  evidenzia la struttura dello stack dopo l'ingresso nel gestore di interrupt (dichiarato far): nello stack, all'indirizzo (offset rispetto a SS BP+6, c'è la word dei flag, spinta dalla chiamata ad interrupt: proprio perché la funzione è di tipo far, per il compilatore il primo parametro formale si trova in quella stessa locazione. Dopo quello relativo ai flag possono essere dichiarati altri parametri formali, i quali referenziano (come, del resto, nelle funzioni di tipo interrupt) il contenuto dello stack 'sopra' i flag. E' superfluo (speriamo!) ricordare che un gestore di interrupt non viene mai invocato direttamente dal programma, ma attivato via hardware oppure mediante l'istruzione INT: in quest'ultimo caso il programma deve spingere sullo stack i valori che dovranno essere referenziati dal gestore come parametri formali. Vale la pena di precisare che, utilizzando l'opzione ‑k‑ BP viene spinto sullo stack e valorizzato con SP solo se sono dichiarati uno o più parametri formali.

Un'ultima osservazione: il tipo far della funzione risulta incoerente con il tipo interrupt richiesto dalla setvect() per il secondo parametro, rappresentante il nuovo vettore. Si rende allora necessaria un'operazione di cast

void far int_handler(void)          // definizione del gestore di interrupt




void install_handler(void)


Utilizzo dei gestori originali

Quando un gestore di interrupt viene installato, il suo indirizzo è copiato nella tavola dei vettori e sostituisce quello del gestore precedentemente attivo. La conseguenza è che quest'ultimo non viene più eseguito, a meno che il suo vettore non sia stato salvato prima dell'installazione del nuovo gestore, in modo che questo possa invocarlo, se necessario. Inoltre l'ultimo gestore installato è comunque il primo ad essere eseguito e deve quindi comportarsi in modo 'responsabile'. Si può riassumere l'insieme delle possibilità in un semplice schema:

Modalità di utilizzo dei gestori originali di interrupt


Comportamento

Caratteristiche


Il nuovo gestore non invoca quello attivo in precedenza e, in uscita, restituisce il controllo al processo interrotto.

La funzione deve riprodurre in modo completo tutte le funzionalità indispensabili del precedente gestore, eccetto il caso in cui esso abbia proprio lo scopo di inibirle oppure occupi un vettore precedentemente non utilizzato.


Il nuovo gestore, in uscita, cede il controllo al gestore attivo in precedenza: quest'ultimo, a sua volta, terminato l'espletamento dei propri compiti, rientra al processo interrotto.

La funzione può delegare in tutto o in parte al gestore precedente l'espletamento delle funzionalità caratteristiche dell'interrupt. Questo approccio è utile soprattutto quando il nuovo gestore deve intervenire sullo stato del sistema (registri, flag, etc.) prima che esso venga conosciuto dalla routine originale (eventualmente per modificarne il comportamento).


Il nuovo gestore invoca quello attivo in precedenza come una subroutine e, ricevuto nuovamente da questo il controllo, ritorna al processo interrotto dopo avere terminato le proprie operazioni.

La funzione può delegare in tutto o in parte al gestore precedente l'espletamento delle funzionalità caratteristiche dell'interrupt. Questo approccio è seguito soprattutto quando il nuovo gestore ha necessità di conoscere i risultati prodotti dall'interrupt originale, o quando può risultare controproducente ritardarne l'esecuzione.

Nel caso 1 non vi è praticamente nulla da aggiungere a quanto già osservato.

Il caso 2, detto 'concatenamento', merita invece alcuni approfondimenti. Innanzitutto va sottolineato che il nuovo gestore cede il controllo al gestore precedentemente attivo, il quale lo restituisce direttamente al processo interrotto: il nuovo gestore non ha dunque la possibilità di conoscere i risultati prodotti da quello originale, ma soltanto quella di influenzarne, se necessario, il comportamento modificando opportunamente il valore di uno o più registri.

Il controllo viene ceduto al gestore originale mediante un vero e proprio salto senza ritorno, cioè con un'istruzione JMP : bisogna ricorrere allo inline assembly. Vediamo un esempio di gestore dell'int 17h, interrupt BIOS per la stampante. Quando il programma lo installa, esso entra in azione ad ogni chiamata all'int 17h ed agisce come un filtro: se AH è nullo, cioè se è richiesto all'int 17h il servizio 0, che invia un byte in output alla stampante, viene controllato il contenuto di AL (il byte da stampare). Se questo è pari a B3h (decimale  , la barretta verticale), viene sostituito con 21h (decimale  , il punto esclamativo). Il controllo è poi ceduto al precedente gestore, con un salto (JMP) all'indirizzo di questo, ottenuto ad esempio mediante la getvect() e memorizzato nello spazio riservato dalla GlobalData(), in quanto esso deve trovarsi in una locazione relativa a CS

#pragma option -k- // per gli smemorati: niente PUSH BP e MOV BP,SP

#define oldint17h  GlobalData


void GlobalData(void)       // funzione jolly: spazio per vettore originale



void far newint17h(void)


ENDFUNC:

asm jmp dword ptr oldint17h;


Chiaro, no? Se non effettuasse il salto alla routine originale, la newint17h() dovrebbe riprodurne tutte le funzionalità relative alla gestione della stampante. Se il programma fosse compilato con standard stack frame (senza la solita opzione ‑k‑) sarebbe indispensabile un'istruzione in più, POP BP, per ripristinare lo stack:

void far newint17h(void)    // se non e' usata l'opzione -k- il compilatore


Senza la POP BP, la IRET del gestore originale restituirebbe il controllo all'indirizzo IP:BP e utilizzerebbe CS per ripristinare i flag: terribili guai sarebbero assicurati, anche senza considerare che la word dei 'veri' flag rimarrebbe, dimenticata, nello stack.

Il metodo di concatenamento suggerito mantiene la propria validità anche con le funzioni di tipo interrupt; è sufficiente liberare lo stack dalle copie dei registri prima di effettuare il salto:

void interrupt newint17h(int Bp,int Di,int Si,int Ds,int Es,int Dx,int Cx,int Bx,

int Ax)



Si noti che oldint17h è, anche in questo caso, una macro che referenzia in realtà la funzione jolly contenente i dati globali: nonostante le funzioni interrupt provvedano alla gestione automatica di DS, in questo caso il valore originale del registro è già stato ripristinato dalla POP DS

Non sempre il ricorso allo inline assembly è assolutamente indispensabile: la libreria del C Microsoft include la _chain_intr(), che richiede come parametro un vettore di interrupt ed effettua il concatenamento tra funzione di tipo interrupt e gestore originale. Di seguito presentiamo il listato di una funzione analoga alla _chain_intr() di Microsoft, adatta però ad essere inserita nelle librerie C Borland



BARNINGA_Z! - 1992


CHAINVEC.C - chainvector()


void far cdecl chainvector(void(interrupt *oldint)(void));

void(interrupt *oldint)(void); puntatore al gestore originale

Restituisce: nulla


COMPILABILE CON TURBO C++ 2.0


bcc -O -d -c -k- -mx chainvec.c


dove -mx puo' essere -mt -ms -mc -mm -ml -mh




void far cdecl chainvector(void(interrupt *oldint)(void))



La chiamata alla chainvector() spinge sullo stack  5 word: la parte segmento e la parte offset di oldint, l'attuale coppia CS:IP e BP. La chainvector() raggiunge il proprio scopo ripristinando i valori dei registri copiati nello stack dalla funzione interrupt e modificando il contenuto dello stack medesimo in modo tale che la struttura di questo, prima dell'istruzione RET, divenga quella descritta in figura 

La RET trasferisce il controllo all'indirizzo seg:off di oldint, cioè al gestore originale, che viene così eseguito con lo stack contenente le 3 word (flag e CS:IP) salvate dalla chiamata che aveva attivato la funzione interrupt. In pratica, il gestore originale opera come se nulla fosse avvenuto, restituendo il controllo all'indirizzo CS:IP originariamente spinto sullo stack: in altre parole, al processo interrotto.

Fig.  : Lo stack in chainvector() prima dell'esecuzione della RET.

Forse è opportuno sottolineare che la chainvector() può essere invocata solamente nelle funzioni dichiarate interrupt e che, data l'inizializzazione automatica di DS a DGROUP nelle funzioni interrupt, il puntatore al gestore originale può essere una normale variabile globale. Ovviamente, eventuali istruzioni inserite nel codice in posizioni successive alla chiamata a chainvector() non verranno mai eseguite. La chainvector() è dichiarata far, ed il suo unico parametro è un puntatore a 32 bit, pertanto il listato è valido per qualunque modello di memoria (pag.  ). Il trucco, lo ripetiamo, sta nel modificare lo stack in modo tale che esso contenga 5 word: i flag e la coppia CS:IP salvati dalla chiamata ad interrupt, più la coppia segmento:offset rappresentante l'indirizzo del gestore originale. A questo punto è sufficiente che la funzione far che effettua il concatenamento termini, eseguendo la RET, poiché questa non può che utilizzare come indirizzo di ritorno l'indirizzo del gestore originale. Di seguito è presentato un esempio di utilizzo, in cui oldint17h è una normale variabile C, dichiarata come puntatore ad interrupt, inizializzata al valore del vettore dell'int 17h mediante la getvect()

void interrupt newint17h(int Bp,int Di,int Si,int Ds,int Es,int Dx,int Cx,int Bx,

int Ax)


Nel caso di gestori di interrupt dichiarati far, è ancora possibile scrivere una funzione in grado di effettuare il concatenamento ma, mentre nelle funzioni interrupt la struttura dello stack è sempre quella rappresentata nella figura  , con riferimento ai gestori far bisogna distinguere tra parecchie situazioni differenti, a seconda che sia utilizzata oppure no l'opzione ‑k‑ in compilazione, che il gestore sia definito con parametri formali o ne sia privo e che esso faccia o meno uso di varialbili locali; è inoltre indispensabile che il vettore originale sia salvato in una locazione relativa a CS e non a DS. La varietà delle situazioni che si possono di volta in volta presentare è tale da rendere preferibile, in quanto più semplice e sicuro, il ricorso all'istruzione JMP mediante lo inline assembly

Veniamo ora all'esame del caso 3: il gestore di interrupt utilizza la routine originale come subroutine; esso ha dunque la possibilità sia di influenzarne il comportamento modificando i registri, sia di conoscerne l'output (se prodotto) dopo avere da essa ricevuto nuovamente il controllo del sistema.

Un tipico esempio è rappresentato, solitamente, dai gestori dell'int 08h, interrupt hardware eseguito dal clock circa 18 volte al secondo . L'int 08h svolge alcuni compiti, di fondamentale importanza per il buon funzionamento del sistema , dei quali è buona norma evitare di ritardare l'esecuzione: appare allora preferibile che il gestore, invece di portare a termine il proprio task e concatenare la routine originale, invochi dapprima quest'ultima e solo in seguito compia il proprio lavoro

Il metodo che consente di invocare un interrupt come una subroutine è il seguente:

void (interrupt *old08h)(void);


.


old08h = getvect(0x08);

setvect(0x08,new08h);


.


void interrupt new08h(void)


Il puntatore ad interrupt oldint08h viene inizializzato, mediante la getvect(), al valore del vettore dell'int 08h, il quale è invocato dal nuovo gestore con una semplice indirezione del puntatore . Il compilatore è abbastanza intelligente da capire, vista la particolare dichiarazione del puntatore , che la funzione puntata deve essere invocata in maniera particolare: occorre salvare il registro dei flag sullo stack prima della CALL , dal momento che la funzione termina con una IRET anziché con una semplice RET

I patiti dell'assembly possono sostituirsi al compilatore e fare tutto da soli:

void oldint08h(void)



.


(void(interrupt *)(void))*(long far *)oldint08h = getvect(0x08);


.

void interrupt newint08h(void)


.


Si nota subito che il gestore originale è attivato mediante l'istruzione CALL; non sarebbe possibile, ovviamente, utilizzare la INT perché questa eseguirebbe ancora il nuovo gestore, causando un loop senza possibilità di uscita . La PUSHF è indispensabile: essa salva i flag sullo stack e costituisce, come detto, il 'contrappeso' della IRET che chiude la routine di interrupt. Non va infatti dimenticato che la CALL è, di norma, utilizzata per invocare routine 'normali', terminate da una RET, pertanto essa spinge sullo stack solamente l'indirizzo di ritorno (la coppia CS:IP), con la conseguenza che i flag devono essere gestiti a parte. Inutile aggiungere che non si deve assolutamente inserire una POPF dopo la CALL, in quanto i flag sono estratti dallo stack dalla IRET



E' interessante sottolineare che l'algoritmo descritto è valido ed applicabile tanto nei gestori dichiarati interrupt quanto in quelli dichiarati far, con la sola differenza che in questi ultimi l'indirizzo del gestore originale deve trovarsi in una locazione relativa a CS, per gli ormai noti problemi legati alla gestione di DS (vedere pag.  e seguenti).

Due o tre esempi

Gestire gli interrupt conferisce al programma una notevole potenza, poiché lo mette in grado di controllare da vicino l'attività di tutto il sistema. Vi sono però delle restrizioni a quanto le routine di interrupt possono fare in determinate circostanze: per alcuni dettagli sull'argomento si rimanda al capitolo dedicato ai TSR, ed in particolare alle pagine  e seguenti. In questa sede presentiamo qualche esempio pratico di gestore di interrupt, nella speranza di offrire spunti interessanti.

Inibire CTRL-C e CTRL-BREAK

L'interrupt BIOS 1Bh è generato dal rilevamento della sequenza CTRL‑BREAK sulla tastiera. L'int 1Bh installato dal BIOS è costituito semplicemente da una IRET. Il DOS installa al bootstrap un proprio gestore, che valorizza un flag, controllato poi periodicamente per determinare se sia stato richiesto un BREAK. Le sequenze CTRL‑C sono intercettate dall'int 16h, che gestisce i servizi software BIOS per la tastiera. In caso di CTRL‑C o CTRL‑BREAK il controllo è trasferito all'indirizzo rappresentato dal vettore dell'int 23h. Per evitare che una sequenza CTRL‑C o CTRL‑BREAK provochi l'uscita a DOS del programma, è necessario controllare l'interrupt 1Bh (BIOS BREAK), per impedire la valorizzazione del citato flag, e l'interrupt 16h, per mascherare le sequenze CTRL‑C. Inoltre occorre salvare in locazioni relative a CS i vettori originali. Vediamo come procedere.



BARNINGA_Z! - 1992


CTLBREAK.C - funzioni per inibire CTRL-C e CTRL-BREAK


void far int16hNoBreak(void); gestore int 16h

void far int1BhNoBreak(void); gestore int 1Bh

void oldint16h(void); funzione fittizia: contiene il vettore

originale dell'int 16h

void oldint1Bh(void); funzione fittizia: contiene il vettore

originale dell'int 1Bh


COMPILABILE CON TURBO C++ 2.0


bcc -O -d -c -k- -mx ctlbreak.c


dove -mx puo' essere -mt -ms -mc -mm -ml -mh




#pragma  inline

#pragma  option -k-


void oldint16h(void)      // funzione fittizia: locazione relativa a CS per



void oldint1Bh(void)      // funzione fittizia: locazione relativa a CS per



void far int1BhNoBreak(void) // nuovo gestore int 1Bh: non fa proprio nulla



void far int16hNoBreak(void) // nuovo gestore int 16h: fa sparire i CTRL-C



SERV_0:                    // richiesto servizio 00h o 10h: attendere tasto


asm // chiesto di attendere un tasto, int16hNoBreak() si limita a

// controllare in loop se c'e' un tasto nel buffer di tastiera

LOOP_0:

CTRL_C_0:


asm


SERV_1:     // richiesto serv.01h o 11h: controllare se c'e' tasto in buff.


asm


CTRL_C_1:      // il servizio 01h o 11h richiesto dal programma ha rilevato

// la presenza di un CTRL-C in attesa nel buffer di tastiera

asm


EXIT_FUNC:


asm ret 2; // esce e preserva il nuovo valore dei flags


I commenti inseriti nel listato rendono inutile insistere sulle particolarità della int16hNoBreak(), la quale, tra l'altro, comprende in modo completo tutte le modalità di utilizzo dei gestori originali e di rientro al processo interrotto. Vale comunque la pena di riassumerne brevemente la logica: se il programma richiede all'int 16h il servizio 00h o 10h (estrarre un tasto dal buffer di tastiera e, se questo è vuoto, attendere l'arrivo di un tasto), la int16hNoBreak() entra in realtà in un loop nel quale utilizza il servizio 01h o 11h del gestore originale (controllare se nel buffer è in attesa un tasto). In caso affermativo lo preleva col servizio 00h o 10h e lo verifica: se è un CTRL‑C (o simili) fa finta di nulla, cioè lo ignora e rientra nel ciclo; altrimenti restituisce il controllo (e il tasto) al programma chiamante. Se invece il programma richiede il servizio 01h o 11h, questo è effettivamente invocato, ma, prima di restituire la risposta, se un tasto è presente viene controllato. Se si tratta di CTRL‑C è estratto dal buffer mediante il servizio 00h o 10h e al programma viene 'risposto' che non vi è alcun tasto in attesa; altrimenti il tasto è lasciato nel buffer e restituito al programma. L'istruzione RET 2 consente al programma di verificare l'eventuale presenza del tasto mediante il valore assunto dallo ZeroFlag. Se utilizzata in un programma TSR, la int16hNoBreak() può essere modificata come descritto a pag.  per consentire ad altri TSR di assumere il controllo del sistema durante i cicli di emulazione del servizio 00h.

Accodando al listato appena commentato la banalissima main() listata di seguito si ottiene un programmino che conta da  : durante il conteggio CTRL‑C e CTRL‑BREAK sono disabilitati. Provare per credere.

#include <stdio.h>

#include <dos.h>


void main(void)


Inibire CTRL-ALT-DEL

La pressione contemporanea dei tasti CTRL ALT e DEL (o CANC) provoca un bootstrap (warm reset) della macchina. Per impedire che ciò avvenga durante l'elaborazione di un programma, è sufficiente che questo installi un gestore dell'int 09h, interrupt hardware per la tastiera, che intercetta le sequenze CTRL‑ALT‑DEL e le processa senza che queste raggiungano il buffer di tastiera



BARNINGA_Z! - 1992


CTLALDEL.C - funzioni per inibire CTRL-ALT-DEL


void far int09hNoReset(void); gestore int 09h

void oldint09h(void); funzione fittizia: contiene il vettore

originale dell'int 09h


COMPILABILE CON TURBO C++ 2.0


bcc -O -d -c -k- -mx ctlaldel.c


dove -mx puo' essere -mt -ms -mc -mm -ml -mh




#pragma  inline

#pragma  option -k-


void oldint09h(void)



void far int09hNoReset(void)



TEST_ALT:


asm


NO_RESET:         // CTRL-ALT-DEL premuto: ignora la sequenza e restituisce

// il controllo direttamente al processo interrotto

asm

asm iret;


CHAIN_OLD:           // se viene concatenato il gestore originale, tutte le

// operazioni di gestione hardware vengono effettuate

asm

asm jmp dword ptr oldint09h;


Anche in questo caso una semplice main() consente di sperimentare il gestore: durante il conteggio da  il reset mediante CTRL‑ALT‑DEL è disabilitato.

#include <stdio.h>

#include <dos.h>


void main(void)


Attenzione: il programma non deve per nessun motivo essere interrotto con CTRL‑C o CTRL‑BREAK: il nuovo gestore rimarrebbe attivo, ma la RAM da esso occupata verrebbe disallocata e potrebbe essere sovrascritta dai programmi successivamente lanciati. L'effetto sarebbe quasi certamente il blocco del sistema, con la necessità di effettuare un cold reset. Può essere interessante sperimentare un programma 'a prova di bomba' riunendo int09hNoReset() int16hNoBreak() e int1BhNoBreak() (nonché le funzioni fittizie per i vettori originali ) in un unico listato ed aggiungendo una main() che installi e disinstalli tutti e tre i nuovi gestori.

Redirigere a video l'output della stampante

In questo esempio ritroviamo l'ormai noto interrupt 17h, calato, questa volta, in un caso concreto. Per dirigere sul video l'output della stampante occorre intercettare ogni byte inviato in stampa, sottrarlo all'int 17h e 'consegnarlo' all'int 10h, che gestisce i servizi BIOS per il video. In particolare, si può ricorrere all'int 10h, servizio 0Eh, che scrive a video in modalità teletype (scrive un carattere e muove il cursore, interpretando i caratteri di controllo quali CR e LF: proprio come una stampante).



BARNINGA_Z! - 1992


PRNTOSCR.C - funzioni per dirigere a video l'output della stampante


void far int17hNoPrint(void) gestore int 17h

void oldint17h(void); funzione fittizia: contiene il vettore

originale dell'int 17h

void grfgcolor(void); funzione fittizia: contiene il byte per

il colore di foreground su video grafico


COMPILABILE CON TURBO C++ 2.0


bcc -O -d -c -k- -mx prntoscr.c


dove -mx puo' essere -mt -ms -mc -mm -ml -mh




#pragma  inline

#pragma  option -k-


void oldint17h(void)



void grfgcolor(void) // contiene un byte, inzializzato a 7 (bianco)

// puo' essere modificato da programma


void far int17hNoPrint(void)



TTYWRITE:


asm


EXITFUNC:


asm


CHAINOLD:


asm jmp dword ptr oldint17h;


La int17hNoPrint() può essere installata da un TSR: da quel momento in avanti tutto l'output diretto alla stampante è invece scritto a video. Se questo è in modalità grafica, il byte memorizzato nella funzione fittizia grfgcolor() è utilizzato per attivare il colore di foreground: esso è inzializzato a  (bianco), ma può essere modificato con un'istruzione analoga alla seguente:

*((char *)grfgcolor) = NEW_COLOR;

ove NEW_COLOR è una costante manifesta definita con una direttiva #define. Per un esempio di calcolo degli attributi video si veda pag. 




 Se non è zuppa, è pan bagnato. Sempre di puntatori a 32 bit si tratta. Del resto, un puntatore a 32 bit non è che una tipizzazione particolare di un long int

 Ogni interrupt è identificato da un numero, solitamente espresso in notazione esadecimale, da 0 a FFh (255).

CLI, CLear Interrupts, inibisce gli interrupts hardware; STI, STart Interrupts, li riabilita (l'assembler non distingue tra maiuscole e minuscole).

 Con il termine 'originale' non si intende indicare il gestore DOS o ROM‑BIOS, ma semplicemente quello attivo in quel momento.

 Oppure interrupt far: mai standardizzare, ovviamente!

 Tutti gli esempi di questo capitolo presuppongono la presenza dell'opzione ‑k‑ sulla riga di comando del compilatore o l'inserimento della direttiva #pragma option ‑k‑ nel codice sorgente, onde evitare la generazione della standard stack frame, che richiede le istruzioni PUSH BP e MOV BP,SP in testa ad ogni funzione (incluse quelle che non prendono parametri e non definiscono variabili locali), nonché POP BP in coda, prima della RET finale. Nell'esempio di int_handler(), l'assenza di detta opzione avrebbe provocato l'inserimento di MOV BP,SP dopo la MOV DS,BP. Il modello di memoria è lo small, per tutti gli esempi.

 Nome convenzionalmente assegnato dal compilatore al segmento allocato ai dati statici e globali (è gestito in realtà come una label).

 Consideriamo il caso del già citato in 09h. La pressione di un tasto può avvenire in qualsiasi istante, e non necessariamente in risposta ad una richiesta del programma attivo in quel mentre. Ciò significa che il gestore dell'interrupt non può fare alcuna assunzione a priori sullo stato del sistema e deve evitare di modificarlo inopportunamente, in quanto, a sua volta, il processo interrotto può non avere previsto l'interruzione (e può non essersi neppure 'accorto' di essa).

 Beh, non è sempre vero Si consideri, ad esempio, una funzione dichiarata interrupt, compilata inserendo nel codice la direttiva  (o P80386) e l'opzione  sulla command line del compilatore (esse abilitano, rispettivamente da parte dell'assemblatore e del compilatore, l'uso delle istruzioni estese riconosciute dai processori 80386). Il compilatore non genera il codice necessario a salvare sullo stack i registri estesi (EAX EBX, etc.), bensì si occupa solo dei registri a 16 bit. Se la funzione modifica i registri estesi (è evidente che il programma può comunque 'girare' solo sugli 80386 e superiori), i 16 bit superiori di questi non vengono ripristinati automaticamente in uscita, con le immaginabili conseguenze per gli altri programmi sfruttanti le potenzialità avanzate del microprocessore. In casi come quello descritto il programmatore deve provvedere di propria iniziativa al salvataggio e rirpristino dei registri estesi modificati.

 Meglio chiarire: int Bp, int Di, int Si, int Ds, int Es, int Dx, int Cx, int Bx, int Ax, int Ip, int Cs, int Flags, infine gli eventuali parametri aggiuntivi. Ciò a causa delle convenzioni C in fatto di passaggio dei parametri alle funzioni (il primo parametro spinto sullo stack è, in realtà, l'ultimo dichiarato: ancora una volta si rimanda a pag.  e seguenti); i nomi possono però essere scelti a piacere. Attenzione: l'ordine con cui i registri sono spinti sullo stack non è il medesimo per tutti i compilatori. Per eliminare eventuali problemi di portabilità è necessario conoscere a fondo il comportamento del compilatore utilizzato (marca e versione), di solito descritto nella documentazione con esso fornita.

CS e IP, ovviamente, sono accessibili anche tramite inline assembly e gli pseudoregistri. In questo caso, però, modificarne il valore avrebbe l'effetto di far impazzire il sistema immediatamente. Infatti la modifica non riguarderebbe l'indirizzo di ritorno dell'interrupt, bensì l'indirizzo dell'istruzione da esguire subito dopo la modifica stessa.

 Anzi, per un interrupt hardware il modificare i registri equivarrebbe a mescolare le carte in tavola al programma interrotto.

 Un'alternativa consiste nel memorizzare i dati globali in locazioni relative al valore di CS nel gestore e non al valore di DS. In pratica, occorre dichiarare una o più funzioni che servono unicamente a riservare spazio, mediante istruzioni inline assembly DB o DW o DD, per tali dati (in modo, cioè, che contengano dati e non codice eseguibile). I nomi delle funzioni, tramite opportune operazioni di cast, sono referenziati come puntatori ai dati: vedere pag.  per una presentazione dettagliata dell'argomento.

 La sintassi RET n provoca un incremento di SP pari a n

 Si è detto che il valore di DS non è noto a priori in ingresso al gestore: l'unico registro sul quale si può fare affidamento è CS. Vedere pag.  per i dettagli.

 Anche le più recenti versioni del C Borland includono una funzione analoga alla _chain_intr(), comunque, visto che ormai il lavoro è fatto

 A puro titolo di esempio, si propone il listato di una versione della chainvector() valida per gestori far privi di parametri formali e di variabili locali, compilati con opzione ‑k‑

void far chainvector(void(interrupt *oldint)(void))



Se il gestore far avesse un parametro formale (ad esempio: i flag) e nessuna variabile locale, la presenza di una word in più (BP) nello stack imporrebbe l'uso di una chainvector() diversa dalla precedente versione:

void far chainvector(void(interrupt *oldint)(void))



In entrambi i casi il parametro oldint deve essere definito in una locazione relativa a CS, in quanto DS deve comunque essere ripristinato prima della chiamata a chainvector(). Si aggiunga che i due casi presentati sono solamente alcuni tra quelli che possono effettivamente verificarsi. Per questi motivi si è detto che nei gestori far è preferibile utilizzare direttamente l'istruzione JMP piuttosto che implementare differenti versioni di chainvector() e utilizzare di volta in volta quella adatta.

 Per la precisione: 18,21 volte, cioè ogni 55 millisecondi.

 Incrementa il contatore del timer di sistema (un long int situato all'indirizzo 0:46C); decrementa il contatore del sistema di spegnimento del motore dei floppy drives (un byte a ) se non è zero (quando questo raggiunge lo zero il motore viene spento e il flag di stato del motore (un byte a 0:43F) è aggiornato); genera un int 1Ch.

 Piccola digressione: l'int 08h, per la verità, mette a disposizione un meccanismo analogo, rappresentato dall'int 1Ch. Questo, la cui routine di default è semplicemente una IRET, viene invocato dalla routine dell'int 08h al termine delle proprie operazioni. Installando un gestore per l'int 1Ch si ha la certezza che esso sia eseguito ad ogni timer tick. La differenza tra questo approccio e quello descritto (a titolo di esempio) nel paragrafo è che l'int 1Ch è eseguito ad interrupt hardware disabilitati (l'int 08h è l'interrupt hardware di massima priorità dopo il Non Maskable Interrupt (NMI), che gestisce situazioni di emergenza gravissima); al contrario, il nuovo gestore dell'int 08h esegue ad interrupt abilitati tutte le operazioni successive alla chiamata alla routine originale.

 Il C si presta di per sé ai giochi di prestigio. Il concetto è, tutto sommato, semplice: se, per esempio, l'indirezione di un puntatore restituisce il valore contenuto nella variabile puntata, detta indirezione sostituisce, in pratica, quel valore; allora l'indirezione di un puntatore ad una funzione (cioè ad una porzione di codice eseguibile) può essere considerata equivalente al valore restituito da quella funzione (ed è quindi necessario invocare la funzione per conoscere il valore da essa restituito). Vedere pag. 

 Infatti oldint08h punta ad una funzione interrupt, non ad una funzione qualsiasi.

 In effetti, mentre con la CALL viene specificato l'indirizzo della routine da eseguire, con la INT viene specificato il numero dell'interrupt, il cui indirizzo viene ricavato dalla tavola dei vettori, nella quale vi è, evidentemente, quello del nuovo gestore (che sta effettuando la chiamata).

 La sequenza CTRL‑ALT‑DEL ha il seguente effetto: il valore 1234h (flag per l'effettuazione di un warm reset) è copiato alla locazione  e viene eseguito un salto all'indirizzo FFFF:0, indirizzo standard del ROM‑BIOS ove si trova una seconda istruzione di salto all'indirizzo della routine che effettua il bootstrap.

 A dire il vero, dal momento che non si tratta di un programma TSR, si potrebbero tranquillamente utilizzare normali puntatori a funzione interrupt

 Lo abbiamo conosciuto a pagina 

Scarica gratis Gli interrupt: gestione
Appunti su:











Scarica 100% gratis e , tesine, riassunti



Registrati ora

Password dimenticata?
  • Appunti superiori
  • In questa sezione troverai sunti esame, dispense, appunti universitari, esercitazioni e tesi, suddivisi per le principali facoltà.
  • Università
  • Appunti, dispense, esercitazioni, riassunti direttamente dalla tua aula Universitaria
  • all'Informatica
  • Introduzione all'Informatica, Information and Comunication Tecnology, componenti del computer, software, hardware ...