La gestione dello I/O

Tastiera

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' int09h e all' int16h. Ulteriori esempi di gestori per l'int 09h sono quelli utilizzati nei programmi KBDPLUS e VIDEOCAP.

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.

INT 21H, SERV. 0AH: INPUT DI UNA STRINGA MEDIANTE BUFFER

InputAH0Ah
DS:DXindirizzo (seg:off) del buffer
OutputNessuno; al ritorno il buffer contiene la stringa (vedere note)
NoteIl 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

InputAH0Bh
OutputALFFh se un carattere è disponibile nello Standard Input (generalmente la tastiera);

00h se non vi è alcun carattere.

NoteQuesto 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' int1Bh.

Video

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' int10h oppure, se lo si preferisce[1], 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

InputAH0Fh
ALmodo 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

InputAH02h
BHnumero 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

DHriga
DLcolonna

INT 10H, SERV. 03H: LEGGE LA POSIZIONE DEL CURSORE

InputAH03h
BHnumero della pagina video
OutputCHScan line iniziale del cursore
CLScan line finale
DHRiga
DLColonna

INT 10H, SERV. 05H: STABILISCE LA NUOVA PAGINA VIDEO

InputAH05h
ALnumero della pagina video

INT 10H, SERV. 08H: LEGGE CARATTERE E ATTRIBUTO ALLA POSIZIONE DEL CURSORE

InputAH08h
BHnumero della pagina video
OutputAHattributo del carattere. Vedere anche quanto detto circa i device driver.
ALcodice ASCII del carattere

INT 10H, SERV. 09H: SCRIVE CARATTERE E ATTRIBUTO ALLA POSIZIONE DEL CURSORE

InputAH09h
ALcodice ASCII del carattere
BHnumero della pagina video
BLattributo del carattere
CXnumero di coppie car/attr da scrivere
NoteNon sposta il cursore.

INT 10H, SERV. 0EH: SCRIVE UN CARATTERE IN MODO TTY

InputAH0Eh
ALcodice ASCII del carattere
BHnumero della pagina video
NoteIl 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

InputAH0Fh
OutputAHnumero di colonne
ALmodo video
BHpagina video

INT 10H, SERV. 13H: SCRIVE UNA STRINGA CON ATTRIBUTO

InputAH13h
ALsubfunzione:

0 = usa l'attributo in BL; non sposta il cursore
1 = usa l'attributo in BL; sposta il cursore
2 = usa gli attributi nella stringa; non sposta il cursore
3 = usa gli attributi nella stringa; sposta il cursore

BHpagina video
BLattributo colore (per servizi 0-1)
CXlunghezza della stringa
DHriga a cui scrivere la stringa
DLcolonna a cui scrivere la stringa
ES:BPindirizzo della stringa da scrivere
NoteLe 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) 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 quanto detto circa i device driver.

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[2]):

1)
Controllare il modo video tramite int 10h, serv. 0Fh.
2)
Se il registro AL non contiene 2, 3 o 7 è impossibile effettuare il pop­up: uscire dalla routine segnalando (ad esempio con un bip) la situazione all'utente.
3)
Altrimenti, se si prevede di modificare il contenuto di BH, salvarlo (esso è utilizzato nelle successive chiamate all'int 10h).
4)
Eseguire l'int 10h, serv. 03h per conoscere la posizione attuale del cursore e salvare il contenuto di DX.
5)
Portare il cursore alla posizione desiderata mediante il serv. 02h dell'int 10h (se si desidera salvare tutto il video andare in 0,0).
6)
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).
7)
Se si desidera nascondere il cursore è sufficiente portarlo a riga 26.
8)
Effettuare le operazioni di output connesse all'attività di foreground del TSR.
9)
Portare il cursore nell'angolo superiore sinistro dell'area di video salvata in precedenza.
10)
Ripristinarne il contenuto con un loop analogo a quello descritto al punto 6).
11)
Riportare il cursore alla posizione originaria.

File

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.). 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.

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[3].

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[4].

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

InputAH3Ch
CXattributo del file:

00h = normale
01h = sola lettura
02h = nascosto
04h = di sistema

DS:DXindirizzo (seg:off) del nome del file (stringa ASCIIZ)
OutputAXhandle per il file se CarryFlag = 0, altrimenti codice dell'errore
NoteSe il file specificato non esiste, viene creato; se esiste la sua lunghezza è troncata a 0 byte ed il contenuto distrutto.

INT 21H, SERV. 3DH: APRE UN FILE ESISTENTE

InputAH3Dh
ALmodalità di apertura: campi di bit

BIT 0-2:

0 = sola lettura
1 = sola scrittura
2 = lettura/scrittura

BIT 3:

riservato (sempre 0)

BIT 4-6:

0 = modo compatibilità
1 = esclusivo
2 = scrittura non permessa
3 = lettura non permessa
4 = permesse lettura e scrittura

BIT 7:

0 = utilizzabile da child process
1 = non utilizzabile da child

DS:DXindirizzo (seg:off) del nome del file (stringa ASCIIZ)
OutputAXhandle per il file se CarryFlag = 0, altrimenti codice dell'errore.
NoteSe 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().

INT 21H, SERV. 3EH: CHIUDE UN FILE APERTO

InputAH3Eh
BXhandle del file
OutputAXcodice di errore se CarryFlag = 1
NoteI buffer associati al file sono svuotati e la directory viene aggiornata.

INT 21H, SERV. 3FH: LEGGE DA UN FILE APERTO

InputAH3Fh
BXhandle del file
CXnumero di byte da leggere
DS:DXindirizzo (seg:off) del buffer di destinazione
OutputAXcodice di errore se CarryFlag = 1, altrimenti numero di byte letti.

INT 21H, SERV. 40H: SCRIVE IN UN FILE APERTO

InputAH40h
BXhandle del file
CXnumero di byte da scrivere
DS:DXindirizzo (seg:off) del buffer contenente i byte da scrivere
OutputAXcodice di errore se CarryFlag = 1, altrimenti numero di byte scritti.

INT 21H, SERV. 41H: CANCELLA UN FILE

InputAH41h
DS:DXindirizzo (seg:off) del nome del file (stringa ASCIIZ)
OutputAXcodice di errore se CarryFlag = 1.

INT 21H, SERV. 42H: MUOVE IL PUNTATORE ALLA POSIZIONE NEL FILE

InputAH42h
ALpunto di riferimento dell'offset:

0 = da inizio file
1 = da posizione corrente
2 = da fine file

BXhandle del file
CX:DXspostamento da effettuare in byte (long integer)
OutputAXcodice di errore se CarryFlag = 1, altrimenti DX:AX = nuova posizione nel file (long int).
NoteE' 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 0 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:

1)
Aprire il file mediante int 21h, serv. 3Dh: se il CarryFlag è 0 saltare al passo 3)
2)
Aprire il file mediante int 21h, serv. 3Ch: se il CarryFlag è 1 l'operazione è fallita: uscire.
3)
Muovere il puntatore al file di 0 byte rispetto alla fine del file mediante int 21h, serv. 42h (AL = 2; CX = 0; DX = 0;): se CarryFlag è 1 l'operazione è fallita: uscire.

Se l'operazione 3) riporta CarryFlag = 0 è 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 vedano i programmi SHFVWRIT e VIDEOCAP.

DTA

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

InputAH2Fh
OutputES: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

InputAH1Ah
DS:DXIndirizzo (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

11hFindFirst (Espansione wildcard) mediante FCB
12hFindNext (Espansione wildcard) mediante FCB
14hLettura sequenziale mediante FCB
15hScrittura sequenziale mediante FCB
21hLettura Random mediante FCB
22hScrittura Random mediante FCB
27hLettura Random a Blocchi mediante FCB
28hScrittura Random a Blocchi mediante FCB
4EhFindFirst (Espansione wildcard)
4FhFindNext (Espansione wildcard)

Ad eccezione degli ultimi due in elenco, si tratta di servizi dedicati alla gestione dei file mediante File Control Block[5]: dal momento che le funzionalità da essi offerte si ritrovano nei più recenti (e preferibili) servizi basati sulla tecnica degli handle[6], 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.


OK, andiamo avanti a leggere il libro...

Non ci ho capito niente! Ricominciamo...