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


AppuntiMania.com » Informatica » Appunti di c » I device driver e il c

I device driver e il c




Visite: 1728Gradito:apreciate 5-stela [ Grande appunti ]
Leggi anche appunti:

La command line


La command line Si intende, per command line, la riga di testo digitata al prompt

Interazione tra c e assembler


Interazione 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
immagine di categoria

Scarica gratis I device driver e il c

I Device Driver e il C

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:


I primi 18 byte del file sono occupati dal device driver header: non è perciò possibile compilare e sottoporre a linking il sorgente secondo le normali modalità C.


Lo startup module non può essere utilizzato.


La restante parte del sorgente deve essere strutturata come nel caso di un programma TSR (pag.  ), con le funzioni transienti nella parte terminale. E' inoltre opportuno utilizzare il solito trucchetto delle funzioni jolly (pag.  ) per gestire i dati globali utilizzati anche dalla parte residente.


Se l'operatività del driver è 'pesante', è necessario dotarlo di uno stack locale, onde evitare di rompere le uova nel paniere al DOS: vedere pag. 


Mentre la strategy routine non pone particolari problemi, la interrupt routine deve essere realizzata tenendo presenti alcuni accorgimenti: innanzitutto deve essere dichiarata far (come del resto la strategy), inoltre deve salvare tutti i registri della CPU (compresi i flag) e deve analizzare il Command Code per invocare l'opportuna funzione dedicata. Infine, in uscita, deve ripristinare correttamente i registri e gestire la restituzione di una corretta status word (pag.  ) al DOS tramite l'apposito campo del request header.


In fase di inizializzazione il driver ha a disposizione tutti i servizi BIOS, ma non tutti quelli DOS. In particolare non può allocare memoria. Durante la normale operatività i servizi DOS sono, in teoria, tutti disponibili, ma la logica con cui operano alcune delle funzioni di libreria C le rende comunque inutilizzabili . Mancano, comunque, environment e PSP: l'assenza del Program Segment Prefix determina il caricamento dell'immagine binaria del file ad offset 00h rispetto al Code Segment, cioè al valore di CS


L'utilizzo di funzioni di libreria è comunque sconsigliato in tutte le routine residenti, per i problemi già analizzati con riferimento ai TSR (pag. 


Per ogni servizio, anche se non supportato dal driver, deve essere implementata una routine dedicata: questa deve, quanto meno, invocare a sua volta una generica funzione di gestione dell'errore.


Il DOS assume che le routine di gestione dei servizi si comportino in completo accordo con le specifiche ad essi relative : programmatore avvisato

Ce n'è abbastanza per divertirsi e trascorrere qualche notte insonne. Tuttavia vale la pena di provarci: qualcosa di interessante si può certamente fare.

Un timido tentativo

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

Progetto di un toolkit

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.

Il nuovo startup module

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.

La libreria di funzioni

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

La utility per modificare gli header

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

Il toolkit al lavoro

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.

La funzione init()

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.

Altre funzioni e macro

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.

L'accesso al device driver request header

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.

Le variabili globali dello startup module

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.

Esempio: alcune cosette che il toolkit rende possibili

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

Esempio: esperimenti di output e IOCTL

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)
0Dh (block 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'.

Scarica gratis I device driver e il c
Appunti su:



Scarica 100% gratis e , tesine, riassunti



Registrati ora

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