Virtual Device Driver : Nozioni di base

In questa serie di tutorial, assumo che tu, il lettore, sia familiare con i meccanismi della modalita' protetta dell' Intel  80x86 quali il modo virtual 8086, la paginazione, GDT,LDT,IDT. Se non  ne conosci il funzionamento ti consiglio di leggere prima la documentazione Intel a questo indirizzo: http://developer.intel.com/design/pentium/manuals/

Prefazione:

Window 95 e' un sistema operativo multithreaded funzionante al livello di privilegio piu' alto, ring 0. Tutti i programmi applicativi funzionano invece a ring 3, il piu' basso livello di privilegio. Questa situazione determina che i programmi applicativi sono limitati in quello che possono fare all'interno del sistema. Essi non possono eseguire istruzioni CPU privilegiate, accedere direttamente alle porte I/O e cosi' via. Avrete sicuramente familiarita' con questi tre grossi componenti di sistema: gdi32, kernel32 e user32. Proteste essere portati a pensare che questi tre importanti moduli debbano funzionare a ring 0. Ma in realta' essi funzionano a ring 3, come tutte le altre applicazioni. Cio' significa che essi non hanno piu' privilegi che, diciamo, la calcolatrice di Windows o il gioco del campo minato. La vera potenza del sistema operativo e' sotto il controllo del virtual machine manager (VMM) e dei virtual device drivers (VxD).

Tutto questo non sarebbe accaduto se il DOS non avesse reso lo scenario piu' complicato. Durante l'era Windows 3.1, c'erano molti programmi DOS di successo sul mercato. Windows 3.x doveva essere in grado di farli funzionare accanto ai normali programmi Windows altrimenti sarebbe fallito commercialmente.
Questo dilemma non era semplice da risolvere. I programmi DOS e Windows sono drasticamente differenti l'uno dall'altro. I programmi DOS sono "scorretti" in quanto pensano di controllare ogni cosa nel sistema: tastiera, CPU, memoria, hd disks, ecc. Essi non sanno come cooperare cogli altri programmi mentre i programmi Windows (a quel tempo) si basano sul multitasking cooperativo, ovverosia ogni programma Windows deve cedere il controllo agli altri programmi attraverso GetMessage o PeekMessage.
La soluzione e' far funzionare ogni programma DOS in una virtual 8086 machine mentre tutti gli altri programmi Windows funzionano in un'altra virtual machine chiamata system virtual machine. Windows e' responsabile di dare tempo CPU ad ogni virtual machine in maniera ciclica. Percio' sotto Windows 3.x, i programmi Windows utilizzano il multitasking cooperativo invece le virtual machines utilizzano il multitasking preemptive.

Ma cos'e' una virtual machine? Una virtual machine e' un artificio creato solamente dal software. Una virtual machine reagisce ai programmi in esecuzione come se fosse una vera macchina. Ne segue che un programma non sa di funzionare in una virtual machine e non se ne preoccupa. Percio', fintanto che la virtual machine risponde al programma esattamente come una vera macchina, essa puo' essere considerata come una cosa reale. Si puo' pensare all'interfaccia tra una macchina reale e il suo software come ad una sorta di API. Questa inusuale API consiste di interrupts, chiamate al BIOS, e porte I/O. Se Windows puo' in qualche modo emulare perfettamente questa API, il programma in esecuzione nella virtual machine si comportera' esattamente come se fosse eseguito in una macchina reale.
Questo e' il punto in cui VMM e VxDs entrano in scena. Windows per coordinare e supervisionare le virtual machines (VMs) necessita' di un programma dedicato allo scopo. Questo programma e' il Virtual Machine Manager.

Virtual Machine Manager

VMM e' un programma in protected mode a 32-bit. La sua principale responsabilita' e' erigere e gestire l'intelaiatura che supporta le virtual machines. Come tale e' responsabile della creazione,esecuzione e terminazione delle VMs. VMM e' uno dei molti sottosistemi VxDs che sono conteneuti, nella cartella di sistema, in VMM32.VXD. Benche' sia anch'esso un VxD puo' essere considerato il supervisore degli altri VxDs. Esaminiamo la sequesnza di boot di Windows 95:
  1. io.sys e' caricato in memoria
  2. vengono processati config.sys e autoexec.bat
  3. viene eseguito win.com
  4. win.com lancia VMM32.VXD che e' effettivamente un semplice file EXE DOS
  5. VMM32.VXD carica VMM nella memoria estesa utilizzando il driver XMS
  6. VMM inizializza se stesso e gli altri virtual device drivers di default
  7. VMM commuta la macchina in protected mode e crea la system virtual machine
  8. Virtual Shell Device, il quale e' caricato per ultimo, avvia Windows nella system VM eseguendo krnl386.exe
  9. krnl386.exe carica tutti gli altri files, culminando nella shell di Windows 95.

Come potete vedere, VMM e' il primo VxD ad essere caricato in memoria. Esso crea la system virtual machine ed inizializza gli altri VxDs. Esso, inoltre, fornisce numerosi services agli altri VxDs.
Il modo di operare di VMM e dei VxDs e' differente dagli altri programmi normali. Questi sono per la maggior parte del tempo dormienti. Mentre i programmi applicativo sono in esecuzione nel sistema, questi VxDs sono inattivi. Essi sono svegliati quando si verificano interrupts/faults/eventi che necessitano della loro attenzione.
VMM non e' rientrante. Questo significa che i VxDs devono sincronizzare il loro accesso ai services di VMM. Ci sono delle situazioni in cui non e' sicuro chiamare i servizi di VMM come quando un interrupt hardware e' in elaborazione. Durante quel periodo VMM non puo' tollerare rientranza. Come per i VxD anche voi dovete stare estremamente attenti a quello che state facendo. Ricordate che  non c'e' nessun altro che si preoccupa di eventuali errori del vostro codice. A ring 0 siete assolutamente nelle vostre mani.

Virtual Device Driver

Virtual Device Driver e' abbreviato in VxD. La x e' un sostituto per il nome del device quale virtual keyboard driver, virtual mouse driver e cosi' via. I VxDs sono la chiave per una corretta virtualizzazione dell' hardware . Ricordate che i programmi DOS pensano di possedere ogni cosa nel sistema. Quando essi funzionano nelle virtual machines, Windows deve provvedere a fornigli dei sostituti per i veri devices. I VxDs sono questi sostituti. I VxDs normarmente virtualizzano dei devices hardware. Cosi', ad esempio, quando un programma DOS pensa di stare interagendo con la tastiera, in realta, e' il virtual keyboard device che lavora con esso. Un VxD normalmente prende il controllo del vero device hardware e ne gestisce la condivisione fra le varie VMs.
Tuttavia non c'e' nessuna regola che dica che un VxD DEBBA essere necessariamente associato ad un device hardware. E' vero che i VxDs sono progettati per virtualizzare i device hardware ma possiano anche trattarli come DLLs a ring 0. Per esempio, se volete che la vostra applicazione abbia delle features che possono essere ottenute solo a ring 0 potete scrivere un VxD che svolga il lavoro per voi. In tal caso si puo' considerare il VxD come un estensione del vostro programma dal momento che esso non virtualizza alcun device hardware.
Prima di tuffarvi a capofitto e creare il vostro VxD, lasciatemi innanzitutto puntualizzare alcune cose su quest'ultimi: Ci sono due tipi di VxD sotto Windows 95:

I VxDs statici sono quelli che vengono caricati durante la fase di boot del sistema e restano caricati fino alla chiusura del sistema stesso. Questo tipo di VxD risale al periodo di Windows 3.x. I VxDs dinamici sono invece disponibili solo in Windows 9x. I VxDs dinamici possono essere caricati/scaricati dalla memoria solo quando necessari. La maggior parte di essi sono VxDs che gestiscono le periferiche Plug and Play che sono caricate dal Configuration Manager e dall' Input Output Supervisor. Potete inoltre caricare/scaricare i VxDs dimanici direttamente dalle vostre applicazioni win32.

Comunicazione fra i VxDs

I VxDs, incluso VMM, comunicano fra di loro attraverso tre meccanismi:

Control Messages: Quando si verifica qualche evento di interesse, VMM invia messaggi di controllo a TUTTI i VxDs caricati nel sistema. In questa logica i messaggi di controllo sono come i windows messages delle applicazioni Windows a ring-3. Ogni VxD possiede una funzione, chiamata device control procedure , che riceve ed interagisce con i control messages. Esistono circa 50 o poco piu' control messages di sistema. La ragione per cui non esistono molti messaggi di controllo e' che spesso ci sono parecchi VxD caricati nel sistema e dato che ognuno di essi viene interrotto ad ogni messaggio di controllo, se ce ne fossero troppi il sistema verrebbe sovraccaricato fino ad un blocco totale. Ad ogni modo voi vorrete gestire solo i messaggi realmente importanti collegati alle VMs, ad esempio  quando una VM e' creata, distrutta e cosi' via. In aggiunta ai messaggi di controllo del sistema, un VxD puo' definire i propri control messages personalizzati che possono poi essere utilizzati per comunicare con altri VxDs che li comprendono.

Service APIs : Un VxD, compreso VMM, normalmente esporta un insieme di funzioni pubbliche che possono essere invocate dagli altri VxDs. Queste funzioni sono chiamate servizi del VxD. Il meccanismo di chiamata dei VxD services e piuttosto differente da quello delle applicazioni a ring-3. Ogni VxD che esporta servizi DEVE possedere un ID number unico. Ciascuno puo' ottenere questi IDs dalla Microsoft. Un ID e' un numero a 16-bit che identifica univocamente un VxD. Ad esempio:

Come potete vedere quello della VMM ha ID pari ad 1, VPICD ha ID 3, e cosi' via. VMM utilizza questo ID univoco per trovare il VxD che esporta i VxD services richiesti. Inoltre dovete selezionare il servizio che volete chiamare anche attraverso il suo indice nella tabella dei servizi. Quando un VxD esporta dei servizi VxD, esso salva l'indirizzo degli stessi in una tabella locale. VMM utilizzera' poi l'indice fornito per localizzare l'indirizzo del servizio desiderato dalla tabella dei servizi. Per esempio se volete chiamare GetVersion, che e' il primo servizio della VMM, dovete specificare 0 (l'indice e' zero-based). Il reale meccanismo di una chiamata ad un servizio VxD implica poi un int 20h. Il vostro codice effettua un int 20h seguito da una valore DWORD che e' composto dal device ID e dall'indice del servizio. Cosi', per esempio, se volete chiamare il servizio numero 1 che e' esportato da un VxD che possiede un device ID di 000Dh, il codice dovrebbe essere come questo:

La WORD alta della DWORD che segue l'istruzione int 20h contiene il device ID. La WORD bassa e' invece l'indice zero-based nella tabella dei servizi.
Quando l' int 20h e' invocato, VMM ottiene il controllo ed esamina la DWORD immediatamente seguente l'istruzione di interrupt. Estrae poi il device ID e lo utilizza per trovare il VxD e quindi usa l'indice del servizio per localizzare l'indirizzo del servizio all'interno di quel VxD.

Potete capire che questa operazione e' piuttosto costosa in termini di tempo macchina. VMM deve sprecare tempo sia a localizzare il VxD che l'indirizzo del servizio desiderato.  Di conseguenza VMM  imbroglia un pochino. Dopo che il primo int 20h ha successo, VMM fissa il collegamento. Per fissare il collegamento si intende che VMM rimpiazza l' int 20h e la DWORD sucessiva con una chiamata diretta al servizio. Cosi' il precedente codice dell' int 20h verrebbe trasformato in :

 call dword ptr [VxD_Service_Address]

Questo trucco funziona perche'  int 20h+dword occupa 6 bytes di spazio, che e' esattamente lo stesso dell'istruzione call dword ptr. In questo modo le chiamate sucessive sono piu' efficenti. Questo metodo ha pero' i suoi pro e contro. Di positivo c'e' che esso riduce il carico di lavoro di VMM e del VxD loader dato che essi non devono risolvere  TUTTE le chiamate al momento del caricamento. Le chiamate che non sono mai eseguite rimarranno immutate. Di non proprio positivo c'e' che esso rende il rilascio del VxD impossibile. Dal momento che VMM risolve le chiamate con gli indirizzi effettivi dei servizi del VxD, il VxD che fornisce questi servizi non puo' essere scaricato dalla memoria. Non esiste un meccanismo per il  ripristino dei collegamenti. Corollario di questo fatto e' che i VxD dinamici non sono indicati come fornitori di VxD services.

Callbacks: Le callbacks, o meglio le funzioni callback, sono funzioni del VxD che esistono per essere chiamate dagli altri VxDs. Non fate confusione fra callbacks e servizi. Le callabacks non sono pubbliche come i servizi. Esse sono funzioni private di cui un VxD fornisce, in specifiche situazioni, l'indirizzo ad un'altro VxD. Ad esempio, quando un VxD sta' servendo un interrupt hardware, il VxD non puo' chiamare alcun VxD services altrimenti, visto che VMM non e' rientrante,  potrebbe causare un page fault. Il VxD potrebbe invece passare l'indirizzo di una delle sue funzioni (la callback) alla VMM, cosicche' VMM possa chiamare la funzione quando e' in grado di tollerare i page faults. Il VxD puo' quindi continuare col suo lavoro mentre la sua funzione callback e' invocata. L'idea della callback non e' esclusiva dei VxD. Molte API di Windows le utilizzano allo stesso modo. Il miglior esempio e' forse la window procedure. Voi normalmente specificate l'indirizzo della window procedure nelle struttura WNDCLASS o WNDCLASSEX e passate questa a Windows con una chiamata a RegisterClass o RegisterClassEx. A questo punto, quando ci saranno messagi per la finestra, Windows chiamera' la vostra window procedure. Un'altro esempio e' una procedura di hook di Windows. La vostra applicazione passa a Windows l'indirizzo della procedura di hook, in tal modo Windows la chiamera' solo quando si sara' verificato un evento a cui l'applicazione e' interessata.
I tre metodi sopraelencati sono per la comunicazione fra VxDs. Esistono anche altre interfaccie per la modalita' V86, il modo protetto e per le applicazioni Win32.

Traduzione italiana a cura di: Kill3xx - kaneda27@hotmail.com

Torna alla main page! Torna alla pagina principale