|
Appunti informatica |
|
Visite: 1741 | Gradito: | [ Grande appunti ] |
Leggi anche appunti:La command lineLa command line Si intende, per command line, la riga di testo digitata al prompt Interazione tra c e assemblerInterazione tra C e Assembler Il C, pur rientrando tra i linguaggi di alto Vettori di interrupt o puntatori?Vettori di interrupt o puntatori? Degli interrupt e dei loro vettori si parla |
Sin qui la teoria: in effetti di C si è parlato poco, o per nulla. D'altra parte, il linguaggio utilizzato 'per eccellenza' nello scrivere i device driver, a causa della loro rigidità strutturale e della necessità di disporre di software compatto e molto efficiente , è l'assembler. Proviamo a riassumere i principali problemi che si presentano al povero programmatore C:
Ce n'è abbastanza per divertirsi e trascorrere qualche notte insonne. Tuttavia vale la pena di provarci: qualcosa di interessante si può certamente fare.
Il nostro primo device driver è molto semplice: esso non fa altro che emettere un messaggio durante il caricamento ed installare un buffer per la tastiera, lasciando residente solo quest'ultimo. E' bastato fare finta di scrivere un TSR, con l'accortezza di piazzare la funzione fittizia che riserva lo spazio per il device driver header prima di ogni altra funzione. Diamo un'occhiata al listato: i commenti sono inseriti all'interno dello stesso, onde evitare frequenti richiami, che renderebbero il testo meno leggibile.
KBDBUF.C - Barninga Z! - 01/05/94
Device driver che installa un buffer di tastiera dell'ampiezza voluta dal
programmatore (costante manifesta BUFDIM).
Compilato sotto Borland C++ 3.1:
bcc -c -mt -k- -rd kbdbuf.c
tlink -t -c kbdbuf.obj,kbdbuf.sys
#pragma inline
#pragma option -k- // Come gia' sperimentato nei TSR e' opportuno evitare
// la generazione della standard stack frame laddove
// non serve: vedere pag.
#include <dos.h> // MK_FP(), FP_SEG(), FP_OFF()
#define BIT_15 32768U // 1000000000000000b
#define BUFDIM 64 // Words (tasti) nel kbd buffer; modificare questo
// valore secondo l'ampiezza desiderata per il
// buffer di tastiera
#define BIOSDSEG 0x40 // segmento dati BIOS (tutti i dati BIOS sono
// memorizzati a partire dall'indirizzo 0040:0000
// Macro definite per referenziare con semplicita' i puntatori con cui viene
// gestito il buffer di tastiera. Per i dettagli vedere pag. e seguenti
#define kbdStartBiosPtr *(int far *)0x480 // macro per kbd buf start ptr
#define kbdEndBiosPtr *(int far *)0x482 // macro per kbd buf end ptr
#define kbdHeadBiosPtr *(int far *)0x41A // macro per kbd buf head ptr
#define kbdTailBiosPtr *(int far *)0x41C // macro per kbd buf tail ptr
// Le costanti manifeste che seguono riguardano i servizi e i codici di errore
#define INIT 0 // servizio 0: inizializzazione
#define SRV_OK 0 // servizio completato OK
#define E_NOTSUPP 3 // errore: serv. non implementato
#define E_GENERIC 12 // errore
// Macro per accesso al Request Header
#define ReqHdrPtr ((ReqHdr far *)*(long far *)reqHdrAddr)
// alcune typedef
typedef void DUMMY; // incrementa la leggibilita'
typedef unsigned char BYTE; // incrementa la leggibilita'
// Template di struttura per la gestione della PARTE VARIABILE del Request Header
// del servizio 0 (INIT). Vedere pag.
typedef struct InitHdr; // tipo InitHdr = struct
// Template di struttura per la gestione del Request Header del servizio 0 (INIT)
// La parte variabile e' uno dei suoi campi
typedef struct ReqHdr; // tipo ReqHdr = struct
// Prototipi delle funzioni. Le funzioni DUMMY sono quelle fittizie, definite per
// riservare spazio ai dati residenti (vedere pag.
DUMMY devDrvHdr(DUMMY);
DUMMY reqHdrAddr(DUMMY);
DUMMY kbdBuffer(DUMMY);
DUMMY helloStr(DUMMY);
DUMMY errorStr(DUMMY);
DUMMY okStr(DUMMY);
void far strategyRtn(void);
void _saveregs far interruptRtn(void);
int initDrvSrv(void);
void putstring(char far *string);
// La direttiva ORG 0 informa l'assemblatore che il programma verra' caricato in
// memoria senza PSP e senza Relocation Table
asm org 0; // e' un Device Driver
// devDrvHdr() e' la funzione fittizia che definisce il Device Driver Header
// Vedere pag. per i dettagli
DUMMY devDrvHdr(DUMMY) // Device Driver Header
// reqHdrAddr() riserva spazio per l'indirizzo far del request header passato dal
// DOS in ES:BX alla strategy routine (strategyRtn())
DUMMY reqHdrAddr(DUMMY) // spazio per dati
// kbdBuffer() riserva spazio per il buffer di tastiera. E' ampio BUFDIM words
// e ogni word corrisponde ad un tasto (1 byte per scan code e 1 per ascii code)
DUMMY kbdBuffer(DUMMY) // spazio per dati
// strategyRtn() e' la strategy routine del driver. Non deve fare altro che copiare
// il puntatore far contenuto in ES:BX nello spazio riservato dalla funzione
// fittizia reqHdrAddr(). strategyRtn() e' dichiarata far perche' il DOS la chiama
// con una CALL FAR, assumendo che il segmento sia lo stesso del device driver e
// l'offset sia quello contenuto nell'apposito campo del device driver header
void far strategyRtn(void) // Driver Strategy Routine
// interruptRtn() e' la interrupt routine del driver. Deve esaminare il Command Code
// (ReqHdrPtr->command) e decidere quale azione intraprendere: l'unico servizio
// implementato e' il servizio 0 (INIT). interruptRtn() e' dichiarata far _saveregs
// perche' il DOS la chiama con una CALL FAR (assumendo che il segmento sia lo
// stesso del device driver e l'offset sia quello contenuto nell'apposito campo del
// device driver header) e le tocca il compito di salvare tutti i registri. La
// dichiarazione far _saveregs e' equivalente alla dichiarazione interrupt, con la
// differenza (importantissima) che la funzione NON termina con una IRET. La
// _saveregs non e' necessaria se la funzione provvede esplicitamente a salvare
// tutti i registri (PUSH) in entrata e a ripristinarli (POP) in uscita
void _saveregs far interruptRtn(void) // Driver Interrupt Routine
else
break;
default: // qualsiasi altro servizio
// Comunica al DOS che il servizio non e' supportato ponendo a 1 il bit 15 della
// status word e indicando 03h quale codice di errore nel byte meno significativo
// della medesima.
ReqHdrPtr->status = E_NOTSUPP | BIT_15;
}
asm popf; // fa 'coppia' con la PUSH iniziale
// Fine della parte residente. Tutte le funzioni listate a partire da questo punto
// vengono sovrascritte dal DOS al termine della fase di inizializzazione del
// driver.
// initDrvSrv() e' la funzione dedicata al servizio 0. Essa effettua alcuni
// controlli per determinare se il nuovo buffer di tastiera puo' essere
// installato: in caso affermativo restituisce 0, diversamente restituisce
// un valore che reppresenta un 'errore non identificato' per la status word
// del request header, a cui esso e' assegnato. L'indirizzo di intDrvSrv() e'
// utilizzato per individuare la fine della parte residente.
int initDrvSrv(void) // INIT routine: non residente
else
if((temp = bOff+(2*BUFDIM)) < bOff)
return(E_GENERIC | BIT_15); // Overflow!
kbdStartBiosPtr = bOff;
kbdEndBiosPtr = temp;
kbdHeadBiosPtr = bOff;
kbdTailBiosPtr = bOff;
return(SRV_OK); // restituzione di valore OK
// putstring() stampa una stringa via int 21h, funzione 09h. Impossibile usare
// puts() perche', come descritto a pag. , l'assenza dello startup module
// rende inconsistenti le convenzioni sul contenuto dei registri di segmento sulle
// quali si basano le funzioni di libreria.
void putstring(char far *string) // visualizza una stringa: non
// Fine del codice transiente. Tutte le funzioni listate a partire da questo punto
// sono funzioni fittizie il cui scopo e' riservare spazio ai dati globali necessari
// alla porzione transiente del driver. E' stato necessario ricorrere alle funzioni
// fittizie invece delle normali variabili globali C per gli stessi motivi per i
// quali e' stata implementata putstring() in luogo di puts()
// La sequenza 0dh, 0ah, '$' che chiude ogni stringa rappresenta un CarriageReturn
// LineFeed seguito dal terminatore di stringa, che in assembler e' il '$', a
// differenza del C che utilizza lo zero binario (NULL)
DUMMY helloStr(DUMMY) // spazio stringa: non residente
DUMMY errorStr(DUMMY) // spazio stringa: non residente
DUMMY okStr(DUMMY) // spazio stringa: non residente
La complessità del listato non è eccessiva; qualche precisazione va però fatta circa la modalità di compilazione e linking. Dal momento che il device driver header deve occupare i primi 18 byte del file binario, non è possibile compilare nel modo consueto, con una sintassi del tipo:
bcc kbdbuf.c
in quanto il compilatore chiamerebbe il linker richiedendo di costruire l'eseguibile inserendovi in testa lo startup module. Occorre allora compilare e consolidare il file in due passi separati, escludendo lo startup module dal processo. Inoltre, la compilazione deve generare un file .COM: non è possibile creare un file .EXE perché avrebbe in testa la Relocation Table (vedere pag. ). La sintassi per la compilazione è allora:
bcc -c -mt kbdbuf.c
L'opzione ‑c arresta il processo alla creazione del modulo oggetto KBDBUF.OBJ, mentre l'opzione ‑mt richiede che la compilazione sia effettuata per il modello di memoria tiny (vedere pag. ), adatto alla generazione di eseguibili .COM. L'opzione ‑k‑, necessaria per evitare l'inserimento automatico del codice di gestione dello stack anche nelle funzioni in cui ciò non deve avvenire (pag. per i dettagli) non è specificata sulla riga di comando, in quanto automaticamente attivata dalla direttiva
#pragma option -k-
inserita nel sorgente.
Il linking deve essere effettuato come segue:
tlink -c -t kbdbuf.obj,kbdbuf.sys
ove l'opzione ‑c forza il linker a considerare i caratteri maiuscoli diversi da quelli minuscoli e l'opzione ‑t richiede la generazione di un eseguibile in formato .COM, il cui nome è specificato dall'ultimo parametro: KBDBUF.SYS. Il nostro device driver è pronto: è sufficiente, a questo punto, inserire in CONFIG.SYS una riga analoga alla
DEVICE=KBDBUF.SYS
indicando anche l'eventuale pathname del driver ed effettuare un bootstrap per vederlo all'opera (cioè per leggere il messaggio visualizzato durante il caricamento e per disporre di un buffer di tastiera più 'spazioso' del normale).
Per dovere di chiarezza è necessario spendere alcune parole sull'algoritmo tramite il quale initDrvSrv() verifica la possibilità di installare il nuovo buffer. Tutti i puntatori per la gestione della tastiera sono di tipo near ed esprimono offset relativi al segmento 0040h: è pertanto possibile installare la funzione fittizia kbdBuffer() quale nuovo buffer solamente se il suo indirizzo viene trasformato in un valore segmento:offset espresso come 0040h:offset e, al tempo stesso, offset+(2*BUFDIM) < FFFFh (se tale seconda condizione non fosse rispettata, il buffer inizierebbe ad un indirizzo lecito, ma terminerebbe al di là del limite massimo di 65535 byte indirizzabile a partire dal già citato segmento di default). Riprendiamo il listato della initDrvSrv() per commentare con maggiore facilità l'algoritmo implementato.
int initDrvSrv(void) // INIT routine: non residente
else
// Occorre ancora controllare che il nuovo buffer, oltre ad iniziare ad
// un indirizzo lecito, cioe' tra 0040h:0000h e 0040h:FFFFh, termini
// all'interno dello stesso intervallo.
if((temp = bOff+(2*BUFDIM)) < bOff)
return(E_GENERIC | BIT_15); // Overflow!
// Inizializzazione dei puntatori: da questo momento in poi il nuovo buffer
// di tastiera e' in funzione a tutti gli effetti.
kbdStartBiosPtr = bOff; // Inizio del buffer.
kbdEndBiosPtr = temp; // Fine del buffer.
kbdHeadBiosPtr = bOff; // Uguale valore per testa e coda:
kbdTailBiosPtr = bOff; // il buffer, all'inizio, e' vuoto.
// La initDrvSrv() segnala che tutto e' ok.
return(SRV_OK);
Il traguardo, seppure faticosamente, è raggiunto. Non si può fare a meno di osservare, però, che i problemi da aggirare appaiono esasperanti anche per il più paziente e tenace dei programmatori In particolare, l'impossibilità di utilizzare le funzioni di libreria, persino nella sola parte transiente del driver, è un limite davvero troppo pesante.
E' necessario pensare in grande
I maggiori ostacoli alla realizzazione di un device driver in C derivano dal fatto che in testa ad ogni programma C, dopo la compilazione, è consolidato lo startup module: questo provvede a caricare i registri di segmento (DS ES SS) con i valori necessari per una corretta gestione dello stack e del segmento dati (in accordo con le caratteristiche del modello di memoria ) effettua la scansione della command line (per generare argv e argc) ed inizializza alcune variabili globali utilizzate dalle funzioni di libreria (o da parte di esse); infine chiama la funzione main(), in uscita dalla quale richiama le funzioni che si occupano di terminare il programma in modo 'pulito' (chiudendo i file aperti, etc.). Dal momento che in testa ad ogni device driver deve trovarsi il device driver header, non è possibile utilizzare lo startup module, perdendo così le fondamentali funzionalità in esso implementate.
Il problema può essere aggirato scrivendo uno startup module (o qualcosa di simile), adatto ai device driver, da utilizzare in sostituzione di quello offerto dal compilatore. E' necessario, ahinoi, scriverlo in assembler, ma la consapevolezza che si tratta di un lavoro fatto una volta per tutte è di grande conforto
Già che ci siamo, possiamo progettare e mettere insieme anche alcune funzioni di evidente utilità (scritte in assembler, per maggiore efficienza) e raccoglierle in una libreria.
Infine ci serve un programmino in grado di modificare il device driver header del device driver compilato e consolidato: è così possibile dargli il nome logico desiderato e gli opportuni attributi senza necessità di modificare ogni volta il sorgente dello startup module e riassemblarlo.
Ancora un piccolo sforzo (o meglio tre), dunque, e disporremo di un efficace (speriamo!) toolkit per la realizzazione di device driver in linguaggio C. Vediamo come fare.
Realizzare uno startup module non è poi così difficile, quando si abbia l'accortezza di andare a sbirciare il sorgente di quello che accompagna il compilatore ; nel caso dei device driver è comunque assai comodo inserirvi anche altre funzionalità particolari, quali la strategy routine e un nucleo di base della interrupt: non si tratta, perciò, solamente di un vero e proprio codice di startup (cioè di avviamento).
Particolare importanza deve essere attribuita alla definizione dei segmenti : devono essere presenti tutti i segmenti definiti nello startup module originale ed è fondamentale che il segmento di codice sia definito per primo. Tutte le definizioni di segmento sono date nel file DDSEGCOS.ASI , riportato di seguito: si tratta di un file utilizzato in modo analogo ai file .H del C. Il listato è abondantemente commentato.
; DDSEGCOS.ASI - Barninga Z! - 1994
; ASSEMBLER INCLUDE FILE PER DEVICE DRIVER TOOLKIT
; dichiarazione dei segmenti di codice
; Le righe che seguono dichiarano i segmenti di codice del device driver e
; stabiliscono l'ordine in cui essi devono essere presenti nel file binario. Quasi
; tutte le definizioni sono riprese dallo startup module del TINY MODEL e riportate
; NEL MEDESIMO ORDINE. Alcune di esse non hanno utilita' nel caso dei device driver
; ma devono essere ugualmente riportate per evitare errori di compilazione o di
; runtime.
; Segmento del codice eseguibile
_TEXT segment byte public 'CODE' ; da startup code
_TEXT ends
; Segmento dati inizializzati
_DATA segment word public 'DATA' ; da startup code
_DATA ends
; Segmento dati costanti
_CONST SEGMENT WORD PUBLIC 'CONST' ; da startup code
_CONST ENDS
; Segmenti di riferimento
_CVTSEG SEGMENT WORD PUBLIC 'DATA' ; da startup code
_CVTSEG ENDS
_SCNSEG SEGMENT WORD PUBLIC 'DATA' ; da startup code
_SCNSEG ENDS
; Segmento dati non inizializzati
_BSS segment word public 'BSS'
_BSS ends
; Segmento che inizia al termine del segmento _BSS (e' una definizione dummy che
; consente di identificare con facilita' la fine del segmento _BSS.
_BSSEND SEGMENT BYTE PUBLIC 'BSSEND' ; da startup code
_BSSEND ENDS
; Segmenti definiti per la gestione delle operazioni di inizializzazione e
; terminazione del programma.
_INIT_ SEGMENT WORD PUBLIC 'INITDATA' ; da startup code
_INIT_ ENDS
_INITEND_ SEGMENT BYTE PUBLIC 'INITDATA' ; da startup code
_INITEND_ ENDS
_EXIT_ SEGMENT WORD PUBLIC 'EXITDATA' ; da startup code
_EXIT_ ENDS
_EXITEND_ SEGMENT BYTE PUBLIC 'EXITDATA' ; da startup code
_EXITEND_ ENDS
; Segmento definito appositamente per i device driver. Consente di conoscere con
; facilita' l'indirizzo di fine codice.
_DRVREND segment byte public 'DRVREND'
_DRVREND ends
; I segmenti sono tutti quanti inseriti nel gruppo DGROUP, secono lo schema del
; tiny model, che prevede un unico segmento di RAM (64 Kb) per tutti i segmenti
; del sorgente (i loro indirizzi si differenziano nell'offset). La direttiva ASSUME
; indica all'assemblatore che durante l'esecuzione tutti i registri di segmento
; verranno inizializzati con l'indirizzo di DGROUP; cio' permette all'assemblatore
; di calcolare correttamente gli offsets di tutti gli indirizzamenti nel listato.
DGROUP group _TEXT,
_DATA,
_CVTSEG,
_SCNSEG,
_BSS,
_BSSEND,
_INIT_,
_INITEND_,
_EXIT_,
_EXITEND_,
_DRVREND
assume cs : DGROUP,
ds : DGROUP,
ss : DGROUP,
es : DGROUP
; Seguono le definizioni di alcune costanti manifeste. Importante e' la STKSIZE,
; che definisce l'ampiezza iniziale dello stack locale del device driver. Circa
; la gestione dello stack vedere la funzione setStack() a pag. e
STKSIZE equ 512 ; dimensione in bytes dello stack locale
; 512 bytes e' il MINIMO possibile
CMDSIZE equ 128 ; max lunghezza cmd line
SYSINIT_KB equ 96 ; Kb riservati da Top of Mem per SYSINIT
; Seguono le definizioni delle costanti manifeste per la gestione della status
; word del request header (vedere pag.
; Codici di status da restituire al DOS in OR tra loro. Modificano i bits del
; byte piu' significativo della status word.
S_SUCCESS equ 0000h
S_ERROR equ 8000h ; usare in caso di errore
S_BUSY equ 0200h
S_DONE equ 0100h
; Codici di ritorno in OR coi precedenti. Indicano lo stato con cui si e' chiuso
; il servizio richiesto dal DOS (byte meno significativo della status word).
E_OK equ 0000h
E_WR_PROTECT equ 0
E_UNKNOWN_UNIT equ 1
E_NOT_READY equ 2
E_UNKNOWN_CMD equ 3 ; richiesto servizio non implementato
E_CRC equ 4
E_LENGTH equ 5
E_SEEK equ 6
E_UNKNOWN_MEDIA equ 7
E_SEC_NOTFOUND equ 8
E_OUT_OF_PAPER equ 9
E_WRITE equ 10
E_READ equ 11
E_GENERAL equ 12
; Costante manifesta per la gestione dello stack. Ha un fondamentale ruolo
; nell'implementazione del meccanismo che consente al device driver di
; modifcare la dimensione dello stack iniziale in fase di inizializzazione.
; Vedere setStack() a pag. e per i particolari.
; SE SI INTENDE MODIFICARE LA GESTIONE DELLO STACK TRA L'ISTRUZIONE CHE IN
; driverInit() COPIA SP IN __savSP1 E QUELLA CHE IN setStack() COPIA SP IN __savSP2,
; LE COSTANTI MANIFESTE CHE SEGUONO DEVONO ESSERE MODIFICATE DI CONSEGUENZA!!
NPA_SP_DIFF equ 8 ; differenza tra SP prima delle PUSH dei parms
; di main() e SP in ingresso a setStack() nel
; caso di main(void) e senza variabili auto.
Il testo del file DDSEGCOS.ASI è incluso in tutti i listati assembler laddove sia presente la riga
include ddsegcos.asi
in caratteri maiuscoli o minuscoli: a differenza del C, l'assembler non distingue, per default, maiuscole e minuscole. Vediamo ora il listato dello startup module per i device driver:
; DDHEADER.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - STARTUP
; device driver STARTUP MODULE. Da linkare in testa all'object prodotto dal
; compilatore e contenente la reale implementazione C del driver.
; DDHEADER.ASM implementa il device driver header, e le strategy e interrupt
; routines del device driver. Sono inoltre inserite incluse alcune routines di
; inizializzazione e le dichiarazioni delle variabili globali (alcune delle quali
; non pubblicate e quindi non visibili in C) per il supporto delle librerie
; C, in base allo startup module dei normali programmi. Si noti che i nomi C
; devono ignorare l'underscore (o il primo degli underscore) in testa ai nomi
; assembler.
include DDSEGCOS.ASI ; definizioni dei segmenti!!!
org 0 ; no PSP: e' un device driver
; Dichiarazione dei simboli esterni: sono dichiarate le variabili e le funzioni
; referenziate ma non definite in questo sorgente. Tutte le funzioni sono near
; dal momento che si lavora con il modello TINY.
extrn __stklen : word ; da libreria C: gestione stack.
; Vedere setStack(), pag. e
; seguono le dichiarazioni delle funzioni che gestiscono i vari servizi del device
; driver. La loro implementazione e' libera, ma il nome nel sorgente C deve essere
; identico a quello qui riportato (salvo l'underscore iniziale). Se il sorgente C
; non implementa una o piu' delle seguenti funzioni, all'eseguibile viene linkata
; la corrispondente dummy function inclusa nella libreria (pag. e seguenti).
; Fa eccezione _driverInit() (DDINIT.ASM), che implementa il servizio INIT standard
; e chiama la init() definita dal programmatore nel sorgente C.
extrn _driverInit : near
extrn _mediaCheck : near ; servizi del driver, da
extrn _buildBPB : near ; implementare in C. Le
extrn _inputIOCTL : near ; implementazioni assembler in
extrn _input : near ; libreria servono solo come
extrn _inputND : near ; placeholders per quelle non
extrn _inputStatus : near ; realizzate in C e non fanno che
extrn _inputFlush : near ; chiamare errorReturn(), definita
extrn _output : near ; in questo stesso sorgente
extrn _outputVerify : near
extrn _outputStatus : near
extrn _outputFlush : near
extrn _outputIOCTL : near
extrn _deviceOpen : near
extrn _deviceClose : near
extrn _mediaRemove : near
extrn _outputBusy : near
extrn _genericIOCTL : near
extrn _getLogicalDev : near
extrn _setLogicalDev : near
extrn _endOfServices : near
_TEXT segment ; inizio del segmento _TEXT (codice)
; Il codice (segmento _TEXT) - e quindi il file binario - inizia con il
; device driver header. Notare il primo campo (4 bytes) posto a -1 (FFFFFFFFh).
; La device attribute word (secondo campo) e il logical name (ultimo campo)
; sono 'per default' posti a 0 e, rispettivamente, 8 spazi. Dovranno essere
; settati dopo il linking, utilizzando la utility descritta a pag.
; E' fondamentale che il device driver header sia il primo oggetto definito
; nel segmento del codice.
public _DrvHdr ; header del device driver
_DrvHdr dd -1
dw 0 ; DA SETTARE CON DRVSET
dw offset _TEXT:_Strategy
dw offset _TEXT:_Interrupt
db 8 dup(32) ; DA SETTARE CON DRVSET
;-------- ----- ------ ----- ----- --------- ----- --------
; Tabella dei servizi del device driver. E' utilizzata dalla interrupt routine
; (listata poco piu' avanti) per individuare la funzione da chiamare a seconda
; del servizio richiesto dal DOS. Il posizionamento della tabella all'inizio del
; modulo forza l'assemblatore a referenziare per prime le funzioni dei servizi
; (solo Strategy() e Interrupt() sono referenziate prima di esse, nello header):
; in tal modo esse sono ricercate per prime nelle librerie (se non definite nel
; sorgente) e linkate all'eseguibile prima di tutte le funzioni di libreria C.
; Cio' permette l'utilizzo di _endOfServices(), come dichiarata al termine della
; tabella stessa.
_FuncTab dw offset _TEXT:_driverInit ; 0
dw offset _TEXT:_mediaCheck ; 1
dw offset _TEXT:_buildBPB ; 2
dw offset _TEXT:_inputIOCTL ; 3
dw offset _TEXT:_input ; 4
dw offset _TEXT:_inputND ; 5
dw offset _TEXT:_inputStatus ; 6
dw offset _TEXT:_inputFlush ; 7
dw offset _TEXT:_output ; 8
dw offset _TEXT:_outputVerify ; 9
dw offset _TEXT:_outputStatus ; 10 A
dw offset _TEXT:_outputFlush ; 11 B
dw offset _TEXT:_outputIOCTL ; 12 C
dw offset _TEXT:_deviceOpen ; 13 D
dw offset _TEXT:_deviceClose ; 14 E
dw offset _TEXT:_mediaRemove ; 15 F
dw offset _TEXT:_outputBusy ; 16 10
dw offset _TEXT:_unSupported ; 17 11
dw offset _TEXT:_unSupported ; 18 12
dw offset _TEXT:_genericIOCTL ; 19 13
dw offset _TEXT:_unSupported ; 20 14
dw offset _TEXT:_unSupported ; 21 15
dw offset _TEXT:_unSupported ; 22 16
dw offset _TEXT:_getLogicalDev ; 23 17
dw offset _TEXT:_setLogicalDev ; 24 18
; E' dichiarata una label (etichetta) il cui indirizzo (offset) indica la fine
; della tabella delle funzioni di servizio.
public __endOfSrvc
__endOfSrvc label word ; off DGROUP:fine f() servizi
; E' dichiarata una funzione dummy inserita in libreria dopo tutte le funzioni
; di servizio: il suo indirizzo indica, nel file binario, la fine del codice
; eseguibile delle funzioni di servizio. Vedere anche endOfServices() a pag.
dw offset _TEXT:_endOfServices ; dummy func per segnare
; indirizzo fine ultima
; f() di servizio.
;-------- ----- ------ ----- ----- --------- ----- --------
; L'espressione $-_FuncTab-2 calcola la distanza (in bytes) tra l'inizio della
; tabella dei servizi e l'indirizzo attuale, meno due bytes. In pratica si ottiene
; la dimensione della tabella dei puntatori alle funzioni di servizio e, dal momento
; che ogni puntatore occupa due bytes (tutti puntatori near), __FuncIDX__ puo'
; fungere da indice per individuare la funzione da chiamare a seconda del servizio
; richiesto dal DOS.
__FuncIDX__ dw $ - _FuncTab - 2 ; max indice (word off) in tabella
; il 2 e' per la f() dummy
; Seguono le definizioni di diverse variabili globali. Solo quelle per le quali e'
; specificata la clausola PUBLIC sono visibili da C. Sono definite anche alcune
; labels (etichette) che servono per gestire indirizzi senza utilizzare veri e
; propri puntatori. Tutte le variabili e labels pubblicate al C sono dichiarate
; nell'include file della libreria toolkit BZDD.H (vedere pag. e seguenti).
public _RHptr
_RHptr dd 0 ; puntatore far al Request Header
_DosStkPtr dd 0 ; ptr far a DOS Stack (SS:SP ingresso)
public _DrvStk
_DrvStk db STKSIZE dup(0) ; stack locale del driver
public _DrvStkEnd
_DrvStkEnd label word ; fine dello stack (e' solo una label)
; Il significato e l'uso di alcune delle variabili definite di seguito sono
; commentati a pagina
; Kb mem conv installati (usata nel codice di inizializzazione per l'int 12h). Si
; tratta di un dato raccolto per comodita' del programmatore.
public __systemMem
__systemMem dw 0
; Di seguito sono definite un'etichetta ed una variabile. La prima rappresenta un
; sinonimo della seconda e possono essere utilizzate da C come se fossero la stessa
; cosa. La variabile contiene l'indirizzo di segmento al quale e' caricato il
; driver (CS); il sinonimo _psp e' definito per analogia con la libreria C, ma
; il driver non ha un PSP. Questa e' la parte segmento dell'indirizzo al quale
; si trova il device driver header; percio' RHptr vale _psp:0000 o _baseseg:0000.
public __psp
__psp label word
public __baseseg
__baseseg dw 0 ; segmento di base del DevDrv (CS)
; Di seguito sono definite due variabili di comodita' per il programmatore. I device
; driver non hanno un far heap in cui allocare memoria far: _farMemBase e
; _farMemTop sono gli indirizzi far dell'inizio e, rispettivamente, della fine della
; memoria libera oltre il driver. Va tenuto presente che in quell'area di RAM ci
; sono parti di DOS attive: essa puo' percio' essere usata a discrezione, ma con
; prudenza.
public __farMemBase
__farMemBase dd 0 ; puntatore far alla memoria libera
; oltre il driver in init()
public __farMemTop
__farMemTop dd 0 ; puntatore far alla fine della memoria
; libera oltre il driver in init().
; _cmdLine e' una copia della command line del driver in CONFIG.SYS, a partire dal
; carattere che segue 'DEVICE='. In pratica e' un array di char.
public __cmdLine
__cmdLine db CMDSIZE dup(0) ; copia locale della command line
; _cmdArgsN e _cmdArgs equivalgono a argc e argv. Il massimo numero di puntatori che
; _cmdArgs puo' contenere e' 64, perche' una command line e' al massimo di 128
; caratteri e ogni argomento occupa almeno 2 bytes (1 carattere + 1 spazio).
public __cmdArgsN
__cmdArgsN dw 0 ; come argc
public __cmdArgs
__cmdArgs db CMDSIZE dup(0) ; array puntat. ad argom. cmd line
; Definizione di alcuni puntatori per comodita' del puntatore. Gli indirizzi (near)
; sono calcolati mediante labels definite in coda a questo stesso sorgente.
public __endOfCode
__endOfCode dw offset DGROUP:_ecode@ ; offset (CS:) della fine codice
public __endOfData
__endOfData dw offset DGROUP:_edata@ ; offset (CS:) della fine dati
public __endOfDrvr
__endOfDrvr dw offset DGROUP:_edrvr@ ; offset (CS:) fine spazio driver
; Variabili per la gestione dello stack. Lo stack locale DrvStk e' rilocabile
; (vedere setStack() a pag. e ): qualora esso, a discrezione del
; programmatore, venga effettivamente rilocato, lo spazio di STACKDIM bytes occupato
; inizialmente puo' essere riutilizzato come un normale array di char, il cui
; indirizzo (near ) e' _freArea. La sua effettiva dimensione e' _freAreaDim. Le altre
; variabili sono utilizzate da setStack() e sono pubblicate al C con finalita' di
; debugging
public __freArea
__freArea dw 0 ; offset ex-stack per riutilizzo
public __freAreaDim
__freAreaDim dw 0 ; dimensione ex-stack
public __savSP1
__savSP1 dw 0 ; SP prima di push parms per init()
public __savSP2
__savSP2 dw 0 ; SP all'ingresso di SetStack()
public __newTOS
__newTOS dw 0 ; offset del nuovo Top Of Stack
; Seguono definizioni di variabili date per analogia con lo startup code dei normali
; programmi C.
public __version
__version label word ; versione e revisione DOS
public __osversion
__osversion label word ; versione e revisione DOS
public __osmajor
__osmajor db 0 ; versione DOS
public __osminor
__osminor db 0 ; revisione DOS
public __StartTime
__StartTime dd 0 ; clock ticks allo startup
public _errno
_errno dw 0 ; codice di errore
public ___MMODEL
___MMODEL dw 0 ; tiny model
public _DGROUP@
_DGROUP@ dw 0 ; segmento del gruppo DGROUP
; Le variabili definite di seguito sono necessarie alle funzioni di libreria C per
; la gestione dello heap (malloc() , etc.). Quelle relative al far heap non sono
; inizializzate in quanto ai device driver non e' mai possibile effettuare
; allocazioni far mediante farmalloc(). Le allocazioni dello heap sono effettuate
; all'interno dello stack, secondo lo schema del modello TINY.
public ___heapbase
___heapbase dw offset _DrvStk ; inizio near heap
public ___brklvl
___brklvl dw offset _DrvStk ; attuale fine near heap
public __heapbase
__heapbase dd 0 ; inizio far heap
;
public __brklvl ;
__brklvl dd 0 ; inizio far heap
;
public __heaptop ;
__heaptop dd 0 ; fine far heap
;-------- ----- ------ ----- ----- --------- ----- --------
; Inizia qui il codice eseguibile. Siamo sempre nell'ambito del code segment (il che
; e' normale per le routine, un po' meno per le variabili appena definite. Si veda
; pero' quanto detto a proposito delle variabili nel code segment, a pag.
; Ecco la strategy routine . Essa non fa altro che salvare l'indirizzo del request
; header, passato dal DOS in ES:BX, nel puntatore far RHptr. Si noti che Strategy()
; deve essere una funzione far; inoltre essa non e' pubblicata al C, in quanto si
; tratta di una routine interna al driver, che non deve mai essere chiamata da C.
_Strategy proc far
mov word ptr cs:[_RHptr],bx ; salva ptr al Request Header
mov word ptr cs:[_RHptr+2],es ; prima offset poi segmento
ret
_Strategy endp
;-------- ----- ------ ----- ----- --------- ----- --------
; Ed ecco la interrupt routine : come si vede, ha una struttura semplice. Essa
; attiva lo stack locale e salva i registri, dopodiche' scandisce la tabella
; dei servizi: se il servizio e' 0 (inizializzazione) richiama driverInit(),
; definita oltre in questo sorgente, la quale a sua volta chiama la init() del
; sorgente C. Se il servizio non e' definito chiama errorReturn(), definita oltre
; in questo sorgente, passandole E_UNSUPPORTED quale codice di errore, altrimenti
; chiama la funzione dedicata al servizio. Se il sorgente C implementa una funzione
; con quel nome (vedere la tabella in testa a questo sorgente) e' invocata proprio
; quella funzione, altrimenti e' chiamata la corrispondente funzione dummy della
; libreria toolkit. Al termine delle operazioni, Interrupt() ripristina i registri
; e lo stack DOS e termina restituendo il controllo al sistema. Interrupt(), come
; Strategy(), deve essere far e non e' pubblicata al C, che non la puo' invocare.
_Interrupt proc far
; E' bene che il driver utilizzi un proprio stack, per evitare di
; sottarre risorse al DOS: quindi bisogna modificare SS:SP in modo
; che puntino allo stack del driver e non piu' a quello DOS. E' ovvio
; che l'indirizzo dello stack DOS (SS:SP) deve essere salvato (NON
; SULLO STACK STESSO!) per poterlo ripristinare in uscita. Allo
; scopo e' usata la variabile DosStkPtr.
mov word ptr cs:[_DosStkPtr],sp ; salva SS:SP (ptr a stack DOS)
mov word ptr cs:[_DosStkPtr+2],ss ; prima offset poi segmento
; Lo stack locale viene attivato caricando SS:SP con l'indirizzo
; della fine (lo stack e' usato dall'alto in basso!) dell'area
; allo scopo riservata. Percio' in SS e' caricato CS (nel modello
; TINY il segmento di stack e' lo stesso del codice) e in SP e'
; caricato l'offset della label DrvStkEnd, che indica la fine di
; DrvStk, l'array di STKSIZE bytes dedicato allo scopo.
mov _DGROUP@,cs ; stack pointers settati allo
mov ss,_DGROUP@ ; stack locale (_DGROUP@ e'
mov sp,offset _DrvStkEnd ; una variabile di comodo)
; A questo punto si puo' usare lo stack locale per salvare tutti i
; registri e generare una standard stack frame, cioe' il settaggio
; di SS, SP e BP secondo le convenzioni C in ingresso alle funzioni
; (vedere pag.
push bp ; genera standard stack frame
mov bp,sp
push ax ; e salva tutti i registri
push bx ; senza piu' intaccare lo
push cx ; stack dos
push dx
push si
push di
push ds
push es
pushf
; Tutti i registri di segmento sono uguagliati a CS (tiny model); SS
; e' gia' stato settato.
mov bx,cs
mov ds,bx
mov es,bx
; Qui viene esaminato il servizio richiesto ed e' lanciata la funzione
; corrispondente: il numero di servizio, letto ad offset 2 nel request
; header, e' moltiplicato per 2 (i puntatori alle funzioni sono near e
; percio' ciascuno occupa 2 bytes; quindi in tal modo si ottiene
; direttamente l'offset nella tabella FuncTab del puntatore alla
; funzione da lanciare). Se il risultato della moltiplicazione e'
; maggiore di _FuncIDX__ (massimo indice), e' chiamata la funzione
; unSupported() (definita oltre in questo sorgente), altrimenti si
; salta alla label EXECUTE.
push ds
lds si,DGROUP:_RHptr ; DS:SI punta al Request Header
mov al,[si+2] ; AL = servizio (campo a offset 2)
pop ds
shl al,1 ; per 2 (offsets in _FuncTab sono words)
xor ah,ah ; AX = offset in termini di words
cmp ax,__FuncIDX__ ; ** MAX VALORE DEL COMMAND BYTE x 2 **
jle EXECUTE
call _unSupported
jmp EXITDRIVER
EXECUTE:
; Il servizio richiesto e' lecito: la coppia DS:SI e' caricata con
; l'indirizzo del puntatore alla funzione opportuna e si procede
; alla chiamata con una tecnica analoga all'indirezione di puntatore
; a funzione.
mov si,offset _FuncTab
add si,ax ; DS:SI punta al puntat. a funz. in _FuncTab
call word ptr [si] ; chiama funzione void f(void)
EXITDRIVER:
; Alla fine delle operazioni Interrupt() setta a 1 il bit 8 della
; status word nel request header: si presume che tutte le funzioni
; dedicate ai servizi restituiscano (e percio' il compilatore lo
; carichera' in AX) il valore della status word stessa.
or ax,S_DONE ; segnala fine operazioni
push ds
lds si,DGROUP:_RHptr ; DS:SI punta al Request Header
mov [si+3],ax ; valorizza lo STATUS di ritorno
pop ds
; Uscita da Interrupt(): tutti i registri sono ripristinati, viene
; eliminata la standard stack frame ed e' ricaricato in SS:SP
; l'indirizzo dello stack DOS prelevandolo da DosStkPtr. La funzione
; termina con una normale RET (far, dato che Interrupt() e' dichiarata
; tale) e NON con una IRET
popf ; ripristina tutti i registri
pop es ; estraendone i valori di ingresso
pop ds ; dallo stack locale
pop di
pop si
pop dx
pop cx
pop bx
pop ax
pop bp ; rispristina BP (standard stack frame
mov sp,word ptr cs:[_DosStkPtr] ; ripristina SS:SP (ora puntano
mov ss,word ptr cs:[_DosStkPtr+2] ; nuovamente allo stack DOS)
ret ; non e' un interrupt!
_Interrupt endp
;-------- ----- ------ ----- ----- --------- ----- --------
; La funzione errorReturn() e' pubblicata al C e deve essere usata per segnalare
; che un servizio e' terminato in errore. Accetta come parametro un intero il cui
; byte meno significativo rappresenta il codice di errore e lo restituisce dopo
; avere settato a 1 il bit 15 . Vedere pag. (status word) e pag.
; (DDSEGCOS.ASI).
public _errorReturn ; f() per restituzione errore
_errorReturn proc near ; int errorReturn(int errcod);
; procedura per restituzione codice
push bp ; di errore secondo costanti manifeste
mov bp,sp ; Il parm e' referenziato [BP+4] infatti
mov ax,[bp+4] ; [BP+0] = BP e [BP+2] = IP (per la ret)
or ax,S_ERROR ; setta bit di errore
pop bp
ret
_errorReturn endp
;-------- ----- ------ ----- ----- --------- ----- --------
; La funzione unSupported(), pubblicata al C, e' dedicata a restituire lo stato di
; errore per servizio non supportato. Non fa altro che chiamare errorReturn() con
; l'appropriato codice di errore.
public _unSupported ; f() per servizio non supportato
_unSupported proc near ; int unSupported(void);
mov ax,E_UNKNOWN_CMD
push ax
call _errorReturn ; restituisce codice errore
add sp,2
ret
_unSupported endp
_TEXT ends ; Fine del segmento di codice
; Labels pubbliche per individuare gli offsets dei segmenti (non possono
; essere dichiarate in DDSEGCOS.ASI) perche' devono essere dichiarate una
; volta soltanto.
;-------- ----- ------ ----- ----- ----------------
_DATA segment word public 'DATA'
public _ecode@ ; fine codice (_TEXT)
_ecode@ label byte
_DATA ends
;-------- ----- ------ ----- ----- ----------------
_CVTSEG SEGMENT WORD PUBLIC 'DATA' ; da startup code
public __RealCvtVector
__RealCvtVector label word
_CVTSEG ENDS
;-------- ----- ------ ----- ----- ----------------
_SCNSEG SEGMENT WORD PUBLIC 'DATA' ; da startup code
public __ScanTodVector
__ScanTodVector label word
_SCNSEG ENDS
;-------- ----- ------ ----- ----- ----------------
_BSS segment word public 'BSS' ; da startup code
public _bdata@ ; inizio BSS
_bdata@ label byte
_BSS ends
;-------- ----- ------ ----- ----- ----------------
_BSSEND SEGMENT BYTE PUBLIC 'BSSEND' ; da startup code
public _edata@ ; fine BSS
_edata@ label byte
_BSSEND ENDS
;-------- ----- ------ ----- ----- ----------------
_DRVREND segment byte public 'DRVREND'
public _edrvr@ ; fine driver
_edrvr@ label byte
_DRVREND ends
end
Assemblando il sorgente con il comando
tasm -ml ddheader.asm
si ottiene il file DDHEADER.OBJ, che deve essere consolidato in testa al file .OBJ prodotto dal compilatore a partire dal sorgente C implementante il device driver, al fine di ottenere il file binario caricabile dal sistema operativo. L'opzione ‑ml impone all'assemblatore di distinguere le maiuscole dalle minuscole nei nomi di segmento, di variabile e di funzione, onde consentirne l'utilizzo in C secondo le consuete convenzioni del linguaggio.
Il primo ostacolo è alle nostre spalle: nello scrivere un device driver in C possiamo quasi dimenticarci dell'esistenza del nuovo startup module, così come scrivendo normali programmi ignoriamo del tutto quello originale.
Perché una libreria di funzioni? Per comodità, ma, soprattutto, per ragioni di efficienza. Dal momento che un device driver è largamente assimilabile ad un programma TSR ed è sottoposto ai medesimi vincoli per quel che riguarda l'occupazione di memoria, inserire in una libreria alcune funzioni utilizzate solo durante la fase di inizializzazione può consentire di confinarle nella porzione transiente del codice.
Vale la pena di presentare per prima la funzione driverInit(), utilizzata una sola volta durante l'inizializzazione del driver: va precisato che inserirla nella libreria permette di evitarne la permanenza in memoria dopo la fase di inizializzazione stessa, in quanto essa è inclusa dal linker nell'eseguibile solo dopo le funzioni definite nel sorgente C. Tuttavia, dal momento che essa è referenziata nello startup module prima delle funzioni di gestione dei servizi, è indispensabile che queste ultime siano tutte definite nel sorgente C (e non siano utilizzate le dummy function presenti in libreria) perché la _driverInit() possa essere considerata transiente senza pericolo di scartare routine residenti.
; DDINIT.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione driverInit() per l'inizializzazione standard del device driver
include ddsegcos.asi
; dichiarazione di init(). Equivale alla main() dei normali programmi C, assente
; nei device driver. E' richiamata dalla interrupt routine quando il DOS
; richiede il servizio 0, cioe' al caricamento del driver. Tutte le operazioni
; di inizializzazione devono percio' essere gestite nella init(), che deve essere
; presente in tutti i device driver scritti utilizzando il toolkit: vedere pag.
; per i dettagli. Ovviamente non deve esserci main().
extrn _init : near ; user defined! E' la 'main()' del
; device driver (pag.
; Dichiarazione della funzione di libreria toolkit setupcmd() (pag. ), che
; effettua la scansione della command line e genera argc e argv per la init().
extrn _setupcmd : near
; Altri simboli esterni dallo startup module
extrn __version : word
extrn __stklen : word
extrn __baseseg : word
extrn __systemMem : word
extrn __farMemTop : dword
extrn __farMemBase : dword
extrn __StartTime : dword
extrn __cmdArgs : word
extrn __cmdArgsN : word
extrn __savSP1 : word
extrn _DGROUP@ : word
extrn _edrvr@ : byte
extrn _bdata@ : byte
extrn _edata@ : byte
_TEXT segment
; driverInit() e' il vero e proprio startup code del driver. Dal momento che il
; suo indirizzo si trova ad offset 0 nella FuncTab (e' il primo puntatore nella
; tabella) essa e' chiamata da Interrupt() in corrispondenza del servizio 0 e
; procede alla inizializzazione del driver. driverInit() si occupa delle operazioni
; di inizializzazione standardizzate per tutti i driver: al termine di queste
; chiama init() , che deve essere definita dal programmatore nel sorgente C, la
; quale esegue le operazioni peculiari per quel driver. Il programmatore C deve
; gestire l'inizializzazione dedicata allo specifico driver esclusivamente con
; init(), di cui si parla a pagina
public _driverInit
_driverInit proc near ; driverInit(): simula parte dello startup code
mov ah,30h
int 21h ; richiede versione dos
mov __version,ax ; e la salva
mov __stklen, STKSIZE ; dimensione stack
mov word ptr __baseseg,cs ; segmento di caricamento
; L'algoritmo che segue non trova corrispondenza nello startup code
; dei normali programmi. Viene calcolato EMPIRICAMENTE il confine
; superiore della memoria libera oltre il driver: in caso di problemi
; può essere necessario aumentare il valore di SYSINIT_KB, costante
; manifesta definita in DDSEGCOS.ASI, che esprime il numero di Kb
; riservati alla routine SYSINIT del DOS (vedere pag. ). E' poi
; individuato l'offset della fine dello spazio occupato dal driver e,
; a partire da quello, l'indirizzo far dell'inizio della memoria
; libera oltre il driver. Lo spazio tra gli indirizzi far _farMemBase
; e _farMemTop e' a disposizione del driver per qualsiasi utilizzo,
; purche' limitato alla sola fase di inizializzazione, in quanto
; quella memoria sara' successivamente usata dal DOS (e non e'
; comunque gestibile via farmalloc(), etc.).
int 12h ; Kb RAM instal. (AX); <= 640
mov __systemMem,ax ; salva valore restituito
sub ax,SYSINIT_KB ; protegge spazio per SYSINIT
mov cx,6
shl ax,cl ; Kb * 64 = Seg esa
mov word ptr __farMemTop+2,ax ; top della memoria far libera
mov ax,offset DGROUP:_edrvr@ ; offset fine spazio driver
mov cx,4
shr ax,cl ; off / 16 = normalizzazione
inc ax ; annulla arrotondamento
add ax,word ptr _DGROUP@ ; segmento normalizzato
mov word ptr __farMemBase+2,ax ; base far free mem e' il primo
; seg:0000 oltre il driver
; Seguono nuovamente operazioni derivate dal normale startup code.
mov ah,0
int 1ah ; BIOS time ticks
mov word ptr __StartTime,dx ; per la funzione C clock()
mov word ptr __StartTime+2,cx
or al,al ; midnight flag settato?
jz NOT_MIDNIGHT
push es
mov ax,40h ; setta BIOS midnight flag
mov es,ax ; all'indirizzo 0040:0070
mov bx,70h
mov byte ptr es:[bx],1
pop es
NOT_MIDNIGHT:
mov di,offset DGROUP:_bdata@ ; da startup code: azzera area
mov cx,offset DGROUP:_edata@ ; BSS. (ES = CS = DGROUP)
sub cx,di ; CX = dist. _bdata@-_edata@
xor ax,ax
cld
rep stosb
; A questo punto e' chiamata la funzione di libreria toolkit
; setupcmd() (pag. ), che effettua la scansione della command line
; data in CONFIG.SYS e setta _cmdArgsN e _cmdArgs, equivalenti a argc
; e, rispettivamente, argv.
call _setupcmd ; scansione command line
mov bx,offset __cmdArgs
push bx ; analogo ad argv
push __cmdArgsN ; analogo ad argc
mov __savSP1,sp ; per setStack(): deve essere
; l'ultima prima di call _init
; Infine e' chiamata init(): il controllo dell'inizializzazione passa
; al sorgente C. Vedere pag.
call _init ; int init() USER DEFINED
add sp,4 ; pulizia stack
; Al termine dell'inizializzazione sono (per prudenza) azzerati i
; puntatori all'inizio e alla fine della memoria far disponibile oltre
; il driver, dal momento che, come sottolineato poco fa, dopo la fase
; di bootstrap essa non e' piu' disponibile.
mov word ptr __farMemBase,0 ; dopo init() non ha piu'
mov word ptr __farMemBase+2,0 ; alcun significato
mov word ptr __farMemTop,0 ; dopo init() non ha piu'
mov word ptr __farMemTop+2,0 ; alcun significato
ret ; torna a Interrupt()
_driverInit endp
_TEXT ends
end
La libreria comprende poi le funzioni per la gestione dei servizi non implementati dal driver, le quali non fanno altro che chiamare la unSupported() dello startup module (pag. ): vediamole ordinate per codice crescente di servizio.
; DDMEDCHE.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione mediaCheck() dummy usata solo se non implementata in C
include ddsegcos.asi
extrn _unSupported : near
_TEXT segment
public _mediaCheck
_mediaCheck proc near ; int mediaCheck(void);
call _unSupported
ret
_mediaCheck endp
_TEXT ends
end
; DDBUIBPB.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione buildBPB() dummy usata solo se non implementata in C
include ddsegcos.asi
extrn _unSupported : near
_TEXT segment
public _buildBPB
_buildBPB proc near ; int buildBPB(void);
call _unSupported
ret
_buildBPB endp
_TEXT ends
end
; DDINPIOC.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione inputIOCTL() dummy usata solo se non implementata in C
include ddsegcos.asi
extrn _unSupported : near
_TEXT segment
public _inputIOCTL
_inputIOCTL proc near ; int inputIOCTL(void);
call _unSupported
_inputIOCTL endp
_TEXT ends
end
; DDINPUT.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione input() dummy usata solo se non implementata in C
include ddsegcos.asi
extrn _unSupported : near
_TEXT segment
public _input
_input proc near ; int input(void);
call _unSupported
ret
_input endp
_TEXT ends
end
; DDINPND.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione inputND() dummy usata solo se non implementata in C
include ddsegcos.asi
extrn _unSupported : near
_TEXT segment
public _inputND
_inputND proc near ; int inputND(void);
call _unSupported
ret
_inputND endp
_TEXT ends
end
; DDINPSTA.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione inputStatus() dummy usata solo se non implementata in C
include ddsegcos.asi
extrn _unSupported : near
_TEXT segment
public _inputStatus
_inputStatus proc near ; int inputStatus(void);
call _unSupported
ret
_inputStatus endp
_TEXT ends
end
; DDINPFLU.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione inputFlush() dummy usata solo se non implementata in C
include ddsegcos.asi
extrn _unSupported : near
_TEXT segment
public _inputFlush
_inputFlush proc near ; int inputFlush(void);
call _unSupported
ret
_inputFlush endp
_TEXT ends
end
; DDOUTPUT.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione output() dummy usata solo se non implementata in C
include ddsegcos.asi
extrn _unSupported : near
_TEXT segment
public _output
_output proc near ; int output(void);
call _unSupported
ret
_output endp
_TEXT ends
end
; DDOUTVER.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione outputVerify() dummy usata solo se non implementata in C
include ddsegcos.asi
extrn _unSupported : near
_TEXT segment
public _outputVerify
_outputVerify proc near ; int outputVerify(void);
call _unSupported
ret
_outputVerify endp
_TEXT ends
end
; DDOUTSTA.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione outputStatus() dummy usata solo se non implementata in C
include ddsegcos.asi
extrn _unSupported : near
_TEXT segment
public _outputStatus
_outputStatus proc near ; int outputStatus(void);
call _unSupported
ret
_outputStatus endp
_TEXT ends
end
; DDOUTFLU.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione outputFlush() dummy usata solo se non implementata in C
include ddsegcos.asi
extrn _unSupported : near
_TEXT segment
public _outputFlush
_outputFlush proc near ; int outputFlush(void);
call _unSupported
ret
_outputFlush endp
_TEXT ends
end
; DDOUTIOC.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione outputIOCTL() dummy usata solo se non implementata in C
include ddsegcos.asi
extrn _unSupported : near
_TEXT segment
public _outputIOCTL
_outputIOCTL proc near ; int outputIOCTL(void);
call _unSupported
ret
_outputIOCTL endp
_TEXT ends
end
; DDDEVOPE.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione deviceOpen() dummy usata solo se non implementata in C
include ddsegcos.asi
extrn _unSupported : near
_TEXT segment
public _deviceOpen
_deviceOpen proc near ; int deviceOpen(void);
call _unSupported
ret
_deviceOpen endp
_TEXT ends
end
; DDDEVCLO.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione deviceClose() dummy usata solo se non implementata in C
include ddsegcos.asi
extrn _unSupported : near
_TEXT segment
public _deviceClose
_deviceClose proc near ; int deviceClose(void);
call _unSupported
ret
_deviceClose endp
_TEXT ends
end
; DDMEDREM.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione mediaRemove() dummy usata solo se non implementata in C
include ddsegcos.asi
extrn _unSupported : near
_TEXT segment
public _mediaRemove
_mediaRemove proc near ; int MediaRemove(void);
call _unSupported
ret
_mediaRemove endp
_TEXT ends
end
; DDOUTBUS.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione outputBusy() dummy usata solo se non implementata in C
include ddsegcos.asi
extrn _unSupported : near
_TEXT segment
public _outputBusy
_outputBusy proc near ; int outputBusy(void);
call _unSupported
ret
_outputBusy endp
_TEXT ends
end
; DDGENIOC.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione genericIOCTL() dummy usata solo se non implementata in C
include ddsegcos.asi
extrn _unSupported : near
_TEXT segment
public _genericIOCTL
_genericIOCTL proc near ; int genericIOCTL(void);
call _unSupported
ret
_genericIOCTL endp
_TEXT ends
end
; DDGETLOG.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione getLogicalDev() dummy usata solo se non implementata in C
include ddsegcos.asi
extrn _unSupported : near
_TEXT segment
public _getLogicalDev
_getLogicalDev proc near ; int getLogicalDev(void);
call _unSupported
ret
_getLogicalDev endp
_TEXT ends
end
; DDSETLOG.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione setLogicalDev() dummy usata solo se non implementata in C
include ddsegcos.asi
extrn _unSupported : near
_TEXT segment
public _setLogicalDev
_setLogicalDev proc near ; int setLogicalDev(void);
call _unSupported
ret
_setLogicalDev endp
_TEXT ends
end
Le funzioni sin qui presentate hanno il solo scopo di evitare al programmatore l'obbligo di definire comunque una funzione dedicata per i servizi non supportati: infatti, se nel sorgente C non esiste una funzione con lo specifico nome previsto per ogni servizio, il linker include nel file binario la corrispondente funzione di libreria. Ad esempio, se il driver supporta unicamente i servizi 4, 5, 6 e 9 (vedere pag. ), nel sorgente C devono essere definite, oltre alla init(), anche una input(), una inputND(), una inputStatus() e una output(), rispettivamente: i loro nomi non possono essere modificati. Per tutti gli altri servizi viene automaticamente importata nel file binario la corrispondente funzione di libreria, la quale segnala al DOS che il servizio non è supportato dal driver.
La funzione che segue è inserita in libreria dopo quelle di gestione dei servizi non supportati a soli fini di comodità: il suo indirizzo, infatti, rappresenta l'indirizzo al quale terminano in memoria le funzioni di servizio (quelle definite nel sorgente C precedono sempre, nel file binario, le funzioni di libreria). Essa non esegue alcuna azione e restituisce immediatamente il controllo alla funzione chiamante.
; DDENDOFS.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; funzione endOfServices() dummy usata solo per calcolare l'indirizzo al
; quale termina l'ultima delle f() dummy di servizio. non e' mai eseguita
include ddsegcos.asi
_TEXT segment
public _endOfServices
_endOfServices proc near
ret
_endOfServices endp
_TEXT ends
end
I cinque listati che seguono rendono disponibili funzionalità normalmente incluse nello startup code originale fornito con il compilatore. Si tratta di variabili e funzioni che i normali programmi utilizzano in modo automatico: a scopo di maggiore effiecienza esse sono invece inserite in libreria e il programmatore deve farne esplicito uso se necessario . Vale la pena di sottolineare che nei file DDRESVEC.ASM e DDSAVVEC.ASM sono definite SaveVectors() e _restorezero(), sulla quale ci si sofferma a pag. . Inoltre, DDDUMMY.ASM contiene il codice necessario a simulare alcune funzioni di uscita da programmi C (exit() abort(), etc.), private però di qualunque effetto, in quanto i device driver non terminano mai nel modo consueto ai normali programmi.
; DD_EXPTR.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; procedure dummy che i normali programmi usano per uscire a DOS o
; resettare vettori ed effettuare il flush degli streams: dati
include DDSEGCOS.ASI
extrn _dummy : near ; dummy() e' definita in DDDUMMY.ASM
_DATA segment
; puntatori a funzioni di cleanup per streams e file
;-------- ----- ------ ----- ----- --------- ----- -----
public __exitbuf
__exitbuf dw offset _TEXT:_dummy
public __exitfopen
__exitfopen dw offset _TEXT:_dummy
public __exitopen
__exitopen dw offset _TEXT:_dummy
_DATA ends
end
; DD_VECT.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
include DDSEGCOS.ASI
_TEXT segment
_Int0Vector dd 0 ; area locale memorizzazione
_Int4Vector dd 0 ; vettori per SaveVectors() e
_Int5Vector dd 0 ; _restorezero()
_Int6Vector dd 0 ;
_TEXT ends
end
; DDDUMMY.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; procedure dummy che i normali programmi usano per uscire a DOS o
; resettare vettori ed effettuare il flush degli streams
include DDSEGCOS.ASI
_TEXT segment
; labels per nomi funzioni di uscita e cleanup: puntano
; tutte a __dummy__proc, che segue solo una RET
;-------- ----- ------ ----- ----- --------- ----- -----
public dummy ; static dummy
dummy label
;-------- ----- ------ ----- ----- --------- ----- -----
public _abort ; abort()
_abort label
;-------- ----- ------ ----- ----- --------- ----- -----
public _atexit ; atexit()
_atexit label
;-------- ----- ------ ----- ----- --------- ----- -----
public _dummy ; static dummy
_dummy label
;-------- ----- ------ ----- ----- --------- ----- -----
public _exit ; exit()
_exit label
;-------- ----- ------ ----- ----- --------- ----- -----
public _keep ; keep()
_keep label
;-------- ----- ------ ----- ----- --------- ----- -----
public __cexit ; _cexit()
__cexit label
;-------- ----- ------ ----- ----- --------- ----- -----
public __checknull ; asm
__checknull label
;-------- ----- ------ ----- ----- --------- ----- -----
public __cleanup ; asm
__cleanup label
;-------- ----- ------ ----- ----- --------- ----- -----
public __c_exit ; _c_exit()
__c_exit label
;-------- ----- ------ ----- ----- --------- ----- -----
public __dos_keep ; _dos_keep()
__dos_keep label
;-------- ----- ------ ----- ----- --------- ----- -----
public __exit ; _exit()
__exit label
;-------- ----- ------ ----- ----- --------- ----- -----
public __terminate ; asm
__terminate label
;-------- ----- ------ ----- ----- --------- ----- -----
public ___EXIT ; pascal ___exit()
___EXIT label
;-------- ----- ------ ----- ----- --------- ----- -----
public ___exit ; pascal ___exit()
___exit label
__dummy__proc PROC near ; funzione dummy: esegue solo RET
ret
__dummy__proc ENDP
_TEXT ends
end
; DDRESVEC.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; procedure per la gestione dei vettori int 0, 4, 5, 6 e del gestore
; dell'int 0 (divide by zero). Nei normali programmi al ritorno a DOS
; e' chiamata _restorezero(), che ripristina detti vettori, salvati da
; SaveVectors() e modificati da alcune funzioni di libreria C. Il device
; driver deve invocare esplicitamente SaveVectors() e _restorezero()
; se necessario.
include DDSEGCOS.ASI
extrn _Int0Vector : dword ; definiti in DD_VECT.ASM
extrn _Int4Vector : dword
extrn _Int5Vector : dword
extrn _Int6Vector : dword
_TEXT segment
public __restorezero
__restorezero proc near ; void _restorezero(void)
push ds
mov ax,2500h
lds dx,_Int0Vector
int 21h
pop ds
push ds
mov ax,2504h
lds dx,_Int4Vector
int 21h
pop ds
push ds
mov ax,2505h
lds dx,_Int5Vector
int 21h
pop ds
push ds
mov ax,2506h
lds dx,_Int6Vector
int 21h
pop ds
ret
__restorezero endp
_TEXT ENDS
END
; DDSAVVEC.ASM - Barninga Z! - 1994
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
; procedure per la gestione dei vettori int 0, 4, 5, 6 e del gestore
; dell'int 0 (divide by zero). Nei normali programmi SaveVectors() e'
; chiamata dallo startup code e installa ZeroDivision. Al ritorno a DOS
; e' chiamata _restorezero(), che ripristina i vettori. La ErrorDisplay
; scrive su stderr un mesaggio di errore. Il device driver deve invocare
; esplicitamente SaveVectors() e _restorezero() se necessario.
include DDSEGCOS.ASI
extrn _Int0Vector : dword ; definiti in DD_VECT.ASM
extrn _Int4Vector : dword
extrn _Int5Vector : dword
extrn _Int6Vector : dword
_TEXT segment
ZDivMsg db 'Divide error', 13, 10 ; messaggio di errore int 0
ZDivMsgLen equ $ - ZDivMsg
ErrorDisplay proc near ; void pascal ErrorDisplay(void)
mov ah,040h
mov bx,2
int 021h
ret
ErrorDisplay endp
;-------- ----- ------ ----- ----- --------- ----- -----
ZeroDivision proc far ; void far pascal ZeroDivision(void)
mov cx,ZDivMsgLen
mov dx,offset DGROUP:ZDivMsg
push cs ; modello tiny: DS = CS
pop ds
call ErrorDisplay
mov ax, 3
push ax
ret
ZeroDivision endp
;-------- ----- ------ ----- ----- --------- ----- -----
public SaveVectors
SaveVectors proc near ; void pascal SaveVectors(void)
push ds
mov ax, 3500h
int 021h
mov word ptr _Int0Vector,bx
mov word ptr _Int0Vector+2,es
mov ax,3504h
int 021h
mov word ptr _Int4Vector,bx
mov word ptr _Int4Vector+2,es
mov ax,3505h
int 021h
mov word ptr _Int5Vector,bx
mov word ptr _Int5Vector+2,es
mov ax,3506h
int 021h
mov word ptr _Int6Vector,bx
mov word ptr _Int6Vector+2,es
mov ax,2500h
mov dx,cs
mov ds,dx
mov dx,offset ZeroDivision
int 21h
pop ds
ret
SaveVectors endp
_TEXT ENDS
END
Il listato seguente è relativo alla funzione setStack() , che ha un ruolo di estrema importanza nel toolkit: essa, infatti, consente di rilocare lo stack originale del driver durante l'inizializzazione. Il device driver, per sicurezza, non deve utilizzare lo stack del DOS per effettuare le proprie operazioni; allo scopo, nello startup module, è definita la variabile DrvStk, la quale è semplicemente un array (cioè una sequenza) di byte. La Interrupt(), in ingresso, salva l'indirizzo attualmente attivo nello stack DOS (SS:SP) e carica in SS:SP l'indirizzo del primo byte successivo a DrvStk, individuato dalla label DrvStkEnd (lo stack è sempre usato a ritroso, e viene 'riempito' dall'ultima word alla prima). La dimensione di default dello stack è pari a STKSIZE byte e potrebbe rivelarsi insufficiente; d'altra parte, incrementare il valore di STKSIZE non rappresenterebbe una valida soluzione per tutti i device driver realizzati con il toolkit, in quanto, oltre a non garantire con certezza assoluta un'adeguata capienza di stack in alcuni casi, potrebbe, in altri, determinare uno spreco di memoria pari a tutta la parte di stack non utilizzata.
La setStack() permette al programmatore di creare un nuovo stack , dimensionato in modo ottimale secondo le presumibili esigenze del singolo driver e di forzare la Interrupt() a servirsi di questo, in luogo di quello originale (che può essere riutilizzato a runtime per ogni necessità, come un qualsiasi array). E' sufficiente invocare setStack() passandole come parametri l'indirizzo near (di tipo (void *)) del nuovo stack e un unsigned int che ne esprime la dimensione in byte; essa restituisce un intero senza segno pari al numero di byte effettivamente disponibili nel nuovo stack . In caso di fallimento, setStack() restituisce
Una funzione jolly (pag. 169) è un mezzo semplice per implementare un nuovo stack: il nome della funzione può essere passato a setStack() come indirizzo near[14] (primo parametro).
#pragma option -k-
#include <bzdd.h> // include file per la libreria toolkit (pag. 169)
.
void newDrvStack(void)
.
int init(int argc,char **argv) // inizializzazione del driver
.
}
Nulla di particolarmente complesso, come si può facilmente constatare, quando si osservino scrupolosamente alcuni accorgimenti: in primo luogo, setStack() deve essere chiamata da init() (vedere pag. 169). Questa, inoltre, non deve dichiarare variabili automatiche (ma può dichiarare variabili static e register, purché, si abbia una ragionevole certezza che queste ultime siano effettivamente gestite nei registri della CPU e non allocate nello stack[15]). Possono essere utilizzate variabili globali, eventualmente redichiarate come extern. In pratica, la rilocazione dello stack, se necessaria, deve essere la prima operazione effettuata dal driver; i suddetti limiti, però, non rappresentano un reale problema: è sufficiente che init() deleghi ad un'altra funzione tutte le successive operazioni di inizializzazione per eliminare ogni rischio.
.
int install(int argc,char **argv)
.
int init(int argc,char **argv)
return(install(argc,argv));
}
E' molto importante ricordare che la rilocazione dello stack è permanente: ciò significa che setStack() deve essere invocata una sola volta e che tutte le operazioni effettuate dal driver dopo la chiamata a setStack(), sia in fase di inizializzazione che durante la normale operatività del computer, utilizzano il nuovo stack; d'altra parte, non è possibile riattivare lo stack originale: questo rimane disponibile come un comune array, il cui indirizzo near è dato dal puntatore _freArea e la cui dimensione in byte è pari a _freAreaDim (vedere BZDD.H, a pag. 169 e seguenti). Le variabili void *_freArea e unsigned _freAreaDim valgono NULL e, rispettivamente, 0 se lo stack non è stato rilocato.
Quanto stack serve al driver? Nell'implementazione qui descritta, la costante manifesta STKSIZE vale 512 byte: tale valore, per esigenze intrinseche alla libreria C, non può essere diminuito; tuttavia esso è appena sufficiente per aprire pochi file via stream (vedere pag. 120) e per allocare (con malloc(), etc.: pag. 113) poche decine di byte. La rilocazione dello stack è, pertanto, un'operazione quasi obbligatoria per molti device driver: 2 o 4 Kb sono, di norma, sufficienti per la maggior parte delle esigenze, ma non vi sono problemi ad utilizzare uno stack di dimensioni superiori, a parte il maggior 'consumo' di memoria.
; DDSETSTK.ASM - Barninga Z! - 1994
;
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
;
; unsigned setStack(unsigned base,unsigned len);
;
; genera un nuovo stack locale per il device driver
;
; unsigned base; offset del (puntatore near al) nuovo stack
; unsigned len; lunghezza dello stack in bytes
;
; restituisce: la lunghezza effettiva del nuovo stack; puo' essere
; inferiore a quella richiesta se quest'ultima e'
; dispari o se e' dispri l'offset dello stack (viene
; effettuato allineamento alla word); se il valore
; restituito e' 0 uno dei parametri e' errato e lo
; stack non e' stato generato
;
; note: se utilizzata, e' opportuno che sia la prima f()
; chiamata da init(), inoltre non e' disponibile una
; funzione per ritornare allo stack precedente ed
; eliminare quello generato da setStack(); se il
; nuovo stack viene effettivamente attivato, allora
; l'area statica occupata dallo stack originale del
; driver e' riutilizzabile secondo le necessita'
; dell'utente: il suo offset (puntatore near) e la
; sua lunghezza in bytes sono disponibili in _freArea
; e _freAreaDim rispettivamente.
;
INCLUDE DDSEGCOS.ASI
; dichiarazione simboli esterni
extrn __savSP1 : word
extrn __savSP2 : word
extrn __newTOS : word
extrn _DrvStk : byte
extrn _DrvStkEnd : byte
extrn ___heapbase : word
extrn ___brklvl : word
extrn __freArea : word
extrn __freAreaDim : word
extrn __stklen : word ; da libreria C
extrn __setupio : near ; e' una funzione di libreria C
; chiamata dallo startup code dei
; normali programmi: prepara le
; strutture statiche di tipo FILE
; per gli streams
;------------------------------------------------------------------------------
_TEXT segment
;------------------------------------------------------------------------------
public _setStack
_setStack proc near ; unsigned setStack(unsigned base,unsigned len);
mov __savSP2,sp ; salva SP in ingresso: deve essere la
; prima istruzione di setStack()
push bp ; genera std stack frame per accedere
mov bp,sp ; ai parametri passati da init()
mov dx,word ptr [bp+4] ; DX = offset di inizio del nuovo stack
mov ax,word ptr [bp+6] ; AX = lunghezza
pop bp ; elimina stack frame per operaz. copia
test dx,1 ; offset base del nuovo stack e' pari?
jz W_ALIGNED_BASE ; si; salta
inc dx ; allinea base stack e heap a word
dec ax ; no: lo spazio e' diminuito di 1 byte!
W_ALIGNED_BASE:
and ax,0FFFEh ; impone lunghezza stack pari
mov cx,offset _DrvStkEnd ; CX = attuale TopOfStack
sub cx,__savSP2 ; CX = TOS - SP = bytes da copiare
cmp ax,cx ; lungh. (AX) > bytes (CX) ?
jbe ERROR ; no: stack insufficiente; salta
mov bx,dx ; si: BX = offset base nuovo stack
add dx,ax ; DX = off base + lunghezza = nuovo TOS
jnc PARMS_OK ; CARRY = 0: TOS <= FFFFh: OK; salta
ERROR:
xor ax,ax ; segnala errore
jmp EXIT_FUNC
PARMS_OK:
mov __newTOS,dx ; salva nuovo TOS
mov ___heapbase,bx ; base heap = base nuovo stack
mov ___brklvl,bx ; attuale fine heap = base nuovo stack
mov __freArea,offset _DrvStk ; offset ex-stack per riutilizzo
mov __freAreaDim,STKSIZE ; dimensione vecchio stack
mov bx,__savSP1 ; SP prima di push parametri di init()
sub bx,__savSP2 ; differenza tra i due SP
cmp bx,NPA_SP_DIFF ; diff se init() non ha parms e var. auto
je BP_ADJUST ; se = basta settare BP e copiare stack
STACK_ADJUST: ; altrimenti prima di copiare:
mov word ptr [bp],dx ; BP = offset da SS della copia di BP
; PUSHed in init(), cioe' SS:[BP]
; = precedente BP (TOS), ora = __newTOS
mov bx,offset _DrvStkEnd ; BP e' offset rispetto a SS, ora BX =
sub bx,bp ; distanza tra quell'offset e fine stack
mov bp,dx ; BP viene trasformato per puntare allo
sub bp,bx ; stesso offset rispetto a __newTOS (AX)
jmp COPY_STACK ; BP gia' valorizzato
BP_ADJUST:
mov bp,dx ; BP = __newTOS: init(void) e no var auto
COPY_STACK:
push es ; salva ES (DS e' sempre = CS = SS), SI
push si ; e DI. Non copiati perche' estratti da
push di ; stack prima dell'attivazione nuovo stk
std ; copia dall'alto al basso
mov si,offset _DrvStkEnd ; fine vecchio stack
sub si,2 ; DS:SI -> prima word da copiare (BP DOS)
push ds
pop es ; ES = DS
mov di,dx ; DI = __newTOS
sub di,2 ; ES:DI -> ultima word del nuovo stack
mov bx,cx ; CX contiene ancora numero bytes stack
cli
rep movsb ; copia il contenuto dello stack
sti
pop di ; ripristina i registri salvati
pop si ; estraendoli ancora dal vecchio stack
pop es ; ES era la prima word non copiata
mov sp,dx ; SP = __newTOS
sub sp,bx ; __newTOS - bytes_in_stack = nuovo SP
mov __stklen,ax ; per libreria C
push ax ; usa gia' il nuovo stack
call __setupio ; ora ha senso: c'e' spazio (da startup)
pop ax ; restit. AX = LEN: nuovo stack attivato!
EXIT_FUNC:
ret
_setStack endp
;------------------------------------------------------------------------------
_TEXT ends
;------------------------------------------------------------------------------
end
Un altro tassello della libreria toolkit è rappresentato dalla funzione setupcmd(), che analizza la command line del driver e inizializza una variabile ed un array che possono essere utilizzati da init() in modo del tutto analogo a quello comunemente seguito nella main() dei normali programmi per argc e argv (vedere pag. 107).
La setupcmd() non accetta parametri e non restituisce alcunché; è progettata come procedura di servizio per lo startup module e da questo viene automaticamente invocata: essa accede alla command line presente in CONFIG.SYS tramite il puntatore passato dal DOS nel request header del servizio 0 (vedere pag. 169) e ne effettua una copia locale, sulla quale opera la scansione, sostituendo con un NULL lo spazio immediatamente successivo ad ogni parametro[16]. Al termine della scansione la copia della command line risulta trasformata in una sequenza di stringhe: i loro indirizzi sono memorizzati nell'array char **_cmdArgs ed il loro numero nella variabile int _cmdArgsN; lo startup module (driverInit(), vedere pag. 169), prima di invocare init(), copia nello stack l'indirizzo del primo e il valore contenuto nella seconda, predisponendo così i due parametri che la stessa init() può utilizzare, se dichiarati (vedere pag. 169).
Va ancora precisato che, qualora il device driver non abbia necessità di accedere alla command line, è possibile definire nel sorgente C una
void setupcmd(void)
per evitare che il linker importi nel file binario la versione della funzione presente in libreria, col vantaggio di ottenere un driver di dimensioni minori.
; DDSETCMD.ASM - Barninga Z! - 1994
;
; ASSEMBLER SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
;
;
; void setupcmd(void);
;
; funzione di parsing della command line
;
; genera una copia locale statica della command line e in questa tronca
; opportunamente le stringhe con NULLs, valorizzando un array statico di
; puntatori a char (_cmdArgs) e un intero (_cmdArgsN) che contiene il
; numero degli items presenti sulla cmd line (compreso il nome del driver)
;
include DDSEGCOS.ASI
; dichiarazioni dei simboli esterni
extrn _RHptr : dword
extrn __cmdLine : word
extrn __cmdArgs : word
extrn __cmdArgsN : word
;------------------------------------------------------------------------------
_TEXT segment
;------------------------------------------------------------------------------
public _setupcmd
_setupcmd proc near ; void setupcmd(void) prepara due parametri
; per init() analoghi a argc e argv
push si
push di
push ds
lds si,DGROUP:_RHptr ; DS:SI punta a Request Header
lds si,[si+18] ; DS:SI punta a Command Line
mov di,offset __cmdLine ; ES:DI punta a _cmdLine (ES = SS = CS)
mov cx,CMDSIZE
rep movsb ; crea copia locale della command line
pop ds
mov si,offset __cmdLine ; DS:SI -> _cmdLine (ES = DS = SS = CS)
inc si ; punta al secondo byte (emula LODSB)
mov di,offset __cmdArgs ; ES:DI -> _cmdArgs (ES = DS = SS = CS)
xor dx,dx ; contatore argomenti
cld ; operazioni stringa: forward
call _setarg ; funz. di servizio: vedere a fine listato
NEXTARG:
NEXTCHAR: ;;;; scansione di un parametro
lodsb
cmp al,32
je ENDARG ; blank
cmp al,9 ; > fine argomento
je ENDARG ; tab /
cmp al,13
je ENDCMD ; CR
cmp al,10 ; |
je ENDCMD ; LF > fine command line
cmp al,26 ; |
je ENDCMD ; EOF /
cmp al,34 ;
je DQUOTES ; DOUBLE QUOTES
jmp NEXTCHAR
ENDARG: ;;;; fine parametro
mov byte ptr [si-1],0 ; termina str. con NULL (SI gia' incr.)
ENDARG_1: ;;;; scansione spazio tra due parametri
lodsb
cmp al,32
je ENDARG_1 ; blank
cmp al,9 ; > separatori argomenti
je ENDARG_1 ; tab /
cmp al,13
je ENDCMD_1 ; CR
cmp al,10 ; |
je ENDCMD_1 ; LF > fine command line
cmp al,26 ; |
je ENDCMD_1 ; EOF /
cmp al,34 ;
je DQUOTES ; DOUBLE QUOTES: inizio parametro
call _setarg
jmp NEXTARG
DQUOTES: ;;;; virgolette nella command line
mov byte ptr [si-1],0
inc si ; le virgolette sono scartate:
call _setarg ; in _setarg AX=SI e DEC AX
dec si ; ripristina il puntatore
DQUOTES_1:
lodsb
cmp al,13
je ENDCMD ; CR
cmp al,10 ; |
je ENDCMD ; LF > fine command line
cmp al,26 ; |
je ENDCMD ; EOF /
cmp al,34 ;
je ENDARG ; DQUOTES: fine parametro
jmp DQUOTES_1
ENDCMD: ;;;; fine parametro e command line
mov byte ptr [si-1],0 ; termina str. con NULL (SI gia' incr.)
ENDCMD_1: ;;;; fine command line
xor ax,ax
stosw ; _cmdArgs[DX] = NULL
mov __cmdArgsN,dx ; _cmdArgsN = numero argomenti
pop di
pop si
ret
_setupcmd endp
;-----------------------------------------------------------------
_setarg proc near ;;;; inizio di un nuovo parametro
;;;; routine di servizio per setupcmd()
mov ax,si
dec ax ; SI e' gia' stato incrementato
stosw ; _cmdArgs[DX] -> argomento
inc dx ; trovato un altro argomento
ret
_setarg endp
;------------------------------------------------------------------------------
_TEXT ends
;------------------------------------------------------------------------------
end
Ancora un sorgente, questa volta in C. La funzione discardDriver() comunica al DOS che il device driver non deve rimanere residente in memoria. Le operazioni effettuate seguono le indicazioni di Microsoft per la gestione del servizio 0 (vedere pag. 169). Essa può essere invocata quando, fallite le operazioni di inizializzazione, si renda necessario evitare l'installazione in memoria del driver. Per un esempio di utilizzo, vedere pag. 169.
/*******************************************
DDDISCRD.C - Barninga Z! - 1994
C SOURCE FILE PER DEVICE DRIVER TOOLKIT - LIBRARY
discardDriver() - comunica al DOS di NON lasciare residente
il device driver, secondo la procedura
definita nelle specifiche Microsoft.
Sintassi:
void discardDriver(void);
Compilare con
bcc -mt -c dddiscrd.c
*******************************************/
#include 'bzdd.h'
void discardDriver(void)
La nostra libreria è (finalmente!) completa. Non resta che assemblare tutti i sorgenti e generare il file .LIB; la prima operazione è banale:
tasm -ml *.asm
L'opzione ‑ml richiede all'assemblatore di distinguere i caratteri maiuscoli da quelli minuscoli; l'unica precauzione da prendere consiste nell'evitare di riassemblare, se non necessario, il file DDHEADER.ASM, contenente lo startup module (dunque è meglio rinominarlo o spostarlo temporaneamente in un'altra directory).
Non va poi dimenticato il sorgente C di discardDriver(), per il quale bisogna effettuare la compilazione senza linking (opzione -c) per il modello di memoria tiny (‑mt):
bcc -c -mt dddiscrd.c
La libreria può essere costruita utilizzando la utility TLIB: visto il numero di file coinvolti nell'operazione, può risultare comodo predisporre un response file analogo a quello presentato di seguito.
+-ddinit &
+-ddmedche &
+-ddbuibpb &
+-ddinpioc &
+-ddinput &
+-ddinpnd &
+-ddinpsta &
+-ddinpflu &
+-ddoutput &
+-ddoutver &
+-ddoutsta &
+-ddoutflu &
+-ddoutioc &
+-dddevope &
+-dddevclo &
+-ddmedrem &
+-ddoutbus &
+-ddgenioc &
+-ddgetlog &
+-ddsetlog &
+-ddendofs &
+-dd_exptr &
+-dd_vect &
+-dddummy &
+-ddresvec &
+-ddsavvec &
+-ddsetstk &
+-ddsetcmd &
+-dddiscrd
Il response file (in questo esempio BZDD.LST) elenca tutti i file .OBJ da inserire in libreria[17]; la presenza di entrambi i simboli + e ‑ davanti ad ogni nome forza TLIB a sostituire nella libreria il corrispondente modulo .OBJ, se già esistente (il carattere '&' indica che l'elenco prosegue sulla riga successiva). Pertanto, il comando
tlib /C bzdd @bzdd.lst
produce il file BZDD.LIB, contenente tutte le funzioni sin qui presentate. L'opzione /C impone a TLIB di distinguere i caratteri maiuscoli da quelli minuscoli nei nomi dei simboli referenziati e definiti all'interno di ogni singolo object file (vedere anche pag. 159).
Per utilizzare produttivamente la libreria è ancora necessario creare un file .H (include file) contenente, al minimo, i prototipi delle funzioni, le dichiarazioni delle costanti e le definizioni di alcune macro. Il file BZDD.H è listato di seguito.
// BZDD.H - Barninga Z! - 1994
//
// include file per libreria device driver
//
#ifndef __BZDD_H // evita doppia inclusione
#define __BZDD_H
// necessario includere DOS.H se non ancora incluso
#ifndef __DOS_H
#include <dos.h>
#endif
// COSTANTI MANIFESTE
// codici di ritorno e di stato
#define S_SUCCESS 0x0000
#define S_ERROR 0x8000 // codici di status
#define S_BUSY 0x0200 // in OR tra di loro
#define S_DONE 0x0100
#define E_OK 0 // codici di ritorno in OR coi precedenti
#define E_WR_PROTECT 0
#define E_UNKNOWN_UNIT 1
#define E_NOT_READY 2
#define E_UNKNOWN_CMD 3
#define E_CRC 4
#define E_LENGTH 5
#define E_SEEK 6
#define E_UNKNOWN_MEDIA 7
#define E_SEC_NOTFOUND 8
#define E_OUT_OF_PAPER 9
#define E_WRITE 10
#define E_READ 11
#define E_GENERAL 12
#define E_RESERVED_1 13
#define E_RESERVED_2 14
#define E_INVALID_DSKCHG 15
// servizi del driver
#define C_INIT 0
#define C_MEDIACHECK 1
#define C_BUILDBPB 2
#define C_INPUTIOCTL 3
#define C_INPUT 4
#define C_INPUTND 5
#define C_INPUTSTATUS 6
#define C_INPUTFLUSH 7
#define C_OUTPUT 8
#define C_OUTPUTVERIFY 9
#define C_OUTPUTSTATUS 10
#define C_OUTPUTFLUSH 11
#define C_OUTPUTIOCTL 12
#define C_DEVICEOPEN 13
#define C_DEVICECLOSE 14
#define C_MEDIAREMOVE 15
#define C_OUTPUTBUSY 16
#define C_GENERICIOCTL 19
#define C_GETLOGICALDEV 23
#define C_SETLOGICALDEV 24
// altre
#define NO_VLABEL 'NO NAME' // per dischi senza volume label
#define RB_CHG 0xFF // dischetto sostituito
#define RB_MAYBE 0 // dischetto sostituito forse
#define RB_NOTCHG 1 // dischetto non sostituito
#define NOT_RESIDENT 0 // da passare a setResCodeEnd() se il
// driver non deve rimanere residente.
// setResCodeEnd() e' una macro definita
// in questo sorgente a pag. 169.
// FUNZIONI CORRISPONDENTI AI SERVIZI DEI DRIVER
// servizi del driver, da implementare in C. Le implementazioni assembler in
// libreria servono solo come placeholders per quelle non realizzate in C e
// non fanno che chiamare unSupported()
#ifdef __cplusplus // per poter usare il toolkit con il C++
extern 'C'
#endif
// typedefs per semplificare le dichiarazioni
typedef unsigned char BYTE;
typedef unsigned int WORD;
typedef unsigned long DWORD;
// strutture di vario tipo
typedef struct BPBLK;
// GESTIONE DEL DEVICE DRIVER HEADER
// la struct e la union seguenti possono essere utilizzate per la gestione del campo
// nome logico del device driver header (pag. 169): infatti la union consente di
// utilizzare gli 8 bytes a disposizione come una stringa di caratteri (e' il caso
// dei character device driver) oppure come una struttura blkName, definita come
// 1 byte (numero i unita' supportate) e un campo riservato di 7 bytes (e' il caso
// dei block device driver)
typedef struct blkName;
typedef union h_logName;
// La struct DevDrvHeader ricalca la struttura del device driver header, consentendo
// altresi' l'uso della union h_logName per la gestione del nome logico del device
typedef struct DevDrvHeader;
// GESTIONE DEL REQUEST HEADER
// E' definita una struct per la parte di request header differenziata per ogni
// specifico servizio ed una union che le comprende tutte. Vi e' poi una struct
// che rappresenta la parte fissa del reqest header piu' la union da utilizzare
// per il servizio. In altre parole, i templates di seguito definiti consentono
// di gestire il request header come una struttura (la stessa per tutti i servizi)
// che rappresenta la parte fissa, alla quale ne e' 'accodata' una seconda a
// scelta tra quelle definite appositamente per i vari servizi.
// parti variabili per i diversi servizi
typedef struct c_init;
typedef struct c_mediaCheck;
typedef struct c_buildBPB;
typedef struct c_inputIOCTL;
typedef struct c_input;
typedef struct c_inputND;
typedef struct c_inputStatus;
typedef struct c_inputFlush;
typedef struct c_output;
typedef struct c_outputVerify;
typedef struct c_outputStatus;
typedef struct c_outputFlush;
typedef struct c_outputIOCTL;
typedef struct c_deviceOpen;
typedef struct c_deviceClose;
typedef struct c_mediaRemove;
typedef struct c_outputBusy;
typedef struct c_genericIOCTL;
typedef struct c_getLogicalDev;
typedef struct c_setLogicalDev;
// union raggruppante le struct che descrivono la parte variabile di request
// header per i vari servizi
typedef union cParms;
// struct rappresentante il request header (5 campi per la parte fissa piu' la
// union per la parte variabile). La typedef consente di definire, per
// comodita', tipi di dato corrispondenti al request header stesso,
// all'indirizzo near e all'indirizzo far di un request header.
typedef struct RequestHeader, *RequestHeaderP, far *RequestHeaderFP;
// DICHIARAZIONE DEGLI ITEMS DEFINITI NEL MODULO DI STARTUP (DDHEADER.ASM, pag. 169)
extern RequestHeaderFP RHptr; // puntatore al request header
extern DevDrvHeader DrvHdr; // header del device driver
// MACRO di comodo
// La macro che segue puo' essere utilizzata per settare nella parte variabile del
// request header (per il servizio 0) l'indirizzo di fine codice residente del
// device driver. Per scaricare il driver dalla memoria evitandone l'installazione
// e' sufficiente passarle 0. Vedere la costante manifesta NOT_RESIDENT sopra
// definita e la funzione discardDriver() a pag. 169, il cui utilizzo sostituisce la
// chiamata setResCodeEnd(NOT_RESIDENT). La setResCodeEnd() fornisce, tra l'altro,
// un esempio di utilizzo delle strutture e della union definite per manipolare il
// request header.
#define setResCodeEnd(off) (RHptr->cp.initReq.endAddr = MK_FP(_CS,off));
#endif // __BZDD_H
E' sufficiente includere BZDD.H nel sorgente C del device driver per poter utilizzare tutte le funzioni di libreria, le costanti manifeste, le macro e i template di struttura.
Lo startup module e la libreria ci consentono, come vedremo a pag. 169, di scrivere un device driver interamente in linguaggio C; tuttavia ci occorre ancora uno strumento. Infatti, il device driver header è incorporato nello startup module (pag. 169): questo viene compilato una volta per tutte, mentre alcuni campi dello header, quali device attribute word e nome logico (vedere pag. 169 e dintorni) variano per ogni driver. Non ci sono scappatoie: o ci si adatta a riassemblare ogni volta DDHEADER.ASM, o si modificano i campi del device driver header direttamente nel file binario risultante da compilazione e linking[18]. Alla seconda ipotesi può facilmente fornire supporto una utility appositamente confezionata: il listato di DRVSET.C è presentato e commentato di seguito.
/************************************************************
DRVSET.C - Barninga Z! - 1994
Utility per modificare la device attribute word e il logical name nello header
dei device driver. Funziona con qualsiasi device driver (purché non .EXE)
anche se non realizzato con la libreria toolkit. Lanciare DRVSET con:
drvset [opzioni] nome_di_file
dove:
nome_di_file e' il nome del device driver da modificare
opzioni puo' essere:
-b o -d o -h e -n
Le opzioni -b, -d e -h sono alternative tra loro e devono essere seguite (senza
spazi frapposti) dalla nuova device attribute word in binario, decimale e,
rispettivamente, esadecimale.
L'opzione -n deve essere seguita (senza spazi frapposti) dal nome logico del
device driver, che viene troncato o completato con blanks, se necessario, e
convertito in maiuscole. Nel caso di block device driver, in luogo del nome
logico deve essere specificato il numero di unita' supportate, racchiuso tra
barre ('/').
I campi dello header sono aggiornati solo previa conferma da parte dell'utente.
Compilato con Borland C++ 3.1
bcc drvset.c parseopt.obj
Circa PARSEOPT.OBJ vedere pag. 169 e seguenti.
************************************************************/
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <ctype.h>
#include <parseopt.h> // per la gestione della command line (vedere pag. 169)
#define PRG 'DRVSET'
#define VER '1.0'
#define YEAR '1994'
#define SWCHAR '-'
#define BLANK ' '
#define UFLAG '/'
#define ILLMSG 'Illegal Option'
#define ATTR_MASK 0x17A0 // tutti i bits illeciti nella attrib word
#define BIT_15 0x8000 // character device driver
#define NAMELEN 8
#define MIN_UNITS 1L
#define MAX_UNITS 26L
#define MAX_LINE 128
typedef struct DEVHDR; // la typedef consente di usare DEVHDR per dichiarare variabili
// prototipi di gestione delle opzioni di command line (vedere pag. 169 e seguenti)
int valid_b(struct OPT *vld,int cnt);
int valid_d(struct OPT *vld,int cnt);
int valid_h(struct OPT *vld,int cnt);
int valid_n(struct OPT *vld,int cnt);
int err_handler(struct OPT *vld,int cnt);
int name_flag(struct OPT *vld,int cnt);
// prototipi delle altre funzioni
int main(int argc,char **argv);
void checkAttrBits(void);
int confirm(char *prompt,char yes,char no);
void displayHeader(DEVHDR *hdr,char *title);
int setDevDrvHdr(char *fname);
DEVHDR DevDrvHdr; // globale per semplicita'; e' la struttura per gestire lo header
const char *helpStr = '
filename is : the name of the device driver file to be updatedn
option(s) are:nn
One of the following:n
-bBinAttrn
-dDecAttrn
-hHexAttrn
where BinAttr, DecAttr and HexAttr are the Device Driver Attribute Wordn
in binary, decimal or hexadecimal notation.n
And/or one of the following:n
-nDevDrvName (for character devive driver)n
-n/LogUnits/ (for block device driver)n
Device Driver Logical Name (will be uppercased and truncated if necessary).n
If Block Device Driver, /LogUnits/ specifies the number of Supportedn
Logical Units (slashes must be typed).nn
*** No update done. ***n
';
const char *invAttrib = '%s: Invalid Device Driver Attribute Word.n';
const char *invName = '%s: Invalid Device Driver Logical Name.n';
const char *invUnits = '%s: Invalid Device Driver Supported Units.n';
const char *invFile = '%s: Too many filenames specified.n';
const unsigned char nonFNameChars[] = ;
unsigned optCnt; // contatore opzioni: massimo una tra -b, -h, -d
unsigned nameFlag; // nome file specificato?
// FUNZIONI DI CONTROLLO DELLE OPZIONI DELLA COMMAND LINE
#pragma warn -par
#pragma warn -rvl
// La funzione valid_b() effettua i controlli sul parametro specificato per
// l'opzione -b, onde verificare la correttezza dei bits impostati per la device
// attribute word. Il parametro specificato e' un numero BINARIO. Non vengono
// effettuati controlli sulla coerenza reciproca dei bits settati.
int valid_b(struct OPT *vld,int cnt) // convalida opzione 'b'
strrev(ptr);
for(i = 0; ptr[i]; i++)
switch(ptr[i])
checkAttrBits();
}
// La funzione valid_d() effettua i controlli sul parametro specificato per
// l'opzione -d, onde verificare la correttezza dei bits impostati per la device
// attribute word. Il parametro specificato e' un numero DECIMALE. Non vengono
// effettuati controlli sulla coerenza reciproca dei bits settati.
int valid_d(struct OPT *vld,int cnt) // convalida opzione 'd'
if((temp = atoi(vld->arg)) < 0)
DevDrvHdr.attrib = temp;
checkAttrBits();
}
// La funzione valid_h() effettua i controlli sul parametro specificato per
// l'opzione -h, onde verificare la correttezza dei bits impostati per la device
// attribute word. Il parametro specificato e' un numero ESADECIMALE. Non vengono
// effettuati controlli sulla coerenza reciproca dei bits settati.
int valid_h(struct OPT *vld,int cnt) // convalida opzione 'h'
{
extern unsigned optCnt;
extern DEVHDR DevDrvHdr;
extern const char *invAttrib;
register i;
if(optCnt++)
err_handler(NULL,NULL);
for(i = 0; vld->arg[i] == '0'; i++);
if(strlen(vld->arg+i) > 4)
sscanf(vld->arg+i,'%X',&DevDrvHdr.attrib);
checkAttrBits();
}
// La valid_n() controlla la validita' del nome logico specificato per il device e
// lo copia nel campo apposito del device driver header, trasformando tutti i
// caratteri in maiuscoli, troncandolo se piu' lungo di 8 caratteri e aggiungendo
// spazi a 'tappo' se piu' corto. Nel caso sia usata la sintassi /units/ per
// specificare il numero di unita' supportate (block device driver) controlla la
// correttezza sintattica e la validita' del parametro e lo copia nel primo byte
// del campo. Il controllo tra tipo di parametro e tipo di device e' effettuato
// dalla name_flag(), listata poco sotto.
int valid_n(struct OPT *vld,int cnt) // convalida opzione 'n'
if((units > MAX_UNITS) || (units < MIN_UNITS))
if(strcmp(line,'/'))
DevDrvHdr.name[0] = (unsigned char)units;
}
else
strncpy(DevDrvHdr.name,ptr,NAMELEN);
for(i = strlen(ptr); i < NAMELEN; i++)
DevDrvHdr.name[i] = BLANK; // lunghezza fissa 8 blank padded
}
}
// La err_handler() gestisce il caso di opzione errata
int err_handler(struct OPT *vld,int cnt) // gestione opz. errate
// la name_flag() e' chiamata dalla parseopt() una volta per ogni parametro
// non-option incontrato sulla command line, pertanto e' chiamata una sola volta
// se sulla cmd line e' specificato un solo nome di file. Tramite il contatore
// nameFlag verifica di non essere chiamata piu' di una volta. Inoltre essa
// controlla che vi sia coerenza tra il tipo di device driver indicato nella
// device attribute word (bit 15 settato) e il tipo di parametro per l'opzione
// -n (nome logico o numero di unita').
int name_flag(struct OPT *vld,int cnt) // gestione nome file
if((DevDrvHdr.name[0] < BLANK) && (DevDrvHdr.attrib & BIT_15))
}
#pragma warn .par
#pragma warn .rvl
// Struttura per la gestione delle opzioni (associa ogni opzione lecita alla
// funzione corrispondente).
struct VOPT valfuncs[] = ,
,
,
,
,
,
};
// stringa di definizione delle opzioni (se seguite dai due punti richiedono un
// parametro)
const char *optionS = '?b:d:h:n:';
// FUNZIONI: main(), poi tutte le altre in ordine alfabetico
int main(int argc,char **argv)
if(!optCnt)
if(!nameFlag)
return(setDevDrvHdr(opt[opt[0].opt].arg));
}
// Controlla che i bits settatti nella attribute word del device driver header
// non siano tra quelli riservati DOS (che devono essere zero)
void checkAttrBits(void)
{
if(DevDrvHdr.attrib & ATTR_MASK)
}
// Chiede conferma all'utente. Dal momento che attende lo standard input, la
// conferma puo' essere letta da un file (con la redirezione '<') contenente
// la lettera 'Y' o 'N' seguita da un CR LF. Cio' risulta utile nei casi in cui
// si voglia automatizzare l'operazione di agiornamento, ad esempio in un
// batch file di compilazione del device driver.
int confirm(char *prompt,char yes,char no)
while((ch != yes) && (ch != no));
fprintf(stderr,' %cnn',ch);
return((ch == yes) ? 1 : 0);
}
// Visualizza i campi dello header
void displayHeader(DEVHDR *hdr,char *title)
for( ; i < NAMELEN; i++)
fputc(hdr->name[i],stdout);
fprintf(stdout,''nn');
}
// Legge lo header attuale del driver e chiama displayHeader() una prima volta per
// visualizzarne i campi. Successivamente visualizza, sempre tramite displayHeader()
// i campi come saranno scritti nel driver in base ai parametri passati sulla
// command line e attende conferma via confirm().
int setDevDrvHdr(char *fname)
if(fread(&fileHdr,sizeof(DEVHDR),1,file) < 1)
displayHeader(&fileHdr,'Current Header Fields:');
fileHdr.attrib = DevDrvHdr.attrib;
strncpy(fileHdr.name,DevDrvHdr.name,NAMELEN);
displayHeader(&fileHdr,'New Header Fields:');
if(confirm('Confirm Update (%c/%c)?','Y','N'))
fprintf(stdout,'%s: %s updated successfully.n',PRG,strupr(fname));
}
else
fprintf(stdout,'%s: %s not updated.n',PRG,strupr(fname));
return(0);
}
Il programma non si preoccupa di controllare la coerenza reciproca dei bit della attribute word, perciò è compito del programmatore evitare di violare le regole che stabiliscono quali bit debbano, contemporaneamente, avere medesimo o diverso valore. Tuttavia, è verificato che i bit riservati al DOS siano lasciati a 0. E' effetuato un solo controllo di carattere logico: la consistenza tra utilizzo del campo riservato al nome logico nel device driver header e tipo del driver, come desumibile dalla attribute word impostata (vedere pag. 169 e dintorni per i particolari).
Compilando il sorgente, occorre richiedere che ad esso sia consolidato PARSEOPT.OBJ, necessario alla gestione delle opzioni della command line, come descritto a pag. 169 e seguenti. Il comando
bcc drvset.c parseopt.obj
consente di ottenere DRVSET.EXE che, invocato con l'opzione ‑? visualizza un testo di aiuto.
Vediamone un esempio di utilizzo:
drvset -h8000 -nZ! devprova.sys
Il comando presentato modifica il device driver header di DEVPROVA.SYS, impostando la device attribute word a 8000h (solo il bit 15 a 1, per indicare che si tratta di un character device driver) ed il nome logico del device 'Z!'. L'output prodotto da DRVSET è analogo al seguente:
Current Header Fields:
Next Device Address: FFFF:FFFF
Attribute Word: 0000
Strategy Routine Offset: 038A
Interrupt Routine Offset: 0395
Logical Name: ' '
New Header Fields:
Next Device Address: FFFF:FFFF
Attribute Word: 8000
Strategy Routine Offset: 038A
Interrupt Routine Offset: 0395
Logical Name: 'Z! '
Confirm Update (Y/N)?
Digitando Y o N, DRVSET tenta, o meno, di modificare lo header del file DEVPROVA.SYS, visualizzando poi un messaggio di conferma dell'avvenuta modifica o della rinuncia. Se nella directory corrente è presente un file, ad esempio YES.TXT, costituito di una sola riga di testo contenente il solo carattere Y (in pratica il file si compone di 3 byte: Y, CR e LF), e si redirige lo standard input (vedere pag. 120 e seguenti) di DRVSET a quel file, la risposta affermativa alla domanda diviene automatica: il comando
drvset -h8000 -nZ! devprova.sys < yes.txt
si rivela particolarmente adatto ad essere inserito in un file batch di compilazione e linking del driver DEVPROVA.SYS (vedere, ad esempio, pag. 169).
Abbiamo a disposizione un nuovo startup module, una libreria di funzioni dedicate ai device driver e un programma in grado di modificare secondo le nostre esigenze la device attribute word e il logical name nel device driver header del file binario risultante dal linking. Per avere un device driver manca soltanto il sorgente C, che deve implementare tutte le funzionalità desiderate per il driver, senza mai perdere d'occhio i necessari requisiti di efficienza. Vediamo un elenco delle principali regole a cui attenersi nello scrivere il driver.
1) |
Nel sorgente C deve essere definita la funzione init(), le cui caratteristiche sono discusse a pagina 169. |
2) |
Nel sorgente C devono inoltre essere definite tutte le funzioni che implementano i servizi desiderati. Dette funzioni devono necessariamente uniformarsi ai prototipi dichiarati in BZDD.H (pag. 169 e seguenti). Ad esempio, il servizio 19 (generic IOCTL request), deve sempre essere implementato da una funzione, definita nel sorgente C, avente prototipo int genericIOCTL(void): tutte queste funzioni devono restituire un intero e non possono richiedere parametri. |
3) |
L'intero restituito dalle funzioni di servizio rappresenta lo stato dell'operazione eseguita ed è utilizzato dalla Interrupt() per valorizzare la status word (pag. 169) nel device driver request header. Allo scopo possono essere utilizzate le costanti manifeste definite in BZDD.H. |
4) |
L'inizializzazione del driver deve includere una chiamata alla macro setResCodeEnd() o alla funzione discardDriver(). Vedere pag. 169. |
5) |
Possono essere chiamate liberamente le funzioni di libreria C, tenendo presente che il loro utilizzo nella parte residente del driver comporta i problemi tipici dei programmi TSR discussi a pag. 169. Si noti che la parte residente si compone almeno di tutte le funzioni di servizio e di quelle da esse invocate direttamente o indirettamente, mentre non sono necessariamente residenti la init() e le funzioni chiamate esclusivamente all'interno di questa. |
6) |
Va tenuto presente che vi sono, comunque, limiti all'uso delle funzioni di libreria C: alcune di esse non possono essere referenziate in quanto incoerenti con la logica di implementazione dei device driver. Ad esempio, non è possibile effettuare allocazioni di memoria far (farmalloc(), etc.): tali operazioni falliscono sistematicamente (farmalloc() restituisce sempre 0L) in quanto i device driver non hanno far heap. Inoltre i device driver sono installati residenti da parte del DOS, perciò non deve essere usata la funzione keep(). Ancora, dal momento che i device driver non terminano mai la propria esecuzione, non è possibile utilizzare exit(), _exit(), abort(), etc.. Inoltre, vista la mancanza di environment, i device driver non possono usare getenv() e putenv(). |
7) |
Alcune funzioni di libreria non possono essere utilizzate nella fase di inizializzazione, mentre possono esserlo nell'espletamento di tutti gli altri servizi. Ad esempio, l'allocazione di memoria via DOS (int 21h, servizio 48h) è possibile solo a caricamento del sistema completato, quindi solamente dopo l'installazione di tutti i device driver e dell'interprete dei comandi: pertanto allocmem() fallisce se chiamata da init() o da sue subroutine, mentre può avere successo se chiamata dalle funzioni di servizio durante la sessione di lavoro del computer. |
8) |
Le variabili globali possono essere dichiarate e referenziate come in qualsiasi programma C; si tenga però presente che esse risiedono in memoria oltre il codice dell'ultima funzione estratta dalle librerie: ciò può porre vincoli qualora si intenda ridurre al minimo l'ingombro in memoria della porzione residente del driver. L'ostacolo può essere facilmente aggirato col solito trucco delle funzioni jolly, analogamente ai TSR (vedere pag. 169). |
9) |
La compilazione del sorgente C deve sempre essere effettuata con le opzioni ‑mt (modello di memoria tiny; vedere pag. 151 e seguenti) e ‑c (generazione del file .OBJ senza linking). Il linker deve essere lanciato successivamente, con le opzioni ‑t (generazione di un file .COM) e ‑c (case sensitivity), elencando DDHEADER.OBJ (lo startup module) in testa a tutti i file .OBJ; l'ordine in cui elencare le librerie (BZDD.LIB; la libreria C per il modello di memoria small CS.LIB; le altre librerie eventualmente necessarie) non è fondamentale. Si ricordi, però, che BZDD.LIB e CS.LIB devono essere sempre indicate, mentre altre librerie devono esserlo solo se in esse si trovano funzioni o simboli comunque referenziati. Infine, il device driver header deve essere modificato con DRVSET, secondo necessità. Ad esempio, il character device driver DEVPROVA.SYS (nome logico ZDEV) può essere ottenuto a partire da DEVPROVA.C attraverso i seguenti 3 passi: bcc -c -mt devprova.c tlink -c -t ddheader.obj devprova.obj,devprova.sys,,bzdd.lib cs.lib altre.lib drvset -h8000 -nzdev devprova.sys L'operazione di linking produce anche DEVPROVA.MAP (file ASCII contenente l'elenco dei simboli pubblici definiti nel driver con i rispettivi indirizzi), che può essere tranquillamente gettato alle ortiche[19]. |
Le complicazioni sono, per la maggior parte, più apparenti che reali. La descrizione della init() e qualche esempio lo possono dimostrare.
Come più volte si è detto, nel sorgente C di ogni device driver realizzato con il toolkit deve essere definita una funzione avente nome init(), analogamente a quanto avviene nei comuni programmi C, nei quali deve essere definita una main(). In effetti, tra init() e main() vi sono analogie, in quanto entrambe sono automaticamente chiamate dallo startup module e possono accedere alla command line attraverso i parametri formali; tuttavia le due funzioni sono differenti, in quanto main() può accedere anche alle variabili d'ambiente (vedere pag. 107), mentre init() non ha tale possibilità, dal momento che i device driver non hanno environment. Inoltre, una istruzione return eseguita in main() determina sempre la fine dell'esecuzione del programma, mentre in init() causa la restituzione del controllo al DOS da parte del driver, che può rimanere, però, residente in memoria (a seconda dell'indirizzo di fine codice residente impostato nel request header). Ancora, main() può restituire o meno un valore (in altre parole, può essere dichiarata int o void), mentre init() è obbligatoriamente int: il valore restituito è utilizzato dalla Interrupt() per impostare la status word (pag. 169) del request header[20].
In particolare, la init() può essere definita secondo 3 differenti prototipi:
int init(void);
int init(int argc);
int init(int argc,char **argv);
Nella prima forma, init() non riceve parametri; nella seconda essa rende disponibile un intero, che esprime il numero di argomenti presenti sulla riga di comando del device driver, incluso il nome del driver stesso. La terza forma, oltre all'intero di cui si è detto, rende disponibile un puntatore a puntatore a carattere, cioè un array di puntatori a carattere o, in parole povere, un array di stringhe. Ogni stringa è un argomento della command line: la prima (indice 0) è il nome del driver (completo di eventuale path); il puntatore all'ultimo argomento è seguito da un puntatore nullo (NULL). La stretta parentela con argc e argv della main() dei comuni programmi C è evidente e da essa, del resto, sono derivati i nomi utilizzati[21]; dal punto di vista tecnico essi sono le copie di _cmdArgsN e _cmdArgs effettuate nello stack da driverInit() prima di effettuare la chiamata alla stessa init() (vedere pag. 169).
La init() è eseguita una sola volta, durante il caricamento del driver da parte del sistema, pertanto deve effettuare, eventualmente tramite funzioni richiamate direttamente o indirettamente, tutte le operazioni necessarie all'inizializzazione del driver. Qualora il device driver abbia la necessità di rilocare il proprio stack iniziale, è proprio init() che deve provvedervi, invocando setStack() con le precauzioni descritte a pagina 169.
Inoltre init() ha l'importante compito di comunicare al DOS se installare o no il driver in memoria e, in caso affermativo, di indicare l'indirizzo del primo byte successivo all'area di RAM destinata al driver stesso. Allo scopo è definita in BZDD.H la macro setResCodeEnd(), ed esiste in libreria la funzione discardDriver(): la prima accetta detto indirizzo quale parametro: va ricordato che si tratta di un indirizzo near, cioè, in altre parole, della parte offset dell'indirizzo far, la cui parte segmento è rappresentata dal valore del registro CS. Esempio:
.
#include <bzdd.h>
int init(int argc,char **argv)
.
setResCodeEnd(_endOfDrvr); // lascia residente tutto il codice del driver
return(E_OK); // restituisce OK per la status word
}
L'indirizzo _endOfDrvr, passato a setResCodeEnd(), è una variabile, definita nello startup module[22], esprimente l'offset di una porzione di driver fittizia, collocata dal linker in coda al file binario e può validamente rappresentare, di conseguenza, un indirizzo di sicurezza. A setResCodeEnd() la init() può passare, ad esempio, il proprio indirizzo quando il sorgente sia organizzato in modo tale che init() sia definita per prima tra tutte le funzioni transienti e nessuna di queste referenzi funzioni di libreria (toolkit o C):
.
setResCodeEnd(init);
.
E' ovvio che init() può valorizzare con l'opportuno indirizzo l'apposito campo del request header accedendo direttamente ad esso, senza utilizzare setResCodeEnd()[23]; inoltre, non necessariamente tale operazione deve essere svolta immediatamente prima di eseguire un'istruzione return, anche se, spesso, ciò è causato dalla logica stessa dell'algoritmo di inizializzazione.
La init() invoca, al contrario, discardDriver() (vedere pag. 169) se la procedura di inizializzazione deve concludersi senza rendere residente il device driver: sebbene nel caso dei character device driver si riveli sufficiente chiamare la macro setResCodeEnd() con la costante manifesta NOT_RESIDENT, definita in BZDD.H, o il valore 0 quale parametro, si raccomanda di utilizzare comunque discardDriver(), come nell'esempio poco sopra presentato, dal momento che questa è aderente alle indicazioni in materia presenti nella documentazione ufficiale del DOS.
Nello startup module sono definite due funzioni, utili per la restituzione di codici di errore alla Interrupt() del device driver. Esse sono:
int errorReturn(int errcode);
che valorizza il byte meno significativo della status word (pag. 169) del request header con errcode e pone a 1 il bit di errore del byte più significativo, e
int unSupported(void);
che chiama errorReturn() passandole quale parametro il codice di errore corrispondente allo stato di servizio non definito. Entrambe le funzioni, come si è detto, sono definite nello startup module: pertanto non provocano l'inclusione nel file binario di moduli .OBJ dalle librerie.
In libreria è presente la
void discardDriver(void);
che ha lo scopo di richiedere al DOS di non installare il device driver in memoria. Il suo utilizzo è descritto a pag. 169, con riferimento alla funzione user‑defined init().
Per installare il driver occorre invece chiamare la
setResCodeEnd(off);
macro definita in BZDD.H (pag. 169): off rappresenta l'offset, rispetto a CS, del primo byte di memoria libera oltre la parte residente del driver e deve essere un valore di tipo unsigned int.
La gestione del request header (pag. 169) è di fondamentale importanza, in quanto esso è il mezzo attraverso il quale DOS e device driver si scambiano tutte le informazioni necessarie all'espletamento dei diversi servizi. Quasi tutte le funzioni di servizio devono quindi accedere al request header per conoscere i parametri forniti dal DOS e, spesso, memorizzarvi i risultati delle loro elaborazioni.
L'indirizzo del request header è comunicato dal DOS alla Strategy(), la quale lo memorizza in una variabile dichiarata nello startup module, per uso successivo da parte della Interrupt() e delle funzioni di servizio. Allo scopo, nel file BZDD.H sono definiti template di struct e union, che consentono l'accesso ai campi delle parti fissa e variabile del request header tramite un puntatore dichiarato globalmente.
In particolare, per ogni servizio è definito un template di struttura che rappresenta tutti i campi della parte variabile del request header secondo le specifiche del servizio medesimo. Detti template sono raggruppati in una union, che rappresenta così la parte variabile di tutti i servizi. Il request header è infine rappresentato da un template di struttura, i cui elementi includono i campi della parte fissa e, da ultimo, la union definita come descritto. Le typedef associate ai template rendono più leggibili e concise eventuali dichiarazioni.
Vediamo un esempio pratico di accesso al request header, avendo sott'occhio il listato di BZDD.H (pag. 169 e seguenti): la funzione mediaCheck(), che implementa il servizio 1 (vedere pag. 169), deve conoscere il numero e il media ID byte dell'unità disco sulla quale il DOS richiede informazioni per poi restituire il media change code e l'indirizzo far dell'etichetta di volume. Il campo media ID byte si trova nella parte fissa del request header, mentre tutti gli altri sono nella parte variabile. Innanzitutto, per chiarezza, in mediaCheck() è opportuno redichiarare come extern il puntatore al request header:
extern RequestHeaderFP RHptr;
Il tipo di dato RequestHeaderFP (definito con una typedef) indica un puntatore far ad una struttura di template RequestHeader.
L'accesso al numero dell'unità è ottenuto in modo assai semplice, con l'espressione:
RHptr->unitCode
Infatti, unitCode è un campo (di tipo BYTE, cioè unsigned char) della parte fissa del request header e, come tale, è direttamente membro del template RequestHeader.
Le espressioni che accedono ai campi della parte variabile sono più complesse, in quanto devono tenere presente che questa è rappresentata come una union, membro dello stesso template RequestHeader, avente nome cp: la base dell'espressione per accedere ad ogni campo della parte variabile è dunque
RHptr->cp
Nella union cp occorre, a questo punto, selezionare il template di struttura che rappresenta la parte variabile del request header dello specifico servizio di nostro interesse: quello relativo al servizio 1 ha nome mCReq. Ne segue che la base dell'espressione necessaria per accedere ad ogni campo della parte variabile per il servizio 1 è
RHptr->cp.mCReq
Il gioco è fatto: ogni membro di mCReq è, come accennato, un campo della parte variabile per il servizio 1. Le espressioni complete per l'accesso ai campi usati sono pertanto:
RHptr->cp.mCreq.mdByte
per il media ID byte (di tipo BYTE, cioè unsigned char);
RHptr->cp.mCreq.retByte
per il media change code (anch'esso di tipo BYTE, cioè unsigned char), ed infine
RHptr->cp.mCreq.vLabel
per la volume label (di tipo char far *).
Anche la macro setResCodeEnd() è definita in base alla tecnica descritta, ma della union 'parte variabile' (cp) utilizza il membro struttura che rappresenta proprio la parte variabile del servizio 0 e, all'interno di quest'ultima, il campo opportuno:
RHptr->cp.initReq.endAddr
Un po' di allenamento consente di orientarsi nel labirinto dei template ad occhi (quasi) chiusi.
Nel file BZDD.H (pag. 169) sono dichiarate (extern) le variabili globali accessibili al C definite nello startup module DDHEADER.ASM (pag. 169). Alcune di esse sono il perfetto equivalente delle variabili globali definite nello startup code dei normali programmi C:
extern int errno; // codice di errore
extern unsigned _version; // versione e revisione DOS
extern unsigned _osversion; // versione e revisione DOS
extern unsigned char _osmajor; // versione DOS
extern unsigned char _osminor; // revisione DOS
extern unsigned long _StartTime; // timer clock ticks al caricamento
La variabile _psp è anch'essa definita nello startup code C, ma con differente significato: per un programma essa rappresenta la parte segmento dell'indirizzo al quale è caricato il proprio PSP; nel caso di un device driver, non essendo presente un PSP, essa rappresenta la parte segmento dell'indirizzo al quale è caricato il driver stesso, cioè il valore del registro CS:
extern unsigned _psp;
Le altre variabili globali dichiarate in BZDD.H sono caratteristiche del toolkit startup module e contengono dati che possono risultare di qualche utilità per il programmatore.
La variabile
extern unsigned _baseseg;
è del tutto equivalente alla _psp.
La variabile
extern unsigned _systemMem;
contiene il numero di Kb di memoria convenzionale installati sul personal computer.
Le variabili
extern void huge *_farMemBase;
extern void huge *_farMemTop;
esprimono gli indirizzi dell'inizio e, rispettivamente, della fine della memoria convenzionale libera, compresa tra la RAM occupata dal device driver e quella occupata dalla routine SYSINIT del DOS (vedere pag. 169). La memoria compresa tra i due indirizzi è disponibile per il device driver, ma va tenuto presente che il valore di _farMemTop è determinato empiricamente ed è quindi da utilizzare con cautela. Dette variabili sono significative solo durante l'esecuzione di init() e vengono azzerate quando essa termina.
Alcune variabili rappresentano puntatori near a zone di memoria 'notevoli':
extern void *_endOfSrvc;
extern void *_endOfCode;
extern void *_endOfData;
extern void *_endOfDrvr;
La _endOfSrvc contiene l'indirizzo del primo byte successivo all'ultima delle funzioni di servizio del driver; la _endOfCode punta al primo byte successivo al codice eseguibile del driver[24]; la _endOfData punta al primo byte successivo al segmento riservato ai dati statici, globali e alle costanti. Detto indirizzo coincide con quello di inizio della memoria libera al di sopra del driver: _endOfDrvr contiene perciò il medesimo valore di _endOfData.
Le variabili
extern void *_freArea;
extern unsigned _freAreaDim;
sono significative solamente dopo la rilocazione dello stack originale. Se la chiamata a setStack() ha successo (vedere pag. 169), _freArea contiene l'indirizzo near dello stack originale, ora riutilizzabile come generico buffer, mentre _freAreaDim ne esprime la dimensione in byte. Se lo stack non viene rilocato (setStack() non è chiamata o fallisce) esse contengono entrambe 0.
Infine, le variabili
extern int _cmdArgsN;
extern char **_cmdArgs;
sono l'equivalente dei parametri formali attribuibili alla init() (pag. 169): _cmdArgsN contiene il numero di argomenti della command line del driver, incluso il pathname del driver stesso, mentre _cmdArgs è un array di puntatori a carattere, ogni elemento del quale punta ad una stringa contenente un argomento della command line: _cmdArgs[0] punta al nome del driver, come specificato in CONFIG.SYS; _cmdArgs[_cmdArgsN] è NULL.
Il device driver TESTINIT.SYS fa ciò che il nome suggerisce: pasticcia nella init() per saggiare alcune delle funzionalità offerte dal toolkit: rilocazione dello stack, allocazione dinamica della memoria, gestione dei file via stream Il listato è presentato di seguito; i numerosi commenti in esso presenti rendono superfluo soffermarsi oltre sulle sue caratteristiche.
/***************************************************************
TESTINIT.C - Barninga Z! - 1994
Device driver di prova - funzionalita' toolkit
Il driver effettua varie operazioni di inizializzazione in init()
ma non si installa residente in memoria.
Compilato con Borland C++ 3.1:
bcc -c -mt testinit.c
tlink -c -t ddheader.obj testinit.obj,testinit.sys,,bzdd.lib cs.lib
drvset -h8000 -nZ! testinit.sys
****************************************************************/
#pragma inline
#include <stdio.h>
#include <conio.h>
#include <alloc.h>
#include <bzdd.h>
#define MAXLIN 128
// Le variabili extern dichiarate di seguito sono definite nello startup module ma
// non sono dichiarate in BZDD.H (tuttavia sono pubbliche perche' devono essere
// visibili per alcune funzioni di libreria C): le dichiarazioni qui effettuate
// hanno lo scopo di renderle utilizzabili nel listato esclusivamente a scopo di
// debugging e di controllo. I LORO VALORI NON DEVONO ESSERE MODIFICATI.
extern unsigned _newTOS; // DEBUG
extern unsigned __brklvl; // DEBUG
extern unsigned __heapbase; // DEBUG
extern void far *_heapbase; // DEBUG
extern void far *_heaptop; // DEBUG
void testMemFile(char **argv);
// La stk() e' la funzione jolly che riserva lo spazio per il nuovo stack del driver
void stk(void)
// La variabile globale base e' inizializzata con l'offset dell'indirizzo di stk()
// mentre len contiene la lunghezza del nuovo stack: esse sono passate a setStack()
unsigned base = (unsigned)stk;
unsigned len = 4000;
// init(): tutte le operazioni di inizializzazione devono essere svolte qui. La
// dichiarazione di init() rende disponibili il numero di parametri della riga di
// comando in CONFIG.SYS (argc) e le stringhe dei parametri stessi (argv). In init()
// sono tranquillamente utilizzate le funzioni di libreria printf() e getch().
int init(int argc,char **argv)
// testMemFile() effettua operazioni di allocazione e disallocazione di memoria
// e apre e visualizza un file ASCII, il cui nome e' il primo argomento della
// command line di TESTINIT.SYS
void testMemFile(char **argv)
printf('Coreleft: %u.n',coreleft());
// ciclo di disallocazione per step successivi della memoria allocata in
// precedenza. Ad ogni ciclo e' visualizzato lo heap libero, l'indirizzo
// disallocato e il nuovo heap libero
for(i--; i >= 0; i--)
printf('Premere un tastonn');
getch();
// TESTINIT.SYS presume che sulla command line gli sia passato almeno un argomento
// utilizzato qui come nome di file da aprire. Deve essere un file ASCII. Il file
// e' aperto con fopen() e letto e visualizzato riga per riga con fgets() e printf()
// Infine il file e' chiuso con fclose()
if(!(f = fopen(argv[1],'r')))
perror('TESTINIT.SYS');
else
}
La generazione di TESTINIT.SYS a partire da TESTINIT.C può essere automatizzata con un semplice file batch:
@echo off
REM
REM *** generazione di testinit.obj
REM
bcc -c -mt testinit
if errorlevel 1 goto error
REM
REM *** generazione di testinit.sys
REM
tlink -c -t ddheader testinit,testinit.sys,,bzdd cs
if errorlevel 1 goto error
REM
REM *** attribute word settata per character device driver; nome logico: 'Z!'
REM
drvset -h8000 -nZ! testinit.sys < yes.txt
if errorlevel 1 goto error
REM
REM *** eliminazione file inutili
REM
del testinit.obj
del testinit.map
goto end
REM
REM *** visualizza messaggio in caso di errore
REM
:error
echo TESTINIT.SYS non generato!
REM
REM *** fine batch job
REM
:end
Si noti che DRVSET riceve lo standard input dal file YES.TXT, contenente esclusivamente il carattere 'Y' seguito da CR e LF (vedere pag. 169).
TESTINIT.SYS è copiato nella directory root del drive C: dal file batch medesimo, perciò la riga di CONFIG.SYS che ne determina il caricamente deve essere analoga alla seguente:
DEVICE=C:TESTINIT.SYS C:CONFIG.SYS 1234 ' abc 678' DeF
Il primo argomento (C:CONFIG.SYS) è l'unico significativo, in quanto indica il file ASCII che la testMemFile() deve aprire e visualizzare; i rimanenti parametri hanno unicamente lo scopo di verificare il buon funzionamento della funzione di libreria toolkit setupcmd() (vedere pag. 169). Si noti che ' abc 678' è un unico parametro, grazie alla presenza delle virgolette, le quali consentono inoltre la prsenza di uno spazio prima dei caratteri abc. Tutti i parametri sono convertiti in caratteri maiuscoli da setupcmd().
Il driver TESTDEV.SYS gestisce i servizi Output, Output With Verify e Generic IOCTL Request. Si tratta di routine estremamente semplificate, che hanno unicamente lo scopo di dimostrarne le funzionalità di base.
/***************************************************************
TESTDRV.C - Barninga Z! - 1994
Device driver di prova - funzionalita' toolkit
Il driver gestisce una funzione di output e di output with
verify equivalenti e consente di modificare la modalita'
di output mediante una generic IOCTL request.
Compilato con Borland C++ 3.1:
bcc -c -mt testdrv.c
tlink -c -t ddheader.obj testdrv.obj,testdrv.sys,,bzdd.lib cs.lib
drvset -h8040 -nzeta testdrv.sys
****************************************************************/
#pragma inline
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <bzdd.h>
void colortext(WORD count,BYTE far *buffer); // funzione di servizio per output()
// costanti manifeste definite per comodita'.
#define DEFAULT_ATTR 7 // Bianco/Nero
#define DEFAULT_PAGE 0 // Pagina video (unica usata)
#define BLANK ' ' // Spazio
// costanti manifeste definite per supporto a Generic IOCTL Request (servizio 19)
// IOCTL_CATEGORY e' un identificativo del driver, una specie di parola d'ordine
// inventata di sana pianta. Anche i sottoservizi implementati, IOCTL_GETATTR e
// IOCTL_SETATTR, sono stati numerati 1 e 2 per libera scelta.
#define IOCTL_CATEGORY 0x62 // Identificativo
#define IOCTL_GETATTR 1 // IOCTL servizio 1
#define IOCTL_SETATTR 2 // IOCTL servizio 2
// attrib e' una normale variabile globale, destinata a contenere l'attributo video
// per gli output di testo.
BYTE attrib;
// funzione per la gestione del servizio Write (8). Il suo prototipo e' identico a
// quello dichiarato in BZDD.H. Essa chiama la funzione colortext(), che effettua
// la vera e propria operazione di output, passandole i necessari parametri,
// prelevati dalla parte variabile del request header. Restituisce E_OK, definita in
// BZDD.H per segnalare la fine dell'operazione.
int output(void)
// funzione per la gestione del servizio Write With Verify (9). Come si vede non fa
// altro che chiamare la output, quindi non vi e' alcuna verifica. E' qui solamente
// a scopo dimostrativo. Il prototipo e' identico a quello dichiarato in BZDD.H.
int outputVerify(void)
// funzione per la gestione del servizio Generic IOCTL Request (19). Il prototipo
// e' identico a quello dichiarati in BZDD.H. Essa consente di modificare
// l'attributo video usato dalla output(), o meglio, dalla colortext(), o,
// semplicemente, di conoscere quello attuale, memorizzato nella variabile globale
// attrib. La generic IOCTL Request e' attivata dalle applicazioni mediante
// l'int 21h, servizio 44h, subfunzione 0Ch (character device driver) o 0Dh (block
// device driver). Vedere pagina 169 per un esempio. Per entrambe le funzioni
// IOCTL_GETATTR e IOCTL_SETATTR il formato del campo packet (parte variabile del
// request header) e' molto semplice: il primo byte contiene l'attributo video sia
// in ingresso (se comunicato dall'applicazione al driver) che in uscita (comunicato
// dal driver all'applicazione).
int genericIOCTL(void) // buffer dati 'packet': 1 byte = nuovo attributo
return(E_OK);
}
void colortext(WORD count,BYTE far *buffer)
// init() inizializza il driver, valorizzando la variabile globale attrib. Le altre
// operazioni sono semplicemente la visualizzazione di alcuni dati
int init(int argc,char **argv)
else
attrib = DEFAULT_ATTR;
// visualizza il segmento di caricamento del driver e la versione di DOS
printf('nDevice Driver di prova a %04X:0000. DOS %d.%d.n',
_baseseg,_osmajor,_osminor);
// copia il nome logico del device dal device driver header ad un buffer locale
for(i = 0; i < 9; i++)
if((logicalName[i] = DrvHdr.ln.cname[i]) == BLANK)
break;
// trasformazione in stringa ASCIIZ
logicalName[i] = NULL;
// visualizza valore iniziale di attrib e il nome logico del device
printf('Attributo testo device %s : %dnn',logicalName,attrib);
// richiede di lasciare residente in memoria tutto il codice/dati del driver
setResCodeEnd(_endOfDrvr);
return(E_OK);
}
Vale la pena di soffermarsi sul servizio 13h dell'int 10h (vedere pag. 169), che implementa la funzionalità di output: esso, richiedendo che l'indirizzo della stringa da visualizzare sia caricato in ES:BP, introduce alcune difficoltà nella realizzazione della funzione C colortext(). Infatti, il registro BP è utilizzato dal compilatore C per generare tutti i riferimenti a parametri attuali e variabili locali, cioè ai dati memorizzati nello stack (vedere pag. 168): quando se ne modifichi il valore, come è necessario fare in questo caso, diviene impossibile accedere ai parametri e alle variabili automatiche della funzione (eccetto le variabili register) fino a quando il valore originale di BP non sia ripristinato. Come si vede, colortext() salva BP sullo stack con una istruzione PUSH e lo ripristina con una istruzione POP: ciò è possibile in quanto dette istruzioni referenziano lo stack mediante il registro SP. L'implementazione di una funzione dedicata (la colortext()), che riceve i dati della parte variabile del request header come parametri, si è rivelata preferibile all'inserimento dell'algoritmo nella output(), in quanto l'accesso ai campi di una struttura è effettuato, generalmente, mediante i registri ES e BX: ciò avrebbe reso più difficoltoso l'utilizzo degli pseudoregistri (vedere pag. 169).
L'uso della macro geninterrupt() non interessa le librerie C (vedere pag. 169): il driver è pertanto realizzato in modo da rendere possibile il troncamento della parte residente all'indirizzo della init(). Perché ciò sia possibile è ancora necessario definire la variabile attrib mediante una funzione jolly (e non come vera variabile globale) e implementare nel sorgente C tutte le funzioni di servizio prima della stessa init(), come nell'esempio che segue:
int input(void)
Anche il riferimento a unSupported(), come nel caso di geninterrupt(), non interessa le librerie (unSupported() è definita nel toolkit startup module).
Segue il listato del batch file utilizzato per la generazione di TESTDRV.SYS:
@echo off
REM
REM *** generazione di testdrv.obj
REM
bcc -c -mt testdrv
if errorlevel 1 goto error
REM
REM *** generazione di testdrv.sys
REM
tlink -c -t ddheader testdrv,testdrv.sys,,bzdd cs
if errorlevel 1 goto error
REM
REM *** attribute word per character device driver con generic IOCTL supportato;
REM *** nome logico: 'ZETA'
REM
drvset -b1000000001000000 -nzeta testdrv.sys < yes.txt
if errorlevel 1 goto error
REM
REM *** eliminazione file inutili
REM
del testdrv.obj
del testdrv.map
goto end
REM
REM *** visualizza messaggio in caso di errore
REM
:error
echo TESTDRV.SYS non generato!
REM
REM *** fine batch job
REM
:end
Si noti che l'opzione ‑b richiede a DRVSET di modificare la device attribute word del driver in modo che il DOS lo riconosca come un character device driver (bit 15) in grado di supportare la funzionalità di generic IOCTL request (bit 6).
TESTDRV può essere installato inserendo in CONFIG.SYS una riga analoga alla seguente:
DEVICE=C:TESTDRV.SYS 23
ove il parametro 23 rappresenta l'attributo video iniziale (nell'esempio testo bianco su fondo blu, ma si può, ovviamente, scegliere qualsiasi combinazione di colori).
Dopo il bootstrap è sufficiente redirigere lo standard output al device ZETA per vedere il nostro driver in azione: il comando
type c:config.sys > zeta
visualizza il contenuto del file CONFIG.SYS in caratteri bianchi su fondo blu. Modificando il parametro sulla command line del driver ed effettuando un nuovo bootstrap è possibile sperimentare altre combinazioni di colori (ad esempio 77 produce caratteri magenta su fondo rosso[25]).
E la generic IOCTL request? L'implementazione di genericIOCTL() consente di indagare o modificare al volo l'attributo usato per il device ZETA, senza che vi sia necessità di un reset del computer. L'interfaccia DOS è rappresentata dall'int 21h, servizio 44h, subfunzioni 0Ch (character device driver) e 0Dh (block device driver).
Int 21h, Serv. 44h, subf. 0Ch e 0Dh: Generic IOCTL request
Input |
AH AL CH CL DS:DX |
44h 0Ch (character device driver) Category Code Function Code indirizzo del buffer dati (packet) |
Output |
AX |
codice di errore se CarryFlag = 1. Se CarryFlag = 0, la chiamata ha avuto successo. |
Note |
|
A partire dalla versione 3.3 del DOS sono state adottate alcune convenzioni circa Category Code e Function Code, non tutte documentate ufficialmente. |
Il programma DEVIOCTL, listato di seguito, consente di pilotare il driver TESTDEV.SYS mediante l'invio di una generic IOCTL request al DOS, che, a sua volta (e in modo del tutto trasparente all'applicazione) la trasmette al device driver e riceve da questo il risultato, poi trasferito (sempre in modo trasparente) all'applicazione[26].
/***************************************************************
DEVIOCTL.C - Barninga Z! - 1994
Applicazione per test funzionalita' Generic IOCTL Request
nel driver TESTDEV.SYS.
Lanciato senza parametri richiede l'attributo testo attuale;
lanciato con un parametro assume che esso sia il nuovo
attributo testo desiderato e lo passa al driver.
Compilato con Borland C++ 3.1:
bcc devioctl.c
****************************************************************/
#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <stdlib.h>
#include <dos.h>
#include <string.h>
#define HELPSTR '?'
#define IOCTL_CATEGORY 0x62
#define IOCTL_GETATTR 1
#define IOCTL_SETATTR 2
int openZETA(void); // funzione di apertura del device
void requestIOCTL(int argc,char **argv,int handle); // gestione generic IOCTL request
char *help = ' // stringa di help
Sintassi : DEVIOCTL [nuovo_attrib]n
Senza alcun parametro: richiede l'attributo corrente;n
Con un parametro numerico: lo utilizza per modificare l'attributo correnten
';
// main() pilota le operazioni, controllando il parametro della command line,
// lanciando le funzioni di apertura device e di invio della IOCTL request e
// chiudendo il device a fine lavoro.
int main(int argc,char **argv)
if(!(handle = openZETA()))
requestIOCTL(argc,argv,handle) // invio della generic IOCTL request
close(handle); // chiusura del device
return(0);
}
// Un'applicazione puo' scrivere o leggere un character device solo dopo averlo
// aperto, utilizzando il nome logico come un vero e proprio nome di file. La
// funzione openZETA() apre il device avente nome logico ZETA mediante la funzione di
// libreria C open(); se l'operazione ha successo utilizza l'int 21h, servizio 44h,
// subfunzione 00h per assicurarsi di avere aperto un device e non un file: se il
// carry flag e' 0 e il bit 7 di DX e' 1, allora e' proprio un device driver.
// Infatti il carry flag a 1 indica un errore, mentre il bit 7 di DX a 0 indica che
// si tratta di un file (TESTDRV.SYS non e' installato ed esiste un file 'ZETA'
// nella directory corrente del drive di default).
int openZETA(void)
// La generic IOCTL request e' inviata invocando l'int 21h attraverso la funzione
// di libreria C intr() (vedere pag. 119 e seguenti). Il request packet si compone
// di un solo byte, usato per comunicare al driver il nuovo attributo video e
// ricevere in risposta quello attuale.
void requestIOCTL(int argc,char **argv,int handle)
// il valore per l'attributo e' stato posto nel primo byte del request packet,
// cioe' direttamente nella variabile attrib
printf('Attributo corrente = %dn',attrib);
break;
case 2:
// un parametro sulla command line: e' il nuovo attributo da comunicare al driver
r.r_cx |= IOCTL_SETATTR; // CL = 2 (function)
attrib = newAttrib = (unsigned char)atoi(argv[1]);
// l'indirizzo della variabile attrib, che funge da IOCTL packet, e' gia' stato
// caricato in DS:DX prima della switch
intr(0x21,&r);
if(r.r_flags & 1)
// il valore per l'attributo e' stato posto nel primo byte del request packet,
// cioe' direttamente nella variabile attrib
printf('Attributo: corrente = %d; nuovo = %dn',attrib,newAttrib);
break;
}
}
DEVIOCTL può essere compilato con il comando
bcc devioctl.c
che produce l'eseguibile DEVIOCTL.EXE. Questo, se lanciato con un punto interrogativo ('?') quale unico parametro della command line, visualizza un breve testo di aiuto.
Se invocato senza alcun parametro, DEVIOCTL richiede al driver la funzione IOCTL_GETATTR per conoscere l'attributo video attualmente utilizzato per il device ZETA e visualizza la risposta del driver.
Se invocato con un parametro numerico, DEVIOCTL richiede al driver la funzione IOCTL_SETATTR, per forzare il driver a utilizzare quale nuovo attributo video il parametro della command line; il driver risponde restituendo il precedente attributo utilizzato, che viene visualizzato da DEVIOCTL.
Se TESTDRV non è installato (e quindi il device ZETA non è attivo), DEVIOCTL segnala l'errore.
E' sufficiente installare TESTDRV.SYS come sopra descritto e giocherellare con DEVIOCTL per provare l'ebbrezza di pilotare direttamente il device driver.
[1] Benché il DOS sia in grado, a partire dalla versione 3.0, di caricare device driver sotto forma di programmi .EXE, il formato binario puro rimane l'unico compatibile con tutte le versioni di sistema operativo. Inoltre, molte delle difficoltà insite nell'utilizzo del C per scrivere un device driver rimangono anche quando lo si realizzi sotto forma di .EXE.
[2] Non venga in mente a qualcuno di dichiararla interrupt. Il DOS invoca la interrupt routine eseguendo una CALL FAR; la IRET che chiude ogni funzione dichiarata interrupt provocherebbe l'estrazione dallo stack di una word di troppo, con le solite disastrose conseguenze.
[3] Esempietto: allocazione dinamica di RAM mediante farmalloc() e compagnia. Le funzioni di libreria per la gestione di memoria far lavorano estendendo, tramite il servizio DOS SETBLOCK (int 21h,4Ah) il blocco di RAM allocato al programma: una chiamata a farmalloc() è destinata a fallire miseramente in ogni caso, perché il blocco di memoria assegnato al driver è statico (ha comunque altri blocchi allocati al di sopra di sé; se non altro quello dell'interprete dei comandi).
[4] Ciò è esplicitato, a livello di sorgente, dalla direttiva assembler ORG 00H. Per i normali programmi eseguibili l'offset è, normalmente, 100h e corrisponde al primo byte che segue il PSP (che, a sua volta, occupa proprio 256 byte, 100 esadecimale).
[5] La scelta della logica di implementazione è, ovviamente, libera. Si tenga però presente che anche i servizi definibili dal programmatore (si veda, ad esempio, il servizio 19 a pag. 169) devono rispettare rigorosamente le regole definite per l'interfacciamento con il sistema (struttura ed utilizzo del request header).
[6] Qualche? Beh, cerchiamo di essere ottimisti
[7] Il C è, notoriamente, un linguaggio case‑sensitive.
[8] Con il compilatore è fornito uno startup module apposito per ogni modello di memoria supportato: al linker è indicato dal compilatore stesso quale file .OBJ costituisce il modulo appropriato. Circa i modelli di memoria e le loro caratteristiche, vedere pag. 151.
[9] Il sorgente dello startup module fa quasi sempre parte della dotazione standard dei compilatori, a beneficio dell'utilizzatore che desideri personalizzarlo o ricarvarne nuove idee. Ne consegue che l'implementazione di 'device driver startup module' qui presentata, derivata dallo startup code del compilatore Borland C++ 3.1, necessita sicuramente modifiche più o meno pesanti per essere utilizzata con altri compilatori.
[10] Detto in povere ed approssimative parole, i segmenti rappresentano porzioni di sorgente che devono essere sottoposte a linking in un certo ordine e indirizzate dai registri di segmento secondo predefinite modalità. Ad ogni segmento sono attribuiti un nome ed una classe: l'assemblatore raggruppa tutti i segmenti che hanno medesimo nome, seguendo l'ordine nel quale li individua nel sorgente (o nei diversi sorgenti); la classe definisce gruppi di segmenti e fornisce indicazioni sull'indirizzamento.
[11] Alcune sono utilizzate da funzioni di libreria C: ne segue che sono consolidate al file binario solo se quelle funzioni sono invocate nel sorgente C.
[12] La costante manifesta STKSIZE è definita nel file DDSEGCOS.ASI (pag. 169).
[13] Nel nuovo stack può essere disponibile meno spazio di quanto richiesto: dal momento che ogni stack deve iniziare ad un indirizzo pari e deve essere composto da un numero pari di byte, setStack() effettua gli aggiustamenti eventualmente necessari. Ad esempio, se la dimensione specificata è pari, ma l'indirizzo base del nuovo stack è dispari, setStack() rende disponibile un byte in meno di quanto richiesto e modifica opportunamente l'indirizzo ricevuto come parametro. La differenza tra dimensione richiesta ed effettiva può essere al massimo pari a 2 byte.
[14] Il modello di memoria di riferimento è il tiny model, pertanto tutti gli indirizzi sono, per default, near; inoltre CS = DS = SS.
[15] In realtà, la dichiarazione di variabili automatiche, allocate nello stack, non è di per sé un problema, ma potrebbe esserlo il loro contenuto. Se, ad esempio, una di esse è un puntatore inizializzato, prima della chiamata a setStack(), con un indirizzo relativo allo stack stesso, appare evidente che 'spostando' questo, detto indirizzo dovrebbe essere aggiornato di conseguenza; setStack(), tuttavia, conosce lo spazio occupato dai dati che è chiamata a rilocare, ma non il loro significato.
[16] L'indirizzo passato dal DOS nel request header è quello del byte che segue immediatamente la stringa DEVICE= in CONFIG.SYS. La stringa che si trova a quell'indirizzo non deve essere modificata: di qui la necessità di crearne una copia locale ad uso esclusivo del device driver. Si tenga inoltre presente che la stringa non termina con un NULL, ma con un CR o un LF o un EOF (ASCII 13, 10, 26 rispettivamente).
[17] Il nome del response file deve essere passato a TLIB preceduto dal carattere @.
[18] A dire il vero esiste una terza possibilità: scorporare il device driver header da DDHEADER.ASM: si otterebbe così un (piccolo) DDHEADER.ASM, contenente il solo device driver header, e un DDSTART.ASM, contenente tutto il resto. DDSTART.ASM potrebbe essere assemblato una volta per sempre, mentre DDEHADER.ASM dovrebbe essere editato e riassemblato per ogni driver. Inoltre bisognerebbe ricordarsi sempre di specificare al linker, prima dell'object file risultante dalla compilazione del sorgente C, DDHEADER.OBJ e DDSTART.OBJ, in questo preciso ordine.
[19] Beh, vale la pena di dargli almeno un'occhiata: si può capire molto circa la struttura del file binario e la posizione, al suo interno, dei segmenti, delle funzioni e delle variabili globali.
[20] Va infine sottolineato che il sorgente del device driver può definire anche una main(), ma con il ruolo di una funzione qualsiasi: non viene invocata in modo automatico e riceve i parametri che le passa la funzione che la chiama, coerentemente al prototipo definito 'per l'occasione'.
[21] Come nel caso di main(), non è obbligatorio utilizzare argc e argv: i nomi possono essere liberamente scelti dal programmatore.
[22] Si veda BZDD.H per la dichiarazione di questa ed altre variabili esprimenti gli indirizzi di diverse porzioni del device driver. Esse, inoltre, sono descritte a pag. 169.
[23] Come fare? Basta un'occhiata alla definizione della stessa setResCodeEnd() in BZDD.H per capirlo. Vedere anche, di seguito, la descrizione della modalità di accesso ai campi del request header.
[24] Si noti che lo stack del driver è sempre all'interno dell'area del codice eseguibile: infatti, lo stack originale è esplicitamente definito (startup module) nel code segment, mentre quello rilocato lo è implicitamente, essendo definito mediante una funzione (fittizia).
[25] I numeri da 0 a 7 rappresentano, rispettivamente: nero, blu, verde, azzurro, rosso, viola, marrone e bianco. Al codice di ogni colore si può sommare 8: nel caso del testo si ottiene il medesimo colore, con effetto alta intensità (il marrone appare giallo, il viola appare magenta), mentre nel caso del fondo si ha l'effetto intermittenza sul testo. L'attributo si calcola con la seguente formula:
attributo = (colore_fondo * 16) + colore_testo
dal che si evidenzia che, ad esempio, 23 si ottiene da (1*16)+(7).
[26] In pratica i device driver, secondo il normale schema di azione, dialogano esclusivamente con il DOS. A sua volta, anche l'applicazione dialoga solo con il DOS, il quale isola e, al tempo stesso, interfaccia le due 'controparti'.
Appunti su: |
|