|
Appunti informatica |
|
Visite: 2781 | Gradito: | [ Grande appunti ] |
Leggi anche appunti:Due file sono il medesimo file?Due file sono il medesimo file? La domanda è formulata in modo fuorviante. Il Allocazione dinamica della memoriaAllocazione dinamica della memoria Quando è dichiarata una variabile, il compilatore Il flusso elaborativoIl flusso elaborativo Qualsiasi programma può venire codificato in un linguaggio |
Si è detto che, normalmente, un TSR viene attivato mediante la pressione di una combinazione di tasti, detta hotkey sequence. Notizie dettagliate sulla tastiera , gli interrupt che la gestiscono ed i servizi da questi resi disponibili si trovano nei paragrafi dedicati all'int 09h e all'int 16h (pagg. e ). Ulteriori esempi di gestori per l'int 09h si trovano alle pagine e
In questa sede vale la pena di riportare la descrizione di due servizi dell'int 21h, utili per pilotare le operazioni di I/O e di popup del TSR (vedere anche pag.
Int 21h, serv. 0Ah: Input di una stringa mediante buffer
Input |
AH DS:DX |
0Ah indirizzo (seg:off) del buffer |
Output |
|
Nessuno; al ritorno il buffer contiene la stringa (vedere note) |
Note |
|
Il primo byte del buffer deve contenere, quando il servizio è invocato, la massima lunghezza consentita per la stringa (al massimo 254 caratteri, incluso il RETURN - ASCII 0Dh - finale). Il secondo byte contiene, in uscita, la lunghezza effettiva della stringa, escluso il RETURN terminatore. La stringa è memorizzata a pertire dal terzo byte del buffer. |
Int 21h, serv. 0Bh: Controllo dello stato dell'input
Input |
AH |
0Bh |
Output |
AL |
FFh se un carattere è disponibile nello Standard Input (generalmente la tastiera); 00h se non vi è alcun carattere. |
Note |
|
Questo servizio può essere utilizzato, tra l'altro, per intercettare la pressione di CTRL‑BREAK durante lunghi cicli di calcolo o altre elaborazioni che non attendono un input dalla tastiera, allo scopo di uscirne a richiesta dell'utente. E' superfluo aggiungere che, allo scopo, il programma (TSR o no) deve incorporare un gestore dell'int 1Bh (vedere pag. |
Devono preoccuparsi di gestire il video tutti i TSR che, durante l'attività in foreground, ne modificano il contenuto: è indispensabile, infatti, che essi lo ripristinino prima di restituire il controllo all'applicazione interrotta. Di qui la necessità di salvare (in altre parole: copiare) il buffer video in un array di caratteri appositamente allocato, ed effettuare l'operazione opposta al momento opportuno. Le tecniche utilizzabili sono più di una: è possibile, ad esempio, ricorrere all'int 10h oppure, se lo si preferisce , accedere direttamente alla memoria video. La tecnica di accesso diretto al buffer video può variare a seconda dello hardware installato e della modalità video selezionata; in questa sede ci occupiamo, in particolare, di alcuni servizi dell'int 10h limitandoci, per brevità, alla gestione della modalità testo.
Int 10h, serv. 00h: Stabilisce nuovi modo e pagina video
Input |
AH AL |
0Fh modo video: MODO COLONNE RIGHE COLORE 00h 40 25 grigi 01h 40 25 si 02h 80 25 grigi 03h 80 25 si 07h 80 25 16 EGA |
Int 10h, serv. 02H: Sposta il cursore alle coordinate specificate
Input |
AH BH
DL |
02h numero della pagina video: PAGINE VALIDE MODI VIDEO 0-7 00h-01h 0-3 02h-03h 0 04h-07h 0-7 0Dh 0-3 0Eh 0-1 0Fh riga colonna |
Int 10h, serv. 03h: Legge la posizione del cursore
Input |
AH BH |
03h numero della pagina video |
Output |
CH CL DH DL |
Scan line iniziale del cursore Scan line finale Riga Colonna |
Int 10h, serv. 05h: Stabilisce la nuova pagina video
Input |
AH AL |
05h numero della pagina video |
Int 10h, serv. 08h: Legge carattere e attributo alla posizione del cursore
Input |
AH BH |
08h numero della pagina video |
Output |
AH AL |
attributo del carattere. Vedere pag. in nota. codice ASCII del carattere |
Int 10h, serv. 09h: Scrive carattere e attributo alla posizione del cursore
Input |
AH AL BH BL CX |
09h codice ASCII del carattere numero della pagina video attributo del carattere numero di coppie car/attr da scrivere |
Note |
|
Non sposta il cursore. |
Int 10h, serv. 0Eh: Scrive un carattere in modo TTY
Input |
AH AL BH |
0Eh codice ASCII del carattere numero della pagina video |
Note |
|
Il carattere è scritto alla posizione del cursore, che viene spostato a destra di una colonna, portandosi a capo se necessario. |
Int 10h, serv. 0Fh: Legge il modo e la pagina video attuali
Input |
AH |
0Fh |
Output |
AH AL BH |
numero di colonne modo video pagina video |
Int 10h, serv. 13h: Scrive una stringa con attributo
Input |
AH AL BH BL CX DH DL ES:BP |
13h subfunzione: pagina video attributo colore (per servizi lunghezza della stringa riga a cui scrivere la stringa colonna a cui scrivere la stringa indirizzo della stringa da scrivere |
Note |
|
Le subfunzioni 2 e 3 interpretano la stringa come una sequenza di coppie di byte carattere/attributo: la stringa è copiata nel buffer video così come è. Attenzione: modificare il valore di BP implica l'impossibilità di utilizzare le variabili automatiche (in quanto ad esse il compilatore accede con indirizzi relativi proprio a BP; vedere pag. e seguenti) fino al momento del suo ripristino. E' consigliabile salvarlo con una PUSH BP immediatamente prima di caricare l'indirizzo della stringa in ES:BP e ripristinarlo con una POP BP subito dopo la chiamata all'int 10h. |
Per un esempio di utilizzo del servizio 13h dell'int 10h vedere pag. e seguenti.
Ecco, in dettaglio, un esempio di strategia operativa, ipotizzando il caso di un TSR che lavori esclusivamente in modo testo 80x25 (se il modo video non è appropriato il TSR rinuncia
|
Controllare il modo video tramite int 10h, serv. 0Fh. |
|
Se il registro AL non contiene o è impossibile effettuare il pop‑up: uscire dalla routine segnalando (ad esempio con un bip) la situazione all'utente. |
|
Altrimenti, se si prevede di modificare il contenuto di BH, salvarlo (esso è utilizzato nelle successive chiamate all'int 10h). |
|
Eseguire l'int 10h, serv. 03h per conoscere la posizione attuale del cursore e salvare il contenuto di DX |
|
Portare il cursore alla posizione desiderata mediante il serv. 02h dell'int 10h (se si desidera salvare tutto il video andare in |
|
Eseguire un loop che, ad ogni iterazione, mediante il serv. 08h dell'int 10h salvi in un apposito array di interi una coppia attributo/carattere e muova il cursore mediante il serv. 02h: se si desidera salvare tutto il video il loop deve essere eseguito 2000 volte (80 x 25). |
|
Se si desidera nascondere il cursore è sufficiente portarlo a riga 26. |
|
Effettuare le operazioni di output connesse all'attività di foreground del TSR. |
|
Portare il cursore nell'angolo superiore sinistro dell'area di video salvata in precedenza. |
|
Ripristinarne il contenuto con un loop analogo a quello descritto al punto 6). |
|
Riportare il cursore alla posizione originaria. |
Le numerose funzioni di libreria atte alla gestione dei file possono essere suddivise in due gruppi: del primo fanno parte quelle (come la fopen() , la fread() , etc.) che operano attraverso una struttura dati di tipo FILE associata al file (il cosiddetto stream ); al secondo appartengono quelle operanti semplicemente attraverso lo handle associato al file (open() read() , etc.: vedere pag. ). Nella parte transiente di un TSR è possibile utilizzare qualsiasi funzione senza correre alcun tipo di rischio, mentre, al contrario, nelle routine residenti è necessaria maggiore attenzione: l'uso delle funzioni appartenenti al primo gruppo è sconsigliato, in quanto esse utilizzano tecniche di allocazione dinamica della memoria 'invisibili' al DOS. Quanto detto deve essere esteso anche a quelle del secondo gruppo quando si ricorra a funzioni come, ad esempio, la setbuf() per gestire attraverso un buffer la operazioni di I/O. Infine, ricordiamo che, in generale, l'uso di funzioni di libreria nelle routine residenti comporta alcuni problemi, dei quali si è detto a pag.
Meglio, allora, armarsi di santa pazienza e ricorrere direttamente ai servizi resi disponibili dall'int 21h, anche se ciò può comportare il ricorso allo inline assembly
Sono, comunque, indispensabili alcune precauzioni importanti: in primo luogo va osservato che i file manipolati dalle routine residenti devono essere da queste gestiti con 'cicli' completi durante l'attività in foreground. Ogni file deve, cioè, essere aperto, letto e/o scritto e, infine, richiuso prima di restituire il controllo all'applicazione interrotta. Vanno evitati nel modo più assoluto comportamenti pericolosi, come aprire i file in fase di installazione e lasciarli aperti a beneficio delle routine residenti, che gestiranno le operazioni di I/O senza mai chiuderli e riaprirli. Si tenga presente che il DOS è, di solito, in grado di mantenere un limitato numero di file aperti contemporaneamente: uno handle attivo ma inutilizzato rappresenta una risorsa preziosa sottratta al sistema. Inoltre, cosa ancor più importante, un TSR (salvo il caso in cui sia dotato di capacità di system monitoring particolarmente sofisticate) non può sapere che accade ai propri file per tutto il tempo in cui altre applicazioni sono attive in foreground: il tentativo (ed è solo un esempio tra i tanti possibili) di leggere dati da un file che non esiste più produrrebbe effetti analoghi a quelli di un tuffo in una piscina vuota
In secondo luogo non bisogna dimenticare che molte delle opzioni accettate dalle funzioni di libreria inerenti la modalità di apertura dei file sono gestite dal DOS attraverso chiamate a servizi differenti. Di seguito, senza pretese di completezza, presentiamo alcuni schemi esplicativi.
Int 21h, Serv. 3Ch: Crea un nuovo file o ne tronca uno esistente
Input |
AH CX DS:DX |
3Ch attributo del file: 00h = normale 01h = sola lettura 02h = nascosto 04h = di sistema indirizzo (seg:off) del nome del file (stringa ASCIIZ |
Output |
AX |
handle per il file se CarryFlag , altrimenti codice dell'errore |
Note |
|
Se il file specificato non esiste, viene creato; se esiste la sua lunghezza è troncata a byte ed il contenuto distrutto. |
Int 21h, Serv. 3Dh: Apre un file esistente
Input |
AH AL
|
3Dh modalità di apertura: campi di bit BIT 0-2: = sola lettura = sola scrittura = lettura/scrittura BIT 3: riservato (sempre BIT 4-6: = modo compatibilità = esclusivo = scrittura non permessa = lettura non permessa = permesse lettura e scrittura BIT 7: = utilizzabile da child process = non utilizzabile da child indirizzo (seg:off) del nome del file (stringa ASCIIZ) |
Output |
AX |
handle per il file se CarryFlag , altrimenti codice dell'errore. |
Note |
|
Se il file specificato non esiste, viene restituito un codice di errore; se esiste viene aperto e il puntatore è posizionato all'inizio del file. Su questo servizio si basa la funzione di libreria _open(): vedere pag. |
Int 21h, Serv. 3Eh: Chiude un file aperto
Input |
AH BX |
3Eh handle del file |
Output |
AX |
codice di errore se CarryFlag |
Note |
|
I buffer associati al file sono svuotati e la directory viene aggiornata. |
Int 21h, Serv. 3Fh: Legge da un file aperto
Input |
AH BX CX DS:DX |
3Fh handle del file numero di byte da leggere indirizzo (seg:off) del buffer di destinazione |
Output |
AX |
codice di errore se CarryFlag , altrimenti numero di byte letti. |
Int 21h, Serv. 40h: Scrive in un file aperto
Input |
AH BX CX DS:DX |
40h handle del file numero di byte da scrivere indirizzo (seg:off) del buffer contenente i byte da scrivere |
Output |
AX |
codice di errore se CarryFlag , altrimenti numero di byte scritti. |
Int 21h, Serv. 41h: Cancella un file
Input |
AH DS:DX |
41h indirizzo (seg:off) del nome del file (stringa ASCIIZ) |
Output |
AX |
codice di errore se CarryFlag |
Int 21h, Serv. 42h: Muove il puntatore alla posizione nel file
Input |
AH AL BX CX:DX |
42h punto di riferimento dell'offset: = da inizio file = da posizione corrente = da fine file handle del file spostamento da effettuare in byte (long integer) |
Output |
AX |
codice di errore se CarryFlag , altrimenti DX:AX = nuova posizione nel file (long int |
Note |
|
E' possibile muovere il puntatore oltre la fine del file: in tal caso la lunghezza del file è aggiornata non appena è scritto almeno un byte. Muovere il puntatore ad un offset negativo rispetto all'inizio del file causa invece un errore. E' necessario effettuare almeno uno spostamento (anche se di byte rispetto alla posizione corrente) tra una operazione di lettura ed una di scrittura, o tra una di scrittura ed una di lettura consecutive. |
Concludiamo il paragrafo con un suggerimento: l'algoritmo utile per gestire il file in append mode, cioè per aprirlo e scrivere in coda al medesimo:
|
Aprire il file mediante int 21h, serv. 3Dh: se il CarryFlag saltare al passo 3) |
|
Aprire il file mediante int 21h, serv. 3Ch: se il CarryFlag l'operazione è fallita: uscire. |
|
Muovere il puntatore al file di byte rispetto alla fine del file mediante int 21h, serv. 42h (AL CX DX ;): se CarryFlag l'operazione è fallita: uscire. |
Se l'operazione 3) riporta CarryFlag è allora possibile effettuare le operazioni di scrittura, non trascurando di chiudere il file mediante int 21h, serv. 3Eh prima di restituire il controllo all'applicazione interrotta. Con la versione 3.0 del DOS è stato introdotto il servizio 5Bh, che agisce in maniera del tutto analoga al servizio 3Ch, ma con una importante differenza: fallisce se il file esiste (invece di troncarlo). Per un esempio pratico si veda a pag.
Il DTA (Disk Transfer Address) è un buffer di 128 byte utilizzato da alcuni servizi dell'int 21h nelle operazioni di I/O con i dischi; per default esso è situato ad offset 80h nel PSP . Al momento del pop‑up il TSR agisce nell'ambiente del programma interrotto e ne condivide, quindi, anche il DTA: le routine residenti, qualora utilizzino servizi basati sul DTA, devono salvare l'indirizzo del DTA dell'applicazione interrotta, comunicare al DOS quello del proprio e ripristinare l'indirizzo originale prima di cedere nuovamente il controllo all'applicazione. I servizi DOS che effettuano tali operazioni sono i seguenti:
Int 21h, serv. 2Fh: Ottiene dal DOS l'indirizzo del DTA attuale
Input |
AH |
2Fh |
Output |
ES:BX |
Indirizzo (seg:off) del DTA attuale (se il servizio è richiesto dalla porzione residente di un TSR, normalmente è quello del programma interrotto). |
Int 21h, serv. 1Ah: Comunica al DOS l'indirizzo del DTA
Input |
AH DS:DX |
1Ah Indirizzo (seg:off) del DTA |
Infine, presentiamo l'elenco dei servizi dell'int 21h che fanno uso del DTA e dunque impongono al TSR le precauzioni di cui si è detto:
Servizi dell'int 21h utilizzanti il DTA
11h |
FindFirst (Espansione wildcard ) mediante FCB |
12h |
FindNext (Espansione wildcard) mediante FCB |
14h |
Lettura sequenziale mediante FCB |
15h |
Scrittura sequenziale mediante FCB |
21h |
Lettura Random mediante FCB |
22h |
Scrittura Random mediante FCB |
27h |
Lettura Random a Blocchi mediante FCB |
28h |
Scrittura Random a Blocchi mediante FCB |
4Eh |
FindFirst (Espansione wildcard) |
4Fh |
FindNext (Espansione wildcard) |
Ad eccezione degli ultimi due in elenco, si tratta di servizi dedicati alla gestione dei file mediante File Control Block : dal momento che le funzionalità da essi offerte si ritrovano nei più recenti (e preferibili) servizi basati sulla tecnica degli handle , solitamente non vi è motivo per utilizzarli nella scrittura di programmi per i quali non sia richiesta la compatibilità con versioni di DOS anteriori alla 2.0.
Il PSP (Program Segment Prefix) è un'area di 256 byte riservata dal DOS in testa al codice di ogni programma caricato ed eseguito. Essa contiene dati di vario tipo ed impiego ed 'ospita' il DTA di default; senza entrare nel merito, in questa sede intendiamo trattarne alcuni aspetti che possono risultare di qualche interesse con riferimento ai TSR.
Va precisato, innanzitutto, che qualunque programma può conoscere l'indirizzo di segmento del proprio PSP utilizzando il servizio 62h dell'int 21h
Int 21h, serv. 62h: Ottiene dal DOS l'indirizzo del PSP attuale
Input |
AH |
62h |
Output |
BX |
Indirizzo di segmento del PSP attuale (se il servizio è richiesto dalla porzione residente di un TSR, normalmente è quello del programma interrotto). |
Tale indirizzo è il valore salvato dallo startup code (vedere pag. ) nella variabile globale _psp (definita in DOS.H ); si ricordi però che gestendo i dati globali della parte residente con lo stratagemma della Jolly(), detta variabile non è disponibile dopo l'installazione: il suo valore deve pertanto essere copiato nello spazio riservato ai dati dalla Jolly() durante la fase stessa di installazione. Le routine residenti possono invocare il servizio di cui sopra per conoscere l'indirizzo del PSP dell'applicazione attiva in quel momento (non il proprio: il TSR condivide l'ambiente dell'applicazione interrotta, PSP compreso). Salvando opportunamente il valore restituito in BX, le routine transienti possono servirsi della funzione 50h dell'int 21h per far conoscere al DOS il loro PSP. E' ovvio che al momento di restituire il controllo all'applicazione interrotta deve essere ripristinato l'originario indirizzo di PSP, ancora mediante il servizio 50h.
Int 21h, serv. 50h: Comunica al DOS l'indirizzo del PSP
Input |
AH BX |
50h Indirizzo di segmento del PSP |
I due servizi esaminati sono disponibili a partire dalla versione 2.0 del DOS, ma solo dalla 3.0 in poi essi non fanno uso dello stack interno di sistema e possono pertanto essere richiesti anche mentre è in corso una precedente chiamata all'int 21h. Se il programma opera sotto una versione di DOS antecedente alla 3.0 il problema può essere aggirato controllando l'InDOS flag oppure simulando un errore critico (forzando a il valore del CritErr flag ), in modo che il DOS non utilizzi lo stack dedicato alle operazioni di I/O (vedere pag.
Conoscere l'indirizzo del PSP della porzione residente del TSR è di importanza fondamentale, tra l'altro, ai fini delle operazioni di disinstallazione: se ne parla a pag.
La word (unsigned int) ad offset 02h nel PSP esprime l'indirizzo di segmento del successivo Memory Control Block (pag. ): esso rappresenta il limite superiore del blocco di RAM allocata alla parte residente del TSR.
La word (unsigned int) ad offset 2Ch nel PSP esprime l'indirizzo di segmento dell'environment assegnato dal DOS al programma. I TSR possono servirsene, oltre che per accedere alle variabili d'ambiente, per disallocare la RAM assegnata all'environment stesso: vedere pag.
Particolare interesse rivestono le due doubleword (puntatori far a funzione) ad offset 0Eh e 12h: esse esprimono gli indirizzi dei gestori attivi dell'int 23h (CTRL‑C/CTRL‑BREAK ; pag. ) e, rispettivamente, dell'int 24h (Errore Critico ; pag. ). Il DOS copia questi due valori nel PSP (dalla tavola dei vettori) quando il programma è invocato ed effettua la copia in direzione opposta (dal PSP alla tavola dei vettori) al termine dell'esecuzione. Ciò implica l'impossibilità, per un TSR, di installare routine permanenti di gestione dei due interrupt suddetti, salvo ricorrere ad un piccolo stratagemma: copiare autonomamente gli indirizzi delle proprie routine di gestione dell'int 23h e dell'int 24h nel PSP durante l'installazione. Ecco come fare:
.
*((long far *)MK_FP(_psp,0x0E)) = (long)new23h;
*((long far *)MK_FP(_psp,0x12)) = (long)new24h;
.
In tal modo i due gestori appartenenti al TSR rimangono attivi anche dopo la restituzione del controllo al DOS: particolare molto importante, questo, se, ad esempio, il programma residente è in realtà una libreria di funzioni volte a completare e migliorare (o semplicemente a modificare) gli standard di comportamento del sistema operativo. I puntatori e gli indirizzi delle funzioni sono gestiti come dati di tipo long piuttosto che come puntatori far a funzione: in effetti, si tratta pur sempre di valori a 32 bit; il vantaggio è nella maggiore semplicità formale del listato (circa la macro MK_FP() , vedere pag.
Il byte ad offset 80h nel PSP esprime la lunghezza della command line del programma, escluso il nome del programma ed incluso il CR (ASCII 0Dh) che la chiude; la command line si trova ad offset 81h. Tali informazioni sono sovrascritte se il programma utilizza il DTA di default (vedere pag. ); sull'argomento si tornerà a pag. , con riferimento alla gestione della command line.
A questo punto dovrebbe essere chiaro che l'attivazione del TSR è un momento delicato, da preparare con accortezza, così come lo sono le attività che esso deve svolgere in foreground. Vediamo allora di raccogliere le idee e di riassumere le operazioni indispensabili per evitare fastidiosi crash di sistema.
Innanzitutto occorre tenere sotto controllo lo stato del DOS, del ROM‑BIOS e dello hardware: l'attivazione del TSR deve essere consentita solo se si verificano contemporaneamente alcune fondamentali condizioni:
|
L'InDOS flag deve essere zero. |
|
Il CritErr flag deve essere zero. |
|
Nessuno dei seguenti interrupt ROM-BIOS deve essere in corso di esecuzione: 05h, 09h, 10h, 13h. |
|
Nessuno degli interrupt hardware deve essere in corso di esecuzione. |
Chi ama vivere pericolosamente può, in qualche misura, derogare alle regole appena descritte: se delle condizioni presentate in tabella la prima non è verificata, l'attivazione del TSR è ancora possibile a patto che l'InDOS flag sia uguale a , e l'attivazione sia effettuata dal nuovo gestore dell'int 28h. In questo caso le routine residenti devono però evitare l'utilizzo dei servizi 00h‑0Ch dell'int 21h (infatti, la situazione descritta si verifica quando il DOS attende un input da tastiera proprio attraverso detti servizi; il loro uso ricorsivo avrebbe conseguenze nefaste).
Si è detto che per conoscere lo stato degli interrupt ROM‑BIOS un TSR può servirsi di un flag (vedere, ad esempio, l'int 10h a pag. ): tutto ciò vale, ovviamente, anche con riferimento agli interrupt hardware. Per questi ultimi, però, esiste un metodo più sofisticato, consistente nell'interrogare direttamente il loro gestore (il chip 8529A):
.
asm
.
I bit del registro AL rappresentano i diversi interrupt hardware: per ciascuno di essi il valore indica che quel particolare interrupt è attivo. Ne segue che se non è eseguito alcun interrupt hardware il valore di AL
Non appena attivato, il TSR deve preoccuparsi di svolgere alcune operazioni, delle quali riportiamo un elenco:
|
Salvare l'indirizzo del PSP corrente e comunicare al DOS quello del proprio. |
|
Salvare l'indirizzo del DTA corrente e comunicare al DOS quello del proprio. |
|
Salvare il vettore dell'int 24h e installare il proprio. |
|
Salvare gli indirizzi dei vettori 23h e 1Bh e installare i propri. |
|
Salvare il contenuto del video. |
Solo dopo essersi opportunamente preparato il terreno il TSR può, finalmente, iniziare la propria attività in foreground: è evidente, comunque, che le operazioni 1) e 2) possono essere tralasciate se il foreground task del TSR non coinvolge PSP e DTA; analoghe considerazioni valgono a proposito dell'int 24h (il TSR non deve effettuare operazioni di I/O con dischi o stampanti, etc.) e degli int 23h e 1Bh (il TSR utilizza esclusivamente servizi DOS 'insensibili' al CTRL‑BREAK/CTRL‑C, quali, ad esempio, le routine di I/O per i file ). Infine, è inutile salvare il contenuto del video se il TSR non lo modifica.
Al termine dell'attività in foreground è necessario, prima di restituire il controllo all'applicazione interrotta, ripristinare il contenuto del video, i vettori 1Bh, 23h e 24h e gli indirizzi del DTA e del PSP di questa. In altre parole, occorre ripercorrere a ritroso le attività di preparazione.
I TSR modificano in qualche misura il comportamento del sistema sottraendo alle altre applicazioni, a partire dal momento dell'installazione, la disponibilità di una porzione più o meno rilevante di RAM e, in particolare, sovrapponendosi (o addirittura sostituendosi) al DOS e al BIOS nella gestione delle routine di interrupt : quando occorra ripristinare le normali caratteristiche del sistema si rende necessario resettare la macchina oppure disinstallare (o disattivare) il programma residente.
I termini disinstallazione e disattivazione non sono sinonimi. Con il primo si indica l'interruzione completa e definitiva di ogni forma di attività del TSR, implicante il ripristino di tutti i vettori di interrupt originali, la chiusura di tutti i file da esso eventualmente gestiti e la disallocazione di tutta la RAM ad esso assegnata (codice, eventuali buffer, environment). Il secondo indica, al contrario, il permanere del TSR in memoria: qualora tutti i vettori originali siano ripristinati, esso non ha più alcuna possibilità di riattivarsi . E' però possibile consentire al TSR un livello minimo di attività (ad esempio di solo monitoraggio) in modo tale che esso sia in grado, al verificarsi di una determinata condizione, di reinstallare le proprie routine di gestione degli interrupt e riprendere così in maniera completa lo svolgimento dei propri compiti
Nelle pagine che seguono si analizzerà nel dettaglio il processo di disinstallazione, in quanto le operazioni necessarie alla disattivazione costituiscono un sottoinsieme di quelle ad esso correlate.
La procedura di installazione di un TSR si conclude, generalmente, con una chiamata alla funzione di libreria keep(), la quale, prima di invocare il servizio 31h dell'int 21h per terminare l'esecuzione del programma e renderlo residente (si veda pag. ), chiama la _restorezero() , la quale, definita nello startup code (pag. ) e non documentata , provvede al ripristino di alcuni vettori di interrupt , salvati dallo startup medesimo prima della chiamata alla main() ; i file aperti non vengono chiusi. La chiamata alla keep() equivale dunque a qualcosa di analogo al listato seguente:
.
void _restorezero(void);
.
_restorezero();
_AH = 0x31;
_AL = retcode;
_DX = resparas;
geninterrupt(0x21);
La exit() termina il programma senza renderlo residente (non è una novità): essa chiude tutti i file aperti e libera la memoria allocata con malloc() e simili; in altre parole effettua tutte le operazioni cosiddette di cleanup, tra le quali vi è pure la chiamata alla _restorezero(). Se ne trae quindi, anche in considerazione dei problemi legati all'utilizzo di funzioni di libreria nella porzione residente dei TSR (pag. ), che non è buona politica procedere alla disinstallazione invocando la exit() . E' inoltre opportuno reprimere la tentazione di installare il programma con una chiamata diretta all'int 21h, evitando così che sia eseguita la _restorezero(), per poterlo poi disinstallare via exit(): va tenuto presente che, qualora i dati globali siano gestiti con il famigerato stratagemma della funzione jolly (gli smemorati e i distratti vedano a pag. ), il segmento dati viene abbandonato al suo destino, con tutto il suo contenuto.
E' necessario procedere a basso livello, cioè a più stretto contatto con il DOS.
Le tecniche di disinstallazione sono, in ultima analisi, due: la prima prevede che tutte le operazioni necessarie allo scopo siano gestite dalla porzione residente, all'interno di una o più routine di interrupt ; la seconda, al contario, lascia a queste il solo compito di fornire i dati necessari (vedremo quali) alla porzione transiente, che provvede alla disinstallazione vera e propria. Quest'ultimo approccio richiede che il TSR sia invocato alla riga di comando del DOS una prima volta per essere installato ed una seconda per essere disinstallato, ma è senza dubbio più 'robusto' del primo, in quanto tutte le operazioni delicate (ripristino dei vettori, disallocazione della RAM) vengono svolte esternamente a routine di interrupt e il programmatore può ridurre al minimo il ricorso allo inline assembly servendosi, se lo preferisce, delle funzioni di libreria del C. Vediamo come procedere, passo dopo passo:
|
Controllare se il TSR è installato. |
|
In caso affermativo richiedere alla porzione transiente l'indirizzo dei dati necessari al completamento dell'operazione. |
|
Procedere al ripristino dei vettori di interrupt e alla disallocazione della memoria. |
Il controllo della presenza del TSR in RAM può essere effettuato via int 2Fh : in proposito si rimanda a quanto detto (ed esemplificato) a pag.
Anche la richiesta dell'indirizzo dei dati può utilizzare il meccanismo di riconoscimento e risposta fornito dall'int 2Fh: la routine transiente carica AH con il byte di identificazione ed AL con il numero del servizio corrispondente, appunto, alla richiesta in questione; l'int 2Fh risponde restituendo l'indirizzo (il quale altro non è che quello della funzione jolly residente in RAM) in AX se si tratta di un segmento, o in DX:AX se è di tipo far (quest'ultimo caso è il più frequente). Questo indirizzo deve necessariamente essere richiesto alla porzione residente del TSR in quanto la parte transiente attiva non avrebbe altro modo per conoscerlo : va ricordato che in questo caso la parte transiente e quella residente appartengono a due distinte istanze del medesimo programma (la prima installata in RAM e la seconda lanciata in un secondo tempo). Segue esempio:
#define UNINSTALL 0x01
void far new2Fh(void)
}
.
Ancora una volta ricordiamo di prestare attenzione allo stack: prima dell'istruzione IRET potrebbe essere necessaria una POP BP (se il compilatore genera automaticamente le istruzioni PUSH BP e MOV BP,SP in apertura del codice della funzione) . Presentiamo anche un esempio di routine transiente che utilizza l'int 2Fh:
void unistall(void)
.
La variabile ResidentJolly è dichiarata puntatore far a void: con opportune operazioni di cast e somme di offset (analoghe a quelle descritte con riferimento alla funzione Jolly(), pag. ) essa può agevolmente essere utilizzata come puntatore a qualsiasi tipo di dato. Quali dati? Tutti i vettori di interrupt agganciati dal TSR (usare tranquillamente setvect() per ripristinarli) e, naturalmente, l'indirizzo di segmento del PSP del TSR, indispensabile per disallocare la RAM.
Questa operazione non presenta particolari problemi. E' sufficiente passare l'indirizzo di segmento del PSP del TSR alla funzione di libreria freemem() per raggiungere lo scopo. Supponendo, per semplicità, che detto indirizzo sia il primo dato salvato nello spazio riservato nella Jolly() durante l'installazione, si può avere:
.
if(freemem(*((unsigned far *)ResidentJolly))
puts('Errore: impossibile disallocare la RAM.');
.
Una precisazione importante: liberare con la freemem() la RAM allocata al TSR non significa rilasciare automaticamente quella occupata dal suo environment: la disallocazione di questa deve essere effettuata esplicitamente, salvo il caso in cui si sia già provveduto durante l'installazione. Sull'argomento si è detto fin troppo a pag.
Se il TSR che viene disinstallato è l'ultimo installato, si cancella ogni traccia della sua presenza nel sistema: i vettori di interrupt tornano ad essere quelli precedenti all'installazione e la struttura della catena dei MCB viene ripristinata. Ma cosa accade se il TSR non è l'ultimo presente in RAM? Il ripristino dei vettori implica che una chiamata a quegli interrupt trasferisca il controllo alle routine che erano attive prima dell'installazione del TSR stesso: i TSR caricati in RAM successivamente all'installazione e prima della disinstallazione di quello, e che abbiano agganciato i medesimi vettori, sono posti fuori gioco. Inoltre, la disallocazione della RAM provoca un'alternanza di blocchi liberi e blocchi assegnati nella serie dei MCB, situazione gestita con qualche difficoltà da alcune versioni di DOS (soprattutto le meno recenti).
Con riferimento alla disattivazione, si può osservare che non sussiste il problema legato alla gestione della RAM, in quanto essa non viene disallocata, mentre rimane valido quanto detto circa i vettori di interrupt, e ciò non solo nel caso in cui il TSR sia disattivato, ma anche quando venga riattivato, dopo il caricamento di altri programmi (forse è meglio rileggere il capitolo dedicato agli interrupt, pag.
Può essere quindi opportuno che un TSR, prima di procedere a disattivazione, riattivazione e disinstallazione, controlli di essere l'ultimo programma residente in RAM: solo in questo caso non vi è rischio di compromettere lo stato del sistema. La funzione che presentiamo (per il template della struct MCB si veda pag. ) consente di disinstallare il TSR (lanciandolo nuovamente al DOS prompt) in condizioni di sicurezza (quasi) assoluta:
BARNINGA_Z! - 1991
LASTTSR.C - AreYouLast()
unsigned *cdecl AreYouLast(long far *vecTable,unsigned ResPSP);
long far *vecTable; puntatore alla copia della tavola dei vettori
creata in fase di installazione.
unsigned ResPSP; indirizzo di segmento del PSP della parte
transiente.
Restituisce: NULL se il TSR non e' l'ultimo programma
residente in RAM
In caso contrario restituisce il puntatore ad
un array che contiene gli indirizzi di tutti i
blocchi che devono essere liberati in quanto
allocati al TSR. L'ultimo elemento dell'array
e' sempre un NULL.
COMPILABILE CON TURBO C++ 1.0
tcc -O -d -c -mx lasttsr.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#include <stdio.h>
#include <dos.h>
#include <alloc.h>
#define LASTBLOCK 'Z'
void _restorezero(void);
unsigned *cdecl AreYouLast(long far *vecTable,unsigned resPSP)
while(mcb->pos != LASTBLOCK);
return(blocks);
La AreYouLast() accetta come parametri il puntatore alla copia della tavola dei vettori creata in fase di installazione e l'indirizzo di segmento del PSP della parte residente
Il primo parametro è utilizzato per confrontare l'attuale tavola dei vettori con quella generata dall'installazione del TSR (si ipotizza che il TSR abbia eseguito la _restorezero() prima di copiare la tavola dei vettori ). Se le due tavole sono identiche (a meno del vettore dell'int 22h, non significativo), la parte residente potrebbe effettivamente essere l'ultimo TSR installato, dal momento che nessuno ha modificato i vettori dopo la sua installazione: per avere un maggiore grado di sicurezza occorre verificare lo stato della catena dei MCB.
Tramite il secondo parametro, la AreYouLast() calcola l'indirizzo del Memory Control Block relativo al PSP della parte residente e lo assegna al puntatore a struttura MCB (vedere pag. ). Questo è dichiarato huge in quanto la normalizzazione automatica garantita dal compilatore consente di trascurare la parte offset del puntatore: essa vale in ogni caso dal momento che un MCB è sempre allineato a un indirizzo di paragrafo (e pertanto varia solo la parte segmento). Il meccanismo del test è semplice: se il MCB successivo a quello della parte residente è il MCB della parte transiente (lo si può verificare mediante il campo psp della struttura), si può ragionevolmente supporre che la porzione residente sia proprio l'ultimo TSR installato, e si può allora procedere ad individuare tutti i blocchi di RAM ad essa allocati.
La variabile blocks è un puntatore ad unsigned: esso punta al primo elemento di un array di unsigned int, ciascuno dei quali è, a sua volta, l'indirizzo di segmento di un blocco di memoria appartenente alla porzione residente del TSR ; l'ultimo elemento dell'array vale sempre NULL. L'indirizzo del primo MCB presente in RAM è ottenuto mediante la getfirstmcb() (vedere pag. ); la ricerca dei MCB si basa su un ciclo ripetuto fino ad incontrare l'ultimo blocco di RAM. L'algoritmo applicato ad ogni MCB è il seguente: se il campo psp è identico al parametro resPSP, allora il blocco appartiene alla parte transiente e si aggiorna l'array degli indirizzi dei blocchi da liberare per passare poi al successivo MCB.
La AreYouLast() restituisce NULL se la parte residente non sembra essere l'ultimo TSR installato o in caso di errore di allocazione della memoria. In caso di successo la AreYouLast() restituisce l'indirizzo dell'array di indirizzi da passare a freemem() per liberare tutti i blocchi di memoria allocati al TSR.
Si è detto che la AreYouLast() consente di valutare se sussistano le condizioni per disinstallare il TSR con sicurezza quasi assoluta: resta da chiarire il significato del 'quasi'.
Al proposito va ricordato che esistono prodotti software , studiati in particolare per macchine dotate di processore 80286, 80386 o superiori che consentono di rimappare agli indirizzi compresi tra A0000h e FFFFFh (cioè tra i 640 Kb e il Mb) una parte della memoria espansa installata: ciò equivale a rendere una zona addizionale di RAM, detta Upper Memory (vedere pag. , direttamente indirizzabile attraverso i registri della CPU (infatti una coppia di registri a 16 bit, rispettivamente segmento ed offset, è in grado di esprimere il numero FFFF:000F quale massimo indirizzo ); è pratica normale utilizzare proprio questa area di RAM per i programmi residenti (onde evitare di sottrarre spazio alle applicazioni nei 640 Kb di memoria convenzionale). Appare chiaro, a questo punto, che se il TSR da disinstallare è residente in memoria convenzionale e nella Upper Memory risiedono programmi installati successivamente (o viceversa), questi non possono essere individuati dalla AreYouLAst(), perché per il DOS la memoria disponibile termina in ogni caso all'indirizzo A0000h e l'ultimo MCB è quello che controlla il blocco di RAM che termina a quell'indirizzo. In tali casi il controllo effettuato sui vettori è decisivo, e dovrebbe rendere la AreYouLast() a prova di bomba, con la sola eccezione di buffer allocati nella Upper Memory dal TSR stesso. A scopo di chiarezza si dà qualche cenno sulla creazione di una copia della tavola dei vettori.
La quantità di RAM necessaria alla porzione residente risulta incrementata di 1 Kb (265 vettori di 4 byte ciascuno), pertanto la funzione jolly deve riservare i 1024 byte necessari (è banale dirlo, ma comunque). In secondo luogo il salvataggio della tavola va effettuato dopo avere agganciato tutti i vettori necessari al TSR e dopo avere ripristinato quelli modificati dallo startup code (pag. ): occorre pertanto invocare esplicitamente la _restorezero(). Ciò nonostante la keep() può ancora essere utilizzata per terminare il programma . Il salvataggio della tavola può essere realizzato mediante la getvect() o, per una maggiore efficienza, tramite indirezioni di puntatori o, ancora, con la funzione di libreria _fmemcpy() . Anche in fase di disinstallazione, come si è visto, è necessario invocare la _restorezero() prima di effettuare il controllo ; inoltre, tutti i gestori di interrupt del TSR devono essere attivati (a meno che l'eventuale routine di disattivazione e riattivazione del TSR provveda anche a modificare opportunamente la copia della tavola dei vettori o si basi semplicemente su flag). La routine di installazione potrebbe presentare la seguente parte terminale:
.
_restorezero();
_fmemcpy((long far *)startUpVectors,MK_FP(0,0),256*sizeof(void far *));
_fmemcpy(((long far *)startUpVectors)+0x23,MK_FP(_psp,0x0E),
2*sizeof(void far *));
keep(code,resparas);
Il nome della funzione fittizia startUpVectors() è forzato a puntatore a long e viene gestito come array; MK_FP(0,0) (pag. ) punta alla tavola dei vettori ( ). Si noti che i vettori, pur essendo a rigore, puntatori a funzioni (di tipo interrupt), sono qui gestiti come long int per migliorare la leggibilità del listato, senza che ciò comprometta la correttezza tecnica del codice, trattandosi in entrambi i casi di dati a 32 bit.
E' necessario scrivere nella copia della tavola dei vettori gli indirizzi dell'int 23h e 24h , prelevandoli dal PSP del programma in fase di installazione, dal momento che il DOS li copia nella tavola dei vettori quando il programa termina (vedere pag. e pag.
Vediamo ora un esempio di programma: si tratta di un semplice TSR, che aggancia l'int 2Fh (per utilizzarlo come canale di comunicazione) e l'int 21h, mascherando il servizio 11h di quest'ultimo. Per disinstallare il TSR occorre lanciarlo nuovamente con un asterisco come parametro sulla command line. E' prevista anche la possibilità di disattivazione e riattivazione, sempre tramite nuova invocazione da DOS prompt (ma con un punto esclamativo quale parametro).
PROVATSR.C - Barninga_Z! - 1991
Esempio di TSR. 'Azzoppa' il comando DIR, che, dopo la
installazione, fornisce risultati erratici. Invocare con
un punto esclamativo come parametro per ripristinare il
comando DIR, lasciando il TSR in RAM. Per riattivare il
TSR lanciarlo nuovamente, sempre con un '!' come parametro
sulla riga di comando. Per disinstallarlo, lanciarlo con
un asterisco come parametro.
Compilabile con TURBO C++ 1.01
tcc -Tm2 -mx provatsr.C
NOTA: -mx rappresenta il modello di memoria: i modelli validi
sono tiny (-mt) small (-ms), medium (-mm), compact (-mc)
e large (-ml). Per il modello huge (-mh) occorre
introdurre una istruzione inline assembly POP DS prima
di ogni POP BP nei due gestori di interrupt, a causa
del salvataggio automatico di DS generato dal compilatore
in ingresso alla funzione.
#pragma inline
#pragma -k+ // il codice e' scritto per TURBO C++ 1.01 (vedere pag.
#include <stdio.h>
#include <dos.h>
#define old21h ((void(interrupt *)())(*(((long far *)ResDataPtr)+0)))
#define old2Fh ((void(interrupt *)())(*(((long far *)ResDataPtr)+1)))
#define Res21h ((void(interrupt *)())(*(((long far *)ResDataPtr)+2)))
#define Res2Fh ((void(interrupt *)())(*(((long far *)ResDataPtr)+3)))
#define ResPSP (*(((unsigned far *)ResDataPtr)+8))
#define ResStatus (*(((unsigned far *)ResDataPtr)+9))
#define ASMold21h GData
#define ASMold2Fh GData+4
#define ASMResStatus GData+18
#define FCB_FFIRST 0x11 /* servizio dell'int 21h da annullare */
#define HEY_YOU 0xAF /* byte di identificazione */
#define HERE_I_AM 0xFDA3 /* risposta: installato */
#define OFF 0
#define ON 1
#define HANDSHAKE 0x00 /* serv. int 2Fh: riconoscimento */
#define UNINSTALL 0x01 /* serv. int 2Fh: disinstallazione */
#define SW_ACTIVE 0x02 /* serv. int 2Fh: attivaz./disattivaz. */
#define UNINST_OPT '*' /* opzione cmd line: disinstallazione */
#define ACTIVE_OPT '!' /* opzione cmd line: attiva/disattiva */
void GData(void); /* prototipo funzione jolly */
void far new21h(void) /* handler int 21h */
CHAIN:
asm jmp dword ptr ASMold21h; /* concatena gestore originale */
void far new2Fh(void) /* handler int 2Fh */
NEXT1:
asm
ANSWER: /* disinstallazione e switch attivazione confluiscono qui */
asm
CHAIN:
asm jmp dword ptr ASMold2Fh; /* concatena gestore originale */
void GData(void) /* funzione jolly */
void releaseEnv(void) /* libera l'environment */
long AreYouThere(char service) /* gestisce comunicazione con TSR */
void uninstall(void) /* gestisce disinstallazione del TSR */
void install(void) /* installa il TSR */
void setStatus(void) /* gestisce switch attivazione/disattivazione */
else
void main(int argc,char **argv)
else /* nessun parametro */
puts('Not Installed: already present in RAM.');
else /* TSR non ancora installato (ignora parametri) */
install();
I numerosi commenti inseriti nel listato eliminano la necessità di descrivere nel dettaglio la struttura e il flusso logico dell'intero programma: ci limitiamo a sottolinearne le particolarità più interessanti, precisando sin d'ora che è stato contenuto quanto più possibile il ricorso allo inline assembly, utilizzando il linguaggio C anche laddove ciò penalizza in qualche misura la compattezza e l'efficienza del codice compilato.
La porzione transiente del programma comunica con quella residente mediante la AreYouThere(), che invoca l'int 2Fh e restituisce, sotto forma di long int il valore restituito dall'interrupt nella coppia di registri DX:AX. Le funzioni che di volta in volta chiamano la AreYouThere() forzano secondo necessità, con opportune operazioni di cast, il tipo di detto valore: la main() ne considera solamente i 16 bit meno significativi, in quanto essi rappresentano la parola d'ordine restituita dal servizio 00h dell'int 2Fh (questo servizio non utilizza il registro DX). Al contrario, la uninstall() e la setStatus() effettuano un cast a (void far *), per gestire correttamente, anche dal punto di vista formale, il puntatore ResDataPtr, il quale è, per il compilatore C, un puntatore a dati di tipo indeterminato: le macro definite in testa al codice permettono di utilizzarlo, nascondendo cast a volte complessi, per scrivere e leggere i dati globali direttamente nel buffer di 20 byte ad essi riservato dalla la funzione jolly GData()
Presentiamo, di seguito, un secondo esempio: si tratta di una versione semplificata del programma precedente, in quanto mancante della capacità di attivarsi e disattivarsi. Differenze sostanziali si riscontrano, però, anche nel metodo utilizzato per la disinstallazione: tutte le operazioni vengono svolte dal gestore dell'int 2Fh. Si tratta dunque di un algoritmo applicabile quando si desideri poter richiedere la disinstallazione mediante hotkey.
PROV2TSR.C - Barninga_Z! - 1991
Esempio di TSR. 'Azzoppa' il comando DIR, che, dopo la
installazione, fornisce risultati erratici. Invocare con
un asterisco come parametro per disinstallarlo.
Compilabile con TURBO C++ 1.01
tcc -Tm2 -mx prov2tsr.C
NOTA: -mx rappresenta il modello di memoria: i modelli validi
sono tiny (-mt) small (-ms), medium (-mm), compact (-mc)
e large (-ml). Per il modello huge (-mh) occorre
introdurre una istruzione inline assembly POP DS prima
di ogni POP BP nei due gestori di interrupt, a causa
del salvataggio automatico di DS generato dal compilatore
in ingresso alla funzione.
#pragma inline
#pragma -k+ // il codice e' scritto per TURBO C++ 1.01 (vedere pag.
#include <stdio.h>
#include <dos.h>
#define old21h ((void(interrupt *)())(*(((long *)GData)+0)))
#define old2Fh ((void(interrupt *)())(*(((long *)GData)+1)))
#define ResPSP (*(((unsigned *)GData)+4))
#define ASMold21h GData
#define ASMold2Fh GData+4
#define ASMResPSP GData+8
#define FCB_FFIRST 0x11
#define HEY_YOU 0xAF
#define HERE_I_AM 0xFDA3
#define HANDSHAKE 0x00
#define UNINSTALL 0x01
#define UNINST_OPT '*'
void GData(void);
void far new21h(void)
CHAIN:
asm jmp dword ptr ASMold21h;
void far new2Fh(void)
NEXT1:
asm
CHAIN:
asm
void GData(void)
void releaseEnv(void)
unsigned AreYouThere(char service)
void install(void)
void uninstall(void)
void main(int argc,char **argv)
Come si può facilmente vedere, la new2Fh() di PROV2TSR.C risulta più complessa rispetto a quella di PROVATSR.C: in effetti essa svolge tutte le operazioni necessarie alla disinstallazione. La coppia di registri DS:SI è caricata per puntare a GData(), cioè al primo dei dati globali (il vettore originale dell'int 21h); la coppia ES:DI punta invece alla tavola dei vettori, ed in particolare al vettore dell'int 21h . Il vettore originale è ripristinato copiando 2 word (4 byte) dalla GData() residente alla tavola. Con la medesima tecnica avviene il ripristino del vettore dell'int 2Fh, dopo opportuno aggiornamento del puntatore alla tavola dei vettori. La RAM è disallocata agendo direttamente sul Memory Control Block del PSP del TSR, il cui indirizzo è ricavato decrementando di uno quello del PSP (il MCB occupa infatti i 16 byte che lo precedono). Allo scopo basta azzerare la coppia di byte (ad offset nel MCB) che esprime l'indirizzo del PSP del programma 'proprietario' del blocco di memoria.
E' stato evitato l'uso dei servizi che l'int 21h rende disponibili per le operazioni ora descritte: si tratta di una scelta effettuata a titolo di esempio, più che di una precauzione volta a rendere massima la sicurezza operativa del TSR (sui problemi legati all'int 21h si veda pag. ), in quanto l'int 2Fh è invocato in modo sincrono dalla parte transiente, la quale 'conosce' lo stato del sistema proprio perché essa stessa lo determina in quel momento. Ciò rende possibile, senza particolari rischi, l'espletamento di quelle operazioni che devono essere effettuate necessariamente via int 21h (chiusura di file, etc.). Quando la disinstallazione non sia richiesta mediante un secondo lancio del programma ma attraverso la pressione di uno hotkey, è necessario prendere alcune precauzioni. La parte transiente deve rilevare lo hotkey attraverso il gestore dell'interrupt di tastiera (int 09h o int 16h): questo si limita modificare lo stato di un flag che viene ispezionato quando il sistema è stabile, per avere la garanzia di procedere in condizioni di sicurezza. Test e disinstallazione possono essere effettuati, ad esempio, nel gestore dell'int 28h o del timer (int 08h). Presentiamo un nuovo listato della new2Fh(): questa versione utilizza l'int 21 in luogo degli accessi diretti alla tavola dei vettori e al MCB:
void far new2Fh(void)
NEXT1:
asm
CHAIN:
asm
Gli offset di volta in volta sommati a GData (il nome della funzione ne rappresenta l'indirizzo, cioè punta alla funzione stessa: vedere pag. ) tengono conto della modalità backwords di memorizzazione dei dati.
In PROV2TSR.C può essere utilizzata la AreYouLast(): essa deve essere chiamata dalla uninstall(), la quale solo se il valore restituitole non fosse NULL disinstalla il TSR ripristinando i vettori e invocando la freemem() per ogni elemento dell'array, eccettuato, naturalmente, il NULL terminale. Si noti che la AreYouLast() presenta caratteristiche più avanzate di quanto necessiti effettivamente a PROV2TSR.C, in quanto esso non gestisce buffer e dunque vi è un solo blocco di memoria allocato alla sua parte residente: quello che la contiene.
Ancora un TSR: questa volta è un programma (quasi) serio. Si tratta di uno screen saver, cioè di un programma che interviene, quando tastiera e mouse non sono sollecitati per un dato intervallo temporale, cancellando il video al fine di prevenirne una eccessiva usura; non appena è premuto un tasto o mosso il mouse, il contenuto del video è ripristinato e la sessione di lavoro può proseguire normalmente (l'operatività della macchina non viene mai bloccata).
SSS.C - Barninga Z! 1994
Sample Screen Saver. TSR, opera validamente in modo testo 80x25.
La stringa passata sulla command line viene visualizzata in posizioni
random a video; se invocato con * come parametro quando residente, si
disinstalla. Il tempo di attesa per entrare in azione e' definito in
ticks di clock dalla costante manifesta MAXTICKS.
Compilato sotto Borland C++ 3.1
bcc -k- sss.c
#pragma inline // usato inline assembly!
#pragma option -k- // evita la standard stack frame; serve nelle
// f() fittizie che definiscono i dati per
// gestire meglio lo spazio. Ovvio bisogna
// tenerne conto anche nelle f() eseguibili che
// usano l'inline assembly
#include <stdio.h>
#include <dos.h>
#include <string.h>
#include <dir.h>
#include <time.h> // per randomize()
#include <stdlib.h> // per randomize() e rand()
#define PRG 'SSS'
#define REL '1.0'
#define YEAR '94'
// costanti manifeste per la gestione del video e del timer
#define MAXTICKS 5460 // 5 minuti, per default
#define MAXSPEED 18 // muove banner: 1 secondo, per default
#define MAXBANNER 40 // max. lunghezza banner; se l'utente
// specifica un banner lungo MAXBANNER,
// il NULL copre RET di resBanner(): OK!
#define MAXPOS 100 // punti del percorso del banner
#define DEFAULTBLANK 0x0720 // blank di default
#define DEFAULTVIDEO 0xB800 // segmento video (default)
#define DEFAULTCOLS 80 // col. video default
#define DEFAULTROWS 25 // righe video default
#define DEFAULTPAGE 0 // pagina video default
// costanti manifeste per i servizi dell'int 2F; 0 e' il servizio di
// riconoscimento, per evitare doppie installazioni in ram del TSR; 1 e' il
// servizio di disinstallazione del TSR dalla memoria
#define HANDSHAKE 0x00 // servizio di scambio parola d'ordine
#define UNINSTALL 0x01 // servizio di disinstallazione
#define UNINST_OPT '*' // da passare sulla comand line per richiedere
// la disinstallazione del TSR
#define HEY_YOU 0xE1 // parola d'ordine di riconoscimento
#define HERE_I_AM 0xB105 // risposta alla parola d'ordine
typedef void DUMMY; // tutte le f() fittizie che definiscono dati
// sono ovviamente void f(void); la typedef
// consente di evidenziare che non sono vere f()
// prototipi delle funzioni
void far new09h(void);
void far new10h(void);
void far new1Ch(void);
void far new2Fh(void);
void far new33h(void);
void _saveregs animate(void);
void _saveregs blankVideo(void);
void _saveregs restoreVideo(void);
int releaseEnv(void);
unsigned areYouThere(char service);
void initializeOffsets(void);
void install(char *banner);
void uninstall(void);
void main(int argc,char **argv);
// inizia qui la parte residente del TSR: dapprima si riserva spazio per i dati
// mediante alcune finte f() che devono solo 'ingombrare' lo spazio necessario,
// poi sono definite tutte le funzioni che lavorano mentre il programma e'
// residente.
// Gruppo delle f() fittizie, usate come contenitori di dati. Equivalgono a
// variabili globali (il nome della funzione puo' essere usato come il nome di
// una variabile, applicando l'opportuno cast) ma si ha la garanzia che lo
// spazio e' allocato in modo statico esattamente dove si vuole: essendo esse
// definite in testa al sorgente, sappiamo che subito dopo lo startup code
// del programma ci sono i dati necessari al TSR quando e' residente in RAM.
// Grazie ad esse, le f() residenti non usano variabili globali o statiche,
// rendendo cosi' possibile limitare l'ingombro in memoria del TSR
DUMMY ticksToWait(DUMMY) // contatore da decrementare per l'attivazione
DUMMY ticksForSpeed(DUMMY) // contatore per la velocita' di animazione
DUMMY resPSP(DUMMY) // indirizzo di segmento del PSP del STR
DUMMY old09h(DUMMY) // vettore originale int 09h
DUMMY old10h(DUMMY) // vettore originale int 10h
DUMMY old1Ch(DUMMY) // vettore originale int 1Ch
DUMMY old2Fh(DUMMY) // vettore originale int 2Fh
DUMMY old33h(DUMMY) // vettore originale int 33h
DUMMY videoBuf(DUMMY) // spazio per il salvataggio del video
DUMMY savedRow(DUMMY) // riga alla quale si trovava il cursore
DUMMY savedCol(DUMMY) // colonna alla quale si trovava il cursore
DUMMY saverActive(DUMMY) // flag che segnala se il saver e' attivo
DUMMY restoreFlag(DUMMY) // flag che segnala di restorare il video
DUMMY inInt10h(DUMMY) // flag che evita ricorsione dell'int 10h
DUMMY resBanner(DUMMY) // stringa per animazione video
DUMMY resBannerLen(DUMMY) // lunghezza banner: per comodita'
DUMMY resBannerOffs(DUMMY) // offsets per scrivere il banner
DUMMY currBannerOff(DUMMY) // contatore per l'array di posizioni
// nuovo gestore dell'int 09h; non serve dichiararlo interrupt perche' non
// usa nessun registro (eccetto AX, salvato e rispristinato da noi stessi), ma
// deve comunque terminare con una iret. Gli underscores davanti ai nomi
// servono perche' il compilatore C li aggiunge a tutti i simboli mentre
// l'assembler no. L'uso dell'inline assembly consente di ottenere una funzione
// efficientissima e molto compatta.
void far new09h(void)
// il gestore dell'int 10h e' qui per sicurezza. Dal momento che l'int 1Ch
// usa l'int 10h per pasticciare col cursore, se il timer chiede un interrupt
// proprio mentre e' servito un int 10h e proprio in quel tick di timer si
// azzera il contatore di attesa per lo screen saver e viene cosi' chiamata
// blankVideo(), si ha una ricorsione dell'int 10h. Le routines BIOS non sono
// rientranti e cio' significa un crash di sistema assicurato. Questo gestore
// dell'int 10h alza un flag in ingresso, invoca il gestore originale e al
// rientro da questo resetta il flag. Detto flag deve essere testato
// nell'int 1Ch: se e' 1 bisogna lasciar perdere tutto e rinviare.
void far new10h(void)
// anche new1Ch() e' dichiarata far, per ottenere maggiore efficienza. Quel
// poco che fa e' implementato in inline assembly; i lavori complessi sono
// curati da alcune funzioni di servizio, che possono essere implementate in
// C grazie alla dichiarazione _saveregs. La f() e' strutturata come segue:
// Se lo screen saver e' attivo si controlla se e' premuto un tasto. In caso
// affermativo si ripristinano il video, il contatore dei ticks di attesa, il
// contatore dei ticks di permanenza del banner, il flag di richiesta di
// restore e il flag di screen saver attivo. In caso negativo si esegue la
// funzione animate(). Se invece lo screen saver non e' attivo, si controlla
// se e' stato premuto un tasto. In caso affermativo si ripristinano il flag
// di tasto premuto (richiesta restore) e il contatore dei ticks di attesa. In
// caso negativo si controlla se il tempo di attesa e' scaduto (contatore ticks
// di attesa = 0). Se lo e' si esegue blankVideo() e si setta il flag di screen
// saver attivo. Se non lo e' si decrementa il contatore dei ticks di attesa.
void far new1Ch(void)
// new2Fh gestisce il dialogo con il TSR. Se AH contiene HEY_YOU, l'interrupt
// e' stato chiamato dalla porzione transiente del programma stesso, dopo che
// la parte residente e' gia' stata installata, altrimenti e' un altro
// programma e non sono fatti nostri. Se AL contiene HANDSHAKE, e' la parola
// d'ordine: bisogna rispondere HERE_I_AM, cosi' la parte transiente sa che
// il TSR e' gia' installato e non tenta di reinstallarlo. Se AL contiene
// UNINSTALL, la parte transiente sta chiedendo alla parte residente di
// disinstallarsi. L'operazione e' semplice: basta ripristinare i vettori di
// interrupt e liberare la memoria allocata al TSR.
// ATTENZIONE: new2Fh() e' dichiarata far per maggiore efficienza e per poter
// restituire valori nei registri della CPU alla routine chiamante. Percio' e'
// indispensabile salvare e ripristinare, se usati, alcuni registri importanti:
// DS, ES, SI, DI. SI e DI, se usati in righe di inline assembly, sono salvati
// automaticamente dal compilatore in testa alla funzione: ecco perche' sono
// estratti dallo stack con le POP senzxa che ci siano le PUSH corrispondenti!!
void far new2Fh(void)
// nuovo gestore dell'int 33h; non serve dichiararlo interrupt perche' non
// usa nessun registro. E' analogo al gestore dell'int 09h, ma, invece di
// sentire la tastiera, sente il mouse.
void far new33h(void)
// animate() effettua le operazioni di animazione del video mentre lo screen
// saver e' attivo. E' opportuno che NON utilizzi f() di libreria C, onde
// poter lasciare residente solo le f() appositamente definite nel sorgente.
// La dichiarazione _saveregs mette new1Ch() al riparo da brutte sorprese.
void _saveregs animate(void)
// si inizializza il puntatore con l'offset eventualmente corretto e si
// scrive il banner.
vPtr = (int far *)MK_FP(DEFAULTVIDEO,offset);
for(i = 0; i < *(int _cs *)resBannerLen; i++)
((char far *)vPtr)[i*2] = ((char _cs *)resBanner)[i];
}
else
// se il contatore non e' zero lo si decrementa, prima o poi si azzerera'
--(*(int _cs *)ticksForSpeed);
// blankVideo() copia nel buffer aaposito la videata presente sul display al
// momento dell'attivazione delloscreen saver e, contemporaneamente, scrive a
// video il carattere e l'attributo definiti come DEFAULTBLANK. La
// dichiarazione _saveregs mette new1Ch() al riparo da brutte sorprese.
void _saveregs blankVideo(void)
// e adesso facciamo sparire il cursore, salvando le sue coordinate attuali e
// spostandolo fuori dal video. E' ok metterlo sulla riga DEFAULTROWS perche'
// la numerazione BIOS delle righe va da 0 a DEFAULTROWS-1.
_AH = 3;
_BH = DEFAULTPAGE;
geninterrupt(0x10);
*(char _cs *)savedRow = _DH;
*(char _cs *)savedCol = _DL;
_DH = DEFAULTROWS;
_AH = 2;
geninterrupt(0x10);
// restoreVideo() ripristina la videata presente sul display al momento della
// attivazione dello screen saver. La dichiarazione _saveregs mette new1Ch() al
// riparo da brutte sorprese.
void _saveregs restoreVideo(void)
// fine della parte residente del TSR. Tutto quello che serve al TSR per
// lavorare, a patto che animate() e sue eventuali subroutines non utilizzino
// funzioni di libreria, sta al di sopra di queste righe di commento. Cio'
// consente di limitare al massimo la quantita' di memoria allocata in modo
// permanente al TSR.
// inizia qui la parte transiente del TSR. Tutte le f() definite a partire
// da questo punto vengono buttate via quando il programma si installa in
// memoria. Queste f() possono fare tutto quello che vogliono, usare f() di
// libreria, variabili globali e statiche, etc.
// releaseEnv() butta alle ortiche l'environment del TSR: dal momento che
// questo non lo utilizza per le proprie attivita' e' inutile lasciarlo li' a
// occupare memoria. Inoltre, dal momento che releaseEnv() e' la prima delle
// funzioni definita fuori dalla parte residente, il suo indirizzo puo' essere
// utilizzato per calcolare quanta ram lasciare residente (secondo parametro)
// della keep()). Restituisce 0 se tutto ok.
int releaseEnv(void)
// areYouThere() e' l'interfaccia di comunicazione con l'int 2Fh, che viene
// invocato passando HEY_YOU in AH e il numero del servizio richiesto in AL.
// L'int 2Fh risponde sempre HERE_I_AM in AX per segnalare che il servizio e'
// stato espletato.
unsigned areYouThere(char service)
// per evitare di utilizzare funzioni di libreria nelle routines residenti, si
// inizializza un array di offsets video ai quali scrivere in sequenza il
// banner. Gli offsets sono generati in modo pseudocasuale. La divisione e
// successiva moltiplicazione applicate al valore restituito da random()
// assicurano che l'offset cosi' ottenuto sia sempre pari.
void initializeOffsets(void)
// install() effettua l'installazione in memoria del TSR, attivando i nuovi
// vettori di interrupt e chiedendo al DOS di riservare al programma la
// memoria necessaria. Questa e' calcolata come differenza tra l'indirizzo
// di releaseEnv() (prima f() transiente) trasformato in indirizzo di segmento
// (cioe' seg+(off/16) arrotondato per eccesso (+1) per sicurezza) e
// l'indirizzo al quale e' caricato (quello del suo PSP). L'indirizzo di
// segmento del PSP (_psp, definita in DOS.H) e' salvato nello spazio riservato
// dalla f() fittizia opportunamente definita: serve al gestore dell'int 2Fh
// per la disinstallazione del programma.
void install(char *banner)
// uninstall() richiede all'int 2Fh la disinstallazione del programma. Se
// areYouThere() non risponde HERE_I_AM e' accaduto qualcosa di strano: in
// teoria cio' dovrebbe avvenire solo se il TSR non e' residente, ma dato che
// prima main() effettua questo controllo prima di invocare uninstall(), la
// probabile causa dell'errore e' che tra le due fasi (controllo e richiesta)
// qualche altro TSR abbia incasinato la memoria disattivando il nostro
// gestore di int 2Fh.
void uninstall(void)
// main() controlla se il TSR e' residente: in tal caso l'unica azione
// possibile e' la disinstallazione. In caso contrario si puo' tentare
// l'installazione
void main(int argc,char **argv)
Il listato di SSS.C contiene commenti in quantità: non pare necessario, pertanto, dilungarsi sulle sue caratteristiche implementative; tuttavia si possono individuare alcuni spunti per migliorarne l'impianto complessivo. In primo luogo, SSS manca di una routine di riconoscimento della modalità video attiva, con la conseguenza che esso lavora correttamente solo se il modo video è testo 80 % 25: un esempio di gestione 'intelligente' del buffer video si trova a pag. . Un secondo limite è costituito dal'impossibilità, per l'utilizzatore, di specificare un tempo di attesa diverso da quello di default (stabilito dalla costante manifesta MAXTICKS); infine, potrebbe essere interessante dotare new09h() di una routine per il riconoscimento di uno hotkey di richiesta di attivazione immediata dello screen saver (un gestore di int 09h assai più sofisticato di quanto occorra in questo caso si trova a pag. ). Al lettore volonteroso non rimane che darsi da fare.
Si tratta, in realtà, di una preferenza condizionata: il primo metodo offre maggiori garanzie di portabilità, mentre il secondo presenta i vantaggi di una maggiore velocità e di un codice eseguibile più compatto (soprattutto se la routine è scritta direttamente in linguaggio assembler). Le nuove schede grafiche introdotte sul mercato (EGA, VGA) hanno eliminato il problema dello 'sfarfallio' (effetto neve) provocato, in alcune circostanze, dall'accesso diretto in scrittura alla memoria video con schede poco evolute (CGA).
Si tratta di una semplificazione piuttosto notevole, che consente, peraltro, di alleggerire (almeno in parte!) il discorso. Ci limitiamo a sottolineare che la sua rimozione comporta l'analisi di due situazioni possibili: il TSR potrebbe modificare la modalità video attraverso il servizio 00h dell'int 10h, oppure adattare il proprio comportamento alla modalità video rilevata, con l'evidente vantaggio di potere in ogni caso effettuare il pop‑up. Quanto alla prima strategia, va osservato che il servizio 00h dell'int 10h cancella il video: ciò avrebbe conseguenze estetiche certamente poco desiderabili. D'altra parte, la scelta del secondo metodo complicherebbe notevolmente la vita al programmatore, in quanto richiederebbe la realizzazione di routine di output a video molto flessibili e parametriche, senza perdere di vista, naturalmente, le consuete esigenze di compattezza ed efficienza.
D'altra parte non sarebbe la prima volta (né l'ultima): abbiamo visto che è quasi impossibile scrivere codice residente efficiente e compatto senza l'aiuto di alcune estensioni del linguaggio, peraltro non sempre portabili verso tutti i compilatori, quale è, appunto, lo inline assembly.
In effetti, il paragone è azzardato. Chi non sa nuotare immagina certo molte spiacevoli situazioni in cui la presenza dell'acqua nella suddetta piscina non sarebbe, comunque, di conforto.
Buffer utilizzati dalle prime versioni di DOS per tenere traccia dei file gestiti dalle applicazioni. Uno dei limiti da essi presentati è che consentono di operare unicamente su file nella directory corrente (la versione 1.0 del DOS non implementa le directory: in ogni disco ne esiste una sola, la root).
Introdotti con il DOS 2.0. Quando un'applicazione apre un file, il DOS associa ad esso un valore a 16 bit (lo handle, appunto) al quale l'applicazione fa riferimento per tutte le operazioni relative a quel file. Vedere pag.
Attenzione, però: eventuali sequenze CTRL‑BREAK o CTRL‑C digitate durante l'attività in foreground del TSR si 'scaricano' sull'applicazione interrotta non appena le viene restituito il controllo.
In realtà, a condizione che la RAM ad esso allocata non sia stata liberata, la disattivazione di un TSR potrebbe anche essere realizzata mediante la sospensione integrale di ogni sua attività: in tal caso la riattivazione (reinstallazione dei vettori) dovrebbe essere effettuata dalla parte transiente (il programma deve essere nuovamente invocato al prompt del DOS e comunicare in modo opportuno, ad esempio via int 2Fh, con la parte residente).
Si pensi, per esempio, ad un programma di redirezione dell'output di altre applicazioni, progettato per visualizzare sul monitor tutto ciò che è diretto alla stampante, fino al verificarsi di un evento particolare (come la pressione di uno hotkey prestabilito) che ne interrompa l'azione: da quel momento esso dovrebbe limitarsi a monitorare la tastiera, per riprendere l'attività di redirezione quando intercetti il medesimo hotkey o un altro evento prescelto dal programmatore.
Per la precisione, si tratta dei vettori 00h, 04h, 05h e 06h. Gli ultimi tre possono essere modificati dalle funzioni appartenenti al gruppo della signal(), se utilizzate nel programma. Ne consegue, tra l'altro, che se le routine residenti fanno uso di tali funzioni devono provvedere autonomamente a salvare e a rispristinare i vettori al momento opportuno.
D'altra parte dovrebbe essere ormai evidente che gli interrupt sono il solo mezzo che i TSR hanno a disposizione per interagire con l'ambiente.
Va però osservato che disattivazione e riattivazione possono essere gestite senza modifiche ai vettori di interrupt: può essere sufficiente un flag opportunamente controllato in testa ai gestori di interrupt che devono essere attivi o inattivi: con un piccolo sacrificio in termini di eleganza, si evitano i problemi di cui sopra.
Meglio ripetere ancora una volta che si tratta di dati salvati in fase di installazione nello spazio riservato da una o più funzioni fittizie. Va ricordato che la parte del TSR residente in memoria e la parte transiente invocata in un secondo tempo, pur essendo due parti di un medesimo programma, si comportano in realtà come se fossero due programmi separati e indipendenti.
I puntatori huge sono automaticamente normalizzati dal compilatore. Ciò significa che la loro parte offset (i 16 bit meno significativi) contiene esclusivamente lo spiazzamento all'interno del paragrafo referenziato dalla parte segmento (i 16 bit più significativi). Vedere pag. e seguenti.
La parte segmento di un puntatore far, costruito mediante due registri a 16 bit, esprime indirizzi di paragrafo (ogni unità conta 16 byte). L'indirizzo FFFF:000F può dunque essere scritto FFFFFh, cifra equivalente al Mb. L'offset non può essere ulteriormente incrementato, poiché un offset di 10h decimale) rappresenta in realtà un incremento di uno della parte segmento, con offset zero. Vedere anche pag.
La keep() invoca a sua volta la _restorezero(), la quale copia nuovamente i vettori originali nella tavola: l'operazione è inutile e potrebbe risultare dannosa, nel caso in cui il programma TSR abbia installato nuovi vettori per gli interrupt 00h, 04h, 05h e 06h.
FINDFIRST mediante File Control Block. Modificando questo servizio si modifica, tra l'altro, il comportamento del comando DIR, che lo utilizza.
I 15 esplicitamente definiti dall'istruzione DB, più i 5 di codice generato automaticamente dal compilatore (opcodes per PUSH BP MOV BP,SP POP BP). L'ordine di memorizzazione dei dati è: vettore originale dell'int 21h (doubleword); vettore originale dell'int 2Fh (doubleword); indirizzo di new21h() (doubleword); indirizzo di new2Fh() (doubleword); indirizzo di segmento del PSP del TSR (word); byte di stato (attivato/disattivato) del TSR (word).
La tavola dei vettori si trova all'indirizzo . Dal momento che ogni vettore è un indirizzo far, ed occupa pertanto 4 byte, per ottenere l'offset di un vettore all'interno della tavola è sufficiente moltiplicare il numero dell'interrupt per 4.
Backwords è un termine nato dall'assonanza con backwards (all'indietro) e significa, letteralmente, a parole rovesciate. In effetti, tutti i dati numerici sono scritti in RAM a rovescio, in modo tale, cioè, che a indirizzo inferiore corrisponda la parte meno significativa della cifra. Ad esempio, il numero 0x11FF024A (che potrebbe rappresentare un puntatore far a 11FF:024A) viene scritto in RAM in modo da occupare 4 byte nel seguente ordine: 4A 02 FF 11
Appunti su: |
|