|
Appunti informatica |
|
Visite: 1984 | Gradito: | [ Grande appunti ] |
Leggi anche appunti:Problemi di cooperazione nel modello a memoria comuneProblemi di cooperazione nel modello a memoria comune IV) Scrivere una applicazione Contenuto del floppy diskContenuto del floppy disk Il floppy disk allegato costituisce una raccolta di Gli operatoriGli operatori Come tutti i linguaggi di programmazione, il C dispone di un insieme |
Il presente capitolo non ha la pretesa di analizzare dal punto di vista tecnico il comportamento del DOS o delle funzioni di allocazione dinamica presenti nella libreria C: esso si propone, piuttosto, di fornire qualche spunto su particolarità non sempre evidenti . Alcuni cenni di carattere tecnico sono, tuttavia, indispensabili.
La libreria C comprende diverse funzioni dedicate all'allocazione dinamica della RAM: esse possono essere suddivise, forse un poco grossolanamente, in due gruppi.
Da un lato vi sono quelle che gestiscono la memoria secondo modalità, per così dire, tipiche della libreria C: malloc() realloc() free() e, in sostanza, tutte le funzioni dichiarate nel file ALLOC.H (o MALLOC.H : se ne parla a pagina
Dall'altro lato troviamo le funzioni basate sui servizi di allocazione della memoria resi disponibili dall'int 21h allocmem() setblock() e freemem(), dichiarate in DOS.H. Ecco la descrizione dei servizi testè citati:
Int 21h, serv. 48h: Alloca un blocco memoria
Input |
AH BX |
48h Numero di paragrafi da allocare |
Output |
AH |
Indirizzo di segmento dell'area allocata, oppure il codice di errore se CarryFlag = 1. In questo caso BX contiene il massimo numero di paragrafi disponibili per l'allocazione. |
Note |
|
Se la funzione è eseguita con successo, AX:0000 punta all'area allocata. Invocare la funzione con BX = FFFFh è un metodo per conoscere la quantità di memoria libera. |
Int 21h, serv. 49h: Dealloca un blocco di memoria
Input |
AH ES |
49h Segmento dell'indirizzo dell'area da liberare |
Output |
AX |
Codice di errore, se il CarryFlag |
Note |
|
Questo servizio restituisce al DOS un'area allocata mediante il servizio 48h. ES contiene il valore da questo restituito in AX |
Int 21h, serv. 4Ah: Modifica l'ampiezza del blocco di memoria allocato
Input |
AH BX ES |
4Ah Nuova dimensione in paragrafi del blocco Segmento dell'indirizzo del blocco da modifcare |
Output |
AX |
Codice di errore, se CarryFlag . In questo caso, se il blocco doveva essere espanso, BX contiene il massimo numero di paragrafi disponibili. |
Note |
|
Questa funzione è utilizzata per espandere o contrarre un blocco precedentemente allocato via servizio 48h. |
E' evidente che un programma il quale intenda interagire con il DOS nell'allocazione della RAM deve necessariamente utilizzare le funzioni appartenenti al secondo gruppo oppure ricorrere direttamente all'int 21h.
La memoria convenzionale è costituita dai primi 640 Kb (o meno di 640) di RAM installati sulla macchina: essi sono compresi tra gli indirizzi e 9FFF:000F . Essi sono l'unica parte di RAM che il DOS è in grado di utilizzare senza artifici per l'esecuzione di se stesso e dei programmi applicativi. L'uso della memoria convenzionale è descritto graficamente in figura
Il primo Kilobyte, dall'indirizzo (origine) a 003F:000F, è occupato dalla tavola dei vettori (vedere pag. ). I successivi 256 byte costituiscono un'area a disposizione del BIOS per la gestione di dati come il modo video attuale, il timer, etc.; essi sono seguiti da un'area di 512 byte usata in modo analogo dal DOS. A è caricato, in fase di bootstrap , il primo dei due file nascosti di sistema, di solito chiamato, a seconda della versione di DOS e del suo produttore, IBMBIO.COM o MSDOS.SYS . Tutti i restanti oggetti evidenziati in figura (a partire dal secondo file nascosto, IBMDOS.COM o IO.SYS ) sono caricati ad indirizzi variabili che dipendono dalla versione del sistema operativo e dalla configurazione della macchina (dal tipo e dal numero di device driver caricati, per fare un esempio). Il confine tra le aree 'Programmi Applicativi' e 'COMMAND.COM : parte transiente' è tratteggiato, in quanto la parte transiente dell'interprete dei comandi può essere sovrascritta in qualsiasi momento dai programmi di volta in volta eseguiti: essa è caricata nella parte alta della memoria convenzionale, ma lo spazio occupato non viene considerato un'area protetta . L'allocazione della memoria per tutti gli oggetti caricati in RAM successivamente a IO.SYS (o IBMDOS.COM) è gestita mediante i Memory Control Block (MCB): ciascuno di essi contiene le informazioni necessarie alla gestione dell'area di memoria della quale costituisce l'intestazione
Fig. : Utilizzo, da parte del DOS, della memoria convenzionale. |
Facciamo un esempio: dopo il bootstrap vi è una porzione (solitamente ampia) di RAM libera, disponibile per l'esecuzione dei programmi. In testa a tale area vi è un MCB. Quando viene caricato ed eseguito un programma, il DOS gli assegna due aree: la prima, di norma piccola, contiene una copia delle variabili d'ambiente (l'environment); la seconda è riservata al programma stesso. L'area libera è stata così suddivisa in tre parti: le prime due appartengono al programma, mentre la terza è libera. Ciascuna delle tre ha il proprio MCB: da ogni MCB è possibile risalire al successivo, ricostruendo così tutta la catena (e quindi la mappa dell'utilizzo della RAM). Supponiamo che il programma non utilizzi il proprio environment, e quindi restituisca al DOS la memoria da quello occupata: le aree sono sempre tre, ma solo la seconda è allocata, mentre la prima e la terza sono libere. Quando, infine, il programma termina, la RAM da esso occupata torna ad essere libera: per evitare inutili frazionamenti della RAM il DOS riunisce tutte le aree libere contigue. Si ritorna perciò alla situazione di partenza: un'unica area, libera, con un unico MCB.
A questo punto è indispensabile analizzare gli MCB con maggiore dettaglio: la figura ne descrive la struttura.
Come si vede, ciascuno di essi ha ampiezza pari a un paragrafo (16 byte) ed è suddiviso in campi.
Fig. : La struttura dei Memory Control Block. |
Il campo POS, di un solo byte, indica la posizione del MCB: se questo è l'ultimo (l'area di RAM che esso controlla è quella che inizia al maggiore indirizzo) il campo contiene il carattere 'Z', altrimenti il carattere 'M
Il campo PSP, una word (unsigned int, dal punto di vista del C), indica l'indirizzo di segmento del Program Segment Prefix del programma a cui appartiene l'area di memoria . Nell'esempio precedente, i campi PSP del MCB del programma e del suo environment hanno il medesimo contenuto. Il campo PSP del MCB di un'area libera assume valore zero. I valori e indicano che l'area è riservata al DOS; in particolare, è il valore che assume il campo PSP del MCB dell'area allocata ai device driver. Detto MCB è, tra l'altro, il primo della catena
Il campo DIM, una word, esprime la dimensione, in paragrafi, dell'area di memoria (escluso il MCB medesimo). Incrementando di uno la somma tra l'indirizzo di segmento di un MCB e il suo campo DIM si ottiene l'indirizzo di segmento del successivo MCB. Se il calcolo è effettuato con riferimento all'ultimo MCB della catena, il valore ottenuto è il cosiddetto Top Of Memory (A000h nel caso di 640 Kb installati)
I 3 byte del campo RESERVED attualmente non sono utilizzati.
Il campo NAME, di 8 byte, a partire dal DOS 4.0 contiene il nome del programma a cui l'area è assegnata (se questa contiene il Program Segment Prefix del programma: rifacendosi ancora all'esempio riportato, il nome non appare nel MCB dell'environment). Se il nome non occupa tutti gli 8 byte disponibili, quelli restanti sono riempiti con spazi (ASCII , esadecimale ). Se l'area è riservata al DOS, il nome, quando presente, è solitamente una stringa significativa per il solo DOS, e il carattere tappo può essere l'ASCII FFh
Qui giunti, conosciamo quanto basta (con un po' di ottimismo e di fortuna) per lavorare alla pari con il DOS. Si tratta, ora, di illustrare alcuni metodi (gran parte dei quali non documentati ufficialmente) per individuare gli indirizzi degli oggetti di cui abbiamo discusso.
Cominciamo dal secondo file nascosto. Il servizio 34h dell'int 21h restituisce l'indirizzo, nel segmento di memoria allocato al DOS, dell'InDOS flag
Int 21h, serv. 34h: Indirizzo dell'InDOS flag
Input |
AH |
34h |
Output |
ES:BX |
Indirizzo (seg:off) dell'InDOS flag |
La parte segmento dell'indirizzo dell'InDOS flag (restituita in ES) è l'indirizzo di segmento al quale è caricato il secondo file nascosto; esso si trova, in altri termini, a ES:0000. La funzione getdosseg() è un esempio di come è possibile procedere per ottenere detto indirizzo.
BARNINGA_Z! - 1991
DOSSEG.C - getdosseg()
unsigned cdecl getdosseg(void);
Restituisce: l'indirizzo di segmento di IO.SYS o IBMDOS.COM
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx dosseg.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#include <dos.h>
unsigned cdecl getdosseg(void)
Circa la parola chiave cdecl vedere pag. . Una versione più efficiente della getdosseg() è listata di seguito:
BARNINGA_Z! - 1991
DOSSEG.C - getdosseg()
unsigned cdecl getdosseg(void);
Restituisce: l'indirizzo di segmento di IO.SYS o IBMDOS.COM
COMPILABILE CON BORLAND C++ 2.0
bcc -O -d -c -k- -mx dosaddr.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#pragma inline
#pragma option -k- // per maggiore efficienza
unsigned cdecl getdosseg(void)
Passiamo ai Memory Control Block. Come si è detto, la prima area gestita tramite MCB è quella dei device driver. Sfortunatamente, anche in questo caso non esistono metodi ufficiali per conoscerne l'indirizzo. Esiste, però, un servizio non documentato dell'int 21h, la funzione 52h, detto 'GetDosListOfLists ', che restituisce l'indirizzo di una tavola di parametri ad uso interno del sistema. La word che precede questa tavola è l'indirizzo (segmento) del primo MCB.
Int 21h, serv. 52h: Indirizzo della lista delle liste
Input |
AH |
52h |
Output |
ES:BX |
Indirizzo (segmento:offset) della lista delle liste. |
Di seguito riportiamo un esempio di funzione che restituisce l'indirizzo di segmento del primo MCB.
BARNINGA_Z! - 1991
FIRSTMCB.C - getfirstmcb()
unsigned cdecl getfirstmcb(void);
Restituisce: l'indirizzo di segmento del primo MCB
COMPILABILE CON BORLAND C++ 2.0
bcc -O -d -c -mx firstmcb.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#include <dos.h>
unsigned cdecl getfirstmcb(void)
La macro MK_FP() è descritta a pag. . Anche in questo caso riportiamo la versione basata sullo inline assembly:
BARNINGA_Z! - 1991
FIRSTMCB.C - getfirstmcb()
unsigned cdecl getfirstmcb(void);
Restituisce: l'indirizzo di segmento del primo MCB
COMPILABILE CON BORLAND C++ 2.0
bcc -O -d -c -k- -mx firstmcb.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#pragma inline
#pragma option -k- // per maggiore efficienza
unsigned cdecl getfirstmcb(void)
A scopo di chiarezza, ripetiamo che getfirstmcb() non restituisce l'indirizzo della prima area di RAM controllata da MCB, bensì quello del primo MCB. L'indirizzo (di segmento) dell'area si ottiene, ovviamente, sommando uno al valore restituito.
Ora che sappiamo dove trovarli, i MCB possono essere comodamente manipolati con l'aiuto di una struttura:
struct MCB
Attenzione: il campo name della struttura di tipo MCB non è una vera e propria stringa, in quanto privo del NULL finale. Ecco, ora, il listato di una funzione in grado di copiare un Memory Control Block in un buffer appositamente predisposto.
BARNINGA_Z! - 1991
PARSEMCB.C - parsemcb()
unsigned cdecl parsemcb(struct MCB *mcb,unsigned mcbseg);
struct MCB *mcb; puntatore ad una struttura di tipo MCB: deve
essere gia' allocata
unsigned ncbseg; indirizzo (segmento) del MCB da copiare
Restituisce: l'indirizzo (segmento) dell'area di RAM controllata dal
MCB dopo avere copiato il contenuto del MCB nella
struttura mcb.
COMPILABILE CON BORLAND C++ 2.0
bcc -O -d -c -mx parsemcb.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#pragma inline
#include <dos.h>
unsigned cdecl parsemcb(struct MCB *mcb,unsigned mcbseg)
Lo inline assembly rende il codice compatto e veloce ; la funzione potrebbe comunque essere realizzata facilmente in C puro. La parsemcb() copia i dati di un MCB in una struttura di template MCB e restituisce l'indirizzo del Memory Control Block incrementato di uno, cioè l'indirizzo (segmento) dell'area di memoria controllata da quel MCB.
Abbiamo tutto ciò che occorre per ricostruire la mappa della memoria convenzionale: basta collegare i vari frammenti in modo opportuno.
BARNINGA_Z! - 1991
MCBCHAIN.C - getmcbchain()
struct MCB * cdecl getmcbchain(unsigned basemcb);
unsigned basemcb; indirizzo (segmento) del primo MCB della catena.
Restituisce: un puntatore ad un array di strutture di tipo MCB.
COMPILABILE CON BORLAND C++ 2.0
bcc -O -d -c -mx mcbchain.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#include <stdio.h> // per NULL
#include <alloc.h> // per malloc() e realloc()
struct MCB * cdecl getmcbchain(unsigned basemcb)
while(mcb[i++].pos != 'Z'); // i e' incrementata dopo il confronto
return(mcb);
La getmcbchain() prende come parametro l'indirizzo di segmento del primo MCB della catena, facilmente ottenibile mediante la getfirstmcb(): come si può vedere, nulla di complicato. Per avere una mappa completa della memoria convenzionale basta ricavare l'indirizzo del secondo file nascosto con una chiamata alla getdosseg(). La mappa può poi essere arricchita individuando la strategia utilizzata dal DOS nell'allocazione della memoria, cioè l'algoritmo con il quale il DOS ricerca un blocco libero di dimensioni sufficienti. Le strategie possibili sono tre: la prima, detta FirstFit , consiste nel ricercare il blocco a partire dall'origine della RAM; la seconda, BestFit, nell'allocare il blocco nella minore area libera disponibile; la terza, LastFit, si esplica nell'allocare la parte alta dell'ultimo blocco libero. La strategia di allocazione è gestita dal servizio 58h dell'int 21h.
Int 21h, serv. 58h: Gestione della strategia di allocazione
Input |
AH AL BX |
58h 00h: ottiene la
strategia di allocazione solo per AL 01h (set strategy): : FirstFit : BestFit >= : LastFit |
Output |
AX |
codice di errore se CarryFlag ; altrimenti: solo per AL 00h (get strategy): : FirstFit : BestFit >= : LastFit |
BARNINGA_Z! - 1992
ALCSTRAT.C - getallocstrategy()
int cdecl getallocstrategy(void);
Restituisce: la strategia di allocazione DOS. In caso di errore
restituisce -1.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx alcstrat.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#pragma inline
#pragma option -k- // maggiore efficienza
int cdecl getallocstrategy(void)
Per un esempio di modifica della strategia DOS di allocazione vedere pag.
Il metodo di puntamento basato sulla coppia segmento:offset consente al DOS di indirizzare, su macchine a 16 bit, un megabyte di RAM . I 384 Kb compresi tra i primi 640 Kb e il Mb sono, di norma, utilizzati come indirizzi per il ROM‑BIOS o sue estensioni, per la gestione del video, etc.: uno schema è riprodotto in figura
Fig. : Utilizzo degli indirizzi di memoria tra i 640 Kb e il megabyte. |
Gli indirizzi compresi tra C000:0 e EFFF:000F sono disponibili per le estensioni ROM‑BIOS: possono, cioè, essere utilizzati dalle schede di supporto per il networking o per particolari periferiche (fax, scanner, etc.). Ad esempio, l'intervallo che si estende da da C000:0 a C7FF:000F è di norma occupato dal BIOS delle schede VGA; inoltre molti calcolatori tipo notebook o laptop dispongono di estensioni ROM‑BIOS, spesso dedicate al controllo del video LCD, nel range da E000:0 a EFFF:000F
Gli indirizzi non occupati possono essere impiegati per simulare l'esistenza di aree di memoria che il DOS gestisce in modo analogo a quelle presenti nella memoria convenzionale: a tal fine è indispensabile un driver in grado di rimappare gli indirizzi tra A000:0 e FFFF:000F alla memoria fisicamente presente, in quanto a detti indirizzi non corrisponde RAM installata. Tali driver utilizzano, per effettuare il remapping, memoria espansa: ne consegue la necessità che sulla macchina ne sia installata una quantità sufficiente (almeno pari all'ampiezza totale delle aree da simulare) . Il DOS, a partire dalla versione 5.0, include il software necessario alla gestione, su macchine 80386 e superiori, di aree di RAM tra i 640 Kb e il Mb, dette Upper Memory Block. Inoltre, sono reperibili in commercio diversi prodotti mediante i quali è possibile ottenere prestazioni analoghe o migliori (per efficienza e flessibilità) sia su macchine 80386/80486 che 80286, con o senza DOS 5.0.
La tecnica di gestione degli Upper Memory Block (UMB), analogamente a quanto avviene per la memoria convenzionale, si basa sui Memory Control Block ; sfortunatamente, i driver DOS e quelli di produttori indipendenti definiscono le aree e comunicano con il sistema (programmi, etc.) mediante tecniche differenti: insomma, il caos regna sovrano. Gli esempi che seguono intendono fornire gli elementi minimi necessari a ricavare una mappa della Upper Memory: essi vanno comunque 'presi con le pinze', dal momento che si basano esclusivamente sui risultati di una ostinata sperimentazione.
Il DOS 5.0 crea (tramite HIMEM.SYS e EMM386.EXE ) la catena di MCB per gli Upper Memory Block restringendo l'ultima area di memoria convenzionale di 16 byte, nei quali definisce il primo MCB della nuova catena: nel caso di 640 Kb di RAM convenzionale, esso si trova a 9FFF:0. Il suo campo POS contiene il carattere 'M'; il campo PSP è valorizzato a ; il campo NAME contiene la stringa 'SC', seguita da sei NULL. Sommando il valore contenuto nel campo DIM all'indirizzo del MCB si ottiene la parte segmento dell'indirizzo di un successivo MCB , il cui campo NAME contiene la stringa 'UMB' seguita da 5 blanks. Il campo PSP è pari all'indirizzo del MCB stesso, incrementato di uno; il campo DIM esprime la dimensione, in paragrafi, dell'Upper Memory Block. All'interno di questo vi sono i MCB necessari per la definizione delle aree allocate e libere . Il campo POS vale 'Z' se vi è questo UMB soltanto, 'M' altrimenti: in questo caso, sommando il campo DIM incrementato di uno all'indirizzo dell'attuale UMB si ottiene l'indirizzo del successivo MCB. Questo, a sua volta, potrebbe avere lo scopo di 'proteggere' un'area non rimappabile . La catena continua sino ad esaurimento degli indirizzi liberi.
Basandosi su queste caratteristiche (lo ripetiamo: individuate empiricamente) è possibile realizzare una funzione in grado di determinare se nel sistema è disponibile Upper Memory gestita dal DOS 5.0:
BARNINGA_Z! - 1991
UMBDOS.C - testforDOS()
unsigned cdecl testforDOS(void);
Restituisce: l'indirizzo (segmento) del MCB corrispondente al primo
UMB (area di RAM sopra i 640 Kb). Restituisce NULL se
non riesce ad individuare la catena di UMB.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx umbdos.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#include <stdio.h> // per NULL
#include <string.h> // per strncmp()
unsigned cdecl testforDOS(void)
return(NULL);
La testforDOS() ipotizza che gli utlimi 16 byte di memoria convenzionale siano un MCB: se il presunto campo POS vale 'M' e il presunto PSP , allora il controllo prosegue secondo le linee indicate in precedenza. Se i 16 byte all'indirizzo segbase+umb.dim sono un MCB il cui campo PSP è pari al proprio indirizzo incrementato di uno e il cui campo NAME contiene 'UMB ', allora testforDOS() presume che gli ultimi 16 byte di memoria convenzionale siano realmente il primo MCB della catena che gestisce gli Upper Memory Block e ne restituisce l'indirizzo, altrimenti restituisce NULL (a significare che non vi è Upper Memory disponibile, o non è stato possibile individuarne la presenza). La mappa della Upper Memory può essere ottenuta semplicemente passando alla getmcbchain() di pag. proprio l'indirizzo restituito dalla testforDOS()
Per quanto riguarda i driver reperibili in commercio (non facenti parte del pacchetto DOS) l'esempio che segue fa riferimento al QEMM386.SYS , prodotto dalla Quarterdeck Office Systems, il quale, su macchine dotate di processore 80386 o superiore, fornisce supporto per la memoria estesa ed espansa, nonché per gli Upper Memory Block. Il metodo utilizzato per la loro gestione differisce significativamente da quello implementato dal DOS. In primo luogo, non esistono UMB contenitori di aree di RAM: ogni UMB rappresenta un'area a se stante, dotata di un proprio MCB, in modo del tutto analogo alle aree definite entro i primi 640 Kb. Inoltre, il primo UMB si trova al primo indirizzo disponibile sopra la memoria convezionale (e non negli ultimi 16 byte di questa); gli indirizzi non disponibili sono protetti con un UMB il cui MCB presenta nel campo PSP il memdesimo valore del campo PSP del MCB dell'area allocata, tra i device driver, a QEMM386.SYS . Questo, infine, incorpora un gestore per l'interrupt 2Fh, tramite il quale comunica con il sistema. Invocando l'int 2Fh dopo avere caricato con opportuni valori i registri è possibile desumere, dai valori in essi restituiti, se QEMM386.SYS è attivo e qual è l'indirizzo del primo UMB.
BARNINGA_Z! - 1991
UMBQEMM.C - testforQEMM386()
unsigned cdecl testforQEMM386(void);
Restituisce: l'indirizzo (segmento) del MCB corrispondente al primo
UMB (area di RAM sopra i 640 Kb). Restituisce NULL se
non riesce ad individuare la catena di UMB.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx umbqemm.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#pragma inline
#pragma option -k- // non indispensabile, ma aggiunge efficienza
#include <stdio.h> // per NULL
#include <string.h> // per strncmp()
unsigned cdecl testforQEMM386(void)
NOTQEMM386:
asm xor cx,cx;
EXITFUNC:
return(_CX);
La testforQEMM386() invoca due volte l'int 2Fh. Nella prima richiede il servizio 0 (AL ; può essere una richiesta di conferma dell'avvenuta installazione): AH BX CX e DX contengono, presumibilmente, valori aventi funzione di 'parola d'ordine' (vedere, per alcuni dettagli circa l'int 2Fh, pag. ). Se QEMM386.SYS è installato ed attivo AX BX CX e DX contengono valori prestabiliti, controllati dalla funzione. La seconda chiamata all'int 2Fh richiede il servizio 1 (AL ; appare essere la richiesta dell'indirizzo del primo MCB per UMB): anche in questo caso AH BX CX e DX sono caricati con valori costanti. QEMM386.SYS restituisce in AX e BX ancora valori prestabiliti, ed in CX la parte segmento dell'indirizzo del primo MCB di controllo per gli Upper Memory Block. La testforQEMM386() restituisce NULL se non ha individuato la presenza di QEMM386.SYS. Anche in questo caso il valore restituito, se diverso da NULL, può essere parametro attuale della getmcbchain() per ricavare la mappa della Upper Memory.
Gli UMB (pag. ) sono definiti dalla XMS (eXtended Memory Services) Specification, a cui si rimanda per la descrizione dei servizi che consentono la loro allocazione e deallocazione (pag.
Fig. : Schema di mapping EMM
tra pagine fisiche (EMM page frame) e pagine logiche. La figura ipotizza
che la |
La memoria espansa consiste in pagine di RAM che possono essere copiate dentro e fuori lo spazio fisico di indirizzamento oltre il limite dei 640 Kb. E', in sostanza, un metodo per superare il limite dei 640 Kb tramite un driver in grado di gestire lo scambio di dati tra la RAM direttamente indirizzabile dal DOS e la memoria presente oltre il primo megabyte, attraverso la page frame, un'area definita entro il primo Mb stesso (memoria convenzionale o upper memory) e utilizzata come buffer di 'transito'. Secondo le specifiche LIM 4.0 la page frame ha dimensione pari a 64 Kb (4 pagine fisiche), e può essere utilizzata per gestire fino a 32 Mb di RAM, suddivisa in pagine logiche (in genere di 16 Kb). Ad ogni gruppo di pagine logiche è associato uno handle (concetto analogo ma non coincidente con quello di pag. ), che lo identifica in modo univoco: il driver si occupa di creare una corrispondenza trasparente tra pagine fisiche e pagine logiche associate ad un certo handle (figura ); il funzionamento del meccanismo sarà sperimentato nel corso del paragrafo. Su macchine 80386 o superiori la memoria espansa può essere emulata (mediante appositi driver) utilizzando la memoria estesa (vedere pag.
Il driver che gestisce la memoria espansa (detta memoria EMS) installa un proprio gestore dell'int 67h e rende disponibile un device che ha il nome convenzionale EMMXXXX0 . Esso è detto EMM (EMS Manager). Di seguito sono presentati i listati di alcune funzioni basate sui servizi dell'int 67h.
Prima di effettuare qualsiasi operazione mediante il driver EMS si deve stabilire se esso è effettivamente installato. Il metodo raccomandato dalle specifiche LIM consiste nel tentare l'apertura del device EMMXXXX0
BARNINGA_Z! - 1991
EMMTEST.C - testEMM()
int cdecl testEMM(void);
Restituisce: 1 se il driver EMM e' installato
0 altrimenti
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx emmtest.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#pragma warn -pia
#include <stdio.h> // per NULL
#include <dos.h>
#include <io.h>
#include <fcntl.h>
int cdecl testEMM(void) /* uso della memoria espansa */
close(handle);
return(retcode);
La testEMM() apre il device EMMXXXX0 (nome di default dell'Expanded Memory Manager) e, mediante la subfunzione 0 del servizio 44h dell'int 21h controlla che esso sia effettivamente un device (bit 7 di DX ) e non un file . Tramite la subfunzione 7 del medesimo servizio, getEMMusage() controlla che il device sia in stato di ready (AL FFh); in caso affermativo può essere comunicato (retcode = 1) alla funzione chiamante che il driver è installato e pronto ad eseguire i servizi richiesti via int 67h.
E' possibile utilizzare un metodo alternativo per controllare la presenza del driver EMM, basato sulle specifiche Microsoft per la struttura dei device driver (vedere pag. ). Vediamo una seconda versione di testEMM(), più snella della precedente
#include <dos.h>
#include <string.h>
#define EMMNAME 'EMMXXXX0'
int cdecl testEMM2(void)
La testEMM2() costruisce un puntatore far a carattere la cui parte segmento è data dalla parte segmento del vettore dell'int 67h (ottenuto tramite il servizio 35h dell'int 21h) e la cui parte offset equivale a 10 (ad offset 10 del blocco di memoria allocato ad un device driver si trova il nome dello stesso); la funzione _fstrncmp() è utilizzata per vedere se la sequenza di caratteri che si trova a quell'indirizzo è proprio EMMXXXX0. Se testEMM2() è compilata con uno dei modelli di memoria tiny, small o medium (pag. ) l'indirizzo della costante manifesta EMMNAME near, ma il compilatore provvede, in base al prototipo di _fstrncmp() dichiarato in STRING.H, a convertirlo opportunamente in puntatore far. L'uso di _fstrncmp() in luogo di _fstrcmp() è imposto dal fatto che il nome dei device driver non è gestito, nell'area di RAM loro allocata, come una stringa C (in altre parole, non è seguito dal NULL). La _fstrncmp() restituisce se le stringhe confrontate sono uguali, cioè nel caso in cui il driver sia installato: l'operatore di not logico '; vedere pag. ) 'capovolge' il risultato, perciò anche questa versione di testEMM() restituisce un valore non nullo se il driver è presente e se non lo è (circa MK_FP() vedere pag.
La versione del gestore EMM installato è ottenibile via int 67h, servizio 46h, sul quale si basa la getEMMversion()
Int 67h, serv. 46h: Versione EMM
Input |
AH |
46h |
Output |
AH AL |
Stato dell'operazione (errore se ; vedere pag. Versione e revisione del gestore EMM. La versione è nei bit 4‑7, la revisione nei bit 0-3. |
BARNINGA_Z! - 1991
EMMVER.C - getEMMversion()
unsigned cdecl getEMMversion(void);
Restituisce: la versione del driver EMM (versione nel byte meno
significativo, revisione nel byte piu' significativo:
4.0 e' restituito come 0x0004).
Se si e' verificato un errore restituisce un numero
negativo (il codice d'errore EMS cambiato di segno).
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- emmver.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#pragma inline
#pragma option -k-
unsigned cdecl getEMMversion(void) /* ottiene la versione LIM */
SETVER:
asm
EXITFUNC:
return(_AX);
La getEMMversion() restituisce un unsigned integer, il cui byte meno significativo rappresenta la versione, e quello più significativo la revisione del gestore EMM . In caso di errore è restituito un valore negativo (il codice di errore cambiato di segno).
La funzione che segue, getEMMframeAddr(), restituisce l'indirizzo della page frame , ottenuto invocando servizio 41h dell'int 67h.
Int 67h, serv. 41h: Indirizzo della page frame
Input |
AH |
41h |
Output |
AH BX |
Stato dell'operazione (errore se ; pag. Indirizzo (segmento) della page frame. |
BARNINGA_Z! - 1991
EMMFRAME.C - getEMMframeAddr()
unsigned cdecl getEMMframeAddr(void);
Restituisce: l'indirizzo (segmento) della Page Frame.
Se si e' verificato un errore restituisce un numero
negativo (il codice d'errore EMS cambiato di segno).
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- emmframe.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#pragma inline
#pragma option -k- /* non indispensabile; accresce l'efficienza */
unsigned cdecl getEMMframeAddr(void)
EXITFUNC:
return(_BX);
Ancora, attraverso i servizi dell'int 67h, è possibile conoscere lo stato della memoria espansa (numero di pagine totali e libere, stato degli handle , etc.).
Int 67h, serv. 42h: Numero di pagine
Input |
AH |
42h |
Output |
AH BX DX |
Stato dell'operazione (errore se ; pag. Numero di pagine non allocate. Numero totale di pagine EMS. |
BARNINGA_Z! - 1991
EMMTOTP.C - getEMMtotPages()
int cdecl getEMMtotPages(void);
Restituisce: il numero di pagine totali EMS
Se < 0 si e' verificato un errore; il valore cambiato di segno
e' il codice di errore EMS
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx emmtotp.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#include <dos.h>
int cdecl getEMMtotPages(void)
La getEMMtotPages() restituisce il numero di pagine logiche EMS disponibili nel sistema: se il valore restituito è negativo rappresenta, cambiato di segno, il codice di errore EMS (l'operazione non è stata eseguita correttamente); inoltre il dato restituito può evidenziare una quantità di memoria EMS maggiore della quantità di memoria fisica installata sulla macchina, quando sia attivo un ambiente in grado di creare memoria virtuale . Analoghe considerazioni valgono per la getEMMfreePages(), che restituisce il numero di pagine logiche EMS non ancora allocate (e quindi disponibili per i programmi).
BARNINGA_Z! - 1991
EMMFREEP.C - getEMMfreePages()
int cdecl getEMMfreePages(void);
Restituisce: il numero di pagine libere EMS
Se < 0 si e' verificato un errore; il valore cambiato di segno
e' il codice di errore EMS
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx emmfreep.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#include <dos.h>
int cdecl getEMMfreePages(void)
Int 67h, serv. 4Bh: Numero di handle EMM aperti
Input |
AH |
4Bh |
Output |
AH BX |
Stato dell'operazione (errore se != ; pag. Numero di handle aperti. |
Int 67h, serv. 4Dh: Pagine allocate agli handle
Input |
AH ES:DI |
4Dh Buffer costituito da tante coppie di word quanti sono gli handle aperti. |
Output |
AH BX |
Stato dell'operazione (errore se ; pag. Numero di handle attivi. Le words dell'array in ES:DI sono riempite, alternativamente, con un numero di handle e il numero delle pagine allocate a quello handle. |
BARNINGA_Z! - 1991
EMMOHNDL.C - getEMMopenHandles()
int cdecl getEMMopenHandles(void);
Restituisce: il numero di handles EMS aperti
Se < 0 si e' verificato un errore; il valore cambiato di segno
e' il codice di errore EMS
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx emmohndl.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#include <dos.h>
int cdecl getEMMopenHandles(void)
Anche la getEMMopenHandles() in caso di errore restituisce il codice d'errore EMS cambiato di segno (negativo); un valore maggiore o uguale a esprime invece il numero di handle EMS aperti, cioè utilizzati nel sistema. La getEMMpagesPerHandle() utilizza invece il servizio 4Dh dell'int 67h per conoscere il numero di pagine allocate a ciascuno handle aperto e memorizza i dati nell'array di strutture di tipo EMMhnd, il cui indirizzo le è passato come parametro.
struct EMMhnd ;
L'array di strutture deve essere allocato a cura del programmatore, ad esempio con una chiamata a malloc()
struct EMMhnd *emmHnd;
unsigned oHnd;
.
if((oHnd = getEMMopenHandles()) < 0)
else
if(!(emmHnd = (struct EMMhnd *)malloc(oHnd*sizeof(struct EMMhnd))))
In assenza di errori può essere invocata la getEMMpagesPerHandle(), listata di seguito, passandole come parametro il puntatore emmHnd
BARNINGA_Z! - 1991
EMMPPH.C - getEMMpagesPerHandle()
int cdecl getEMMpagesPerHandle(struct EMMhnd *emmHnd);
struct EMMhnd *emmHnd; puntatore ad array di strutture EMMhnd, gia' allocato
con tanti elementi quanti sono gli handles aperti.
Restituisce: >= 0 se l'operazione e' stata eseguita correttamente. Il valore
rappresenta il numero di handles EMS attivi.
Se < 0 si e' verificato un errore; il valore cambiato di segno
e' il codice di errore EMS
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx emmpph.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#pragma warn -pia
#include <dos.h>
#include <errno.h>
int cdecl getEMMpagesPerHandle(struct EMMhnd *emmHnd) // uso della memoria espansa
Circa l'uso di DS nella funzione vedere pag. ; le macro FP_SEG() e FP_OFF() sono descritte a pag.
Ad ogni handle può essere associato un nome, mediante la subfunzione 1 del servizio 53h dell'int 67h. I nomi associati agli handle aperti possono essere conosciuti tramite la subfunzione 0 del medesimo servizio.
Int 67h, serv. 53h: Nome dello handle EMS
Input |
AH AL |
53h 00h: richiede il nome di uno handle DX = Numero dello handle EMM. ES:DI = Indirizzo di un buffer ampio almeno 8 byte, in cui è copiato il nome dello handle. 01h: assegna il nome ad uno handle DX = Numero dello handle EMM DS:SI = Indirizzo di un buffer ampio almeno 8 byte, contenente il nome da assegnare allo handle (ASCIIZ |
Output |
AH |
Stato dell'operazione (errore se ; pag. |
BARNINGA_Z! - 1991
EMMGHNAM.C - getEMMhandleName()
int cdecl getEMMhandleName(unsigned handle,char *EMMhname);
int EMMhandle; handle EMM di cui si vuole conoscere il nome.
char *EMMhname; buffer di 9 bytes in cui memorizzare il nome.
Restituisce: lo stato dell'operazione. Se < 0, il valore, cambiato di segno,
e' il codice di errore EMS.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx emmghnam.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#pragma inline
#include <string.h>
int cdecl getEMMhandleName(int EMMhandle,char *EMMhname)
La getEMMhandleName() utilizza il servizio descritto (subfunzione 0) per conoscere il nome associato allo handle EMMhandle. Il buffer EMMhname deve comprendere 9 byte : nei primi 8 è copiato il nome, mentre l'ultimo è valorizzato a NULL per costruire una normale stringa (circa l'uso di DS vedere pag. ). In caso di errore è restituito un valore negativo che, cambiato di segno, rappresenta il codice dell'errore EMS: la funzione non potrebbe segnalare l'errore semplicemente copiando in EMMhname una stringa vuota, dal momento che questa è un nome valido. La differente gestione dei puntatori nei diversi modelli di memoria rende necessaria, come già in getEMMusage(), la compilazione condizionale delle istruzioni relative al caricamento dei registri ES:DI con l'indirizzo del buffer.
Del tutto analoga appare la setEMMhandleName(), che assegna un nome ad uno handle tramite la subfunzione 1 del solito int 67h, servizio 53h.
BARNINGA_Z! - 1991
EMMSHNAM.C - setEMMhandleName()
int cdecl setEMMhandleName(unsigned handle,char *EMMhname);
int EMMhandle; handle EMM a cui si vuole assegnare il nome.
char *EMMhname; buffer contenente il nome (stringa chiusa da NULL).
Restituisce: lo stato dell'operazione. Se < 0, il valore, cambiato di segno,
e' il codice di errore EMS. Se e' 0, l'operazione e' stata
eseguita correttamente.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx emmshnam.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#pragma inline
#include <string.h>
int cdecl setEMMhandleName(int EMMhandle,char *EMMhname)
Le funzioni sin qui presentate (eccetto setEMMhandleName()) consentono esclusivamente di analizzare l'attuale utilizzo della expanded memory: si tratta ora di definire (in breve!) un algoritmo di utilizzo della medesima, per dotarci degli strumenti che ci consentano di sfruttarla attivamente nei nostri programmi. In primo luogo è necessario allocare un certo numero di pagine logiche ad uno handle; in altre parole dobbiamo stabilire quanta memoria espansa ci occorre, tenendo presente che essa è solitamente allocata, per compatitbilità con le prime versioni delle specifiche LIM EMS, in blocchi multipli di 16 Kb (le pagine). Così, se dobbiamo disporre di 40 Kb di expanded memory, è indispensabile allocare 3 pagine logiche. Il driver EMM assegna al gruppo di pagine logiche allocate un numero identificativo (lo handle) al quale è necessario riferirsi per tutte le operazione successivamente effettuate su di esse.
Allocare pagine logiche significa destinarle ad uso esclusivo del programma. L'area di memoria espansa è quindi identificata da due 'coordinate': lo handle e il numero di pagina logica all'interno dell'area stessa . L'allocazione delle pagine logiche è effettuata dal servizio 43h dell'int 67h:
Int 67h, serv. 43h: Alloca pagine logiche nella memoria espansa
Input |
AH BX |
43h numero di pagine logiche che si desidera allocare |
Output |
AH DX |
Stato dell'operazione (errore se ; pag. Handle (se AX |
BARNINGA_Z! - 1991
EMMALLOC.C - allocEMMpages()
int cdecl allocEMMpages(int pages);
int pages; numero di pagine da allocare.
Restituisce: un intero. Se e' maggiore di zero e' lo handle associato
alle pagine allocate. Se e' = zero si e' verificato un errore
non identificato. Se < 0, il valore, cambiato di segno,
e' il codice di errore EMS.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx emmalloc.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#pragma inline
int cdecl allocEMMpages(int pages)
La allocEMMpages() restituisce lo handle associato alle pagine allocate: si tratta di un numero maggiore di . Se viene restituito un numero pari o inferiore a zero, si è verificato un errore e le pagine non sono state allocate: il valore zero non è un codice di errore vero e proprio, ma indica una situazione 'strana', in quanto lo handle è riservato al sistema operativo. Un valore minore di zero, cambiato di segno, rappresenta invece un normale codice di errore EMS. Una tabella dei codici di errore è presentata a pag.
Come utilizzare le pagine disponibili? Il driver è in grado di effettuare un mapping trasparente delle pagine logiche con le pagine fisiche. Ciò significa che tutte le operazioni effettuate su queste ultime (definite all'interno del primo Mb, dunque indirizzabili mediante comuni puntatori di tipo far o huge) vengono trasferite, senza che il programmatore debba preoccuparsene, sulle pagine logiche poste in corrispondenza (cioè mappate) con esse. Effettuare il mapping di una pagina logica con una pagina fisica significa, in pratica, porre la prima in corrispondenza biunivoca con la seconda: è il driver a riportare nella pagina logica tutte le modifiche apportate dal programma al contenuto della pagina fisica. Vediamo una possibile implementazione del servizio 44h dell'int 67h.
Int 67h, serv. 44h: Effettua il mapping di pagine logiche a pagine fisiche
Input |
AH AL BX DX |
44h numero della pagina fisica su cui mappare la pagina logica numero della pagina logica da mappare su quella fisica handle associato alla pagina logica (o al gruppo di pagine logiche di cui questa fa parte) |
Output |
AH |
Stato dell'operazione (errore se ; pag. |
BARNINGA_Z! - 1991
EMMPGMAP.C - mapEMMpages()
int cdecl mapEMMpages(int physicalPage,int logicalPage,int EMMhandle);
int physicalPage; numero della pag. fisica su cui mappare la pag. logica
int logicalPage; numero della pag. logica da mappare su quella fisica
int EMMhandle; handle associato alla pagina logica
Restituisce: 0 se l'operazione e' stata eseguita correttamente
Se < 0, il valore, cambiato di segno, e' il codice di errore EMS.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx emmpgmap.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#pragma inline
int cdecl mapEMMpages(int physicalPage,int logicalPage,int EMMhandle)
Va ancora precisato che una medesima pagina fisica può essere utilizzata, senza difficoltà operative, per effettuare il mapping di più pagine logiche, purché in momenti diversi. Per fare un esempio concreto, possiamo mappare alla pagina fisica la pagina logica associata ad un certo handle e memorizzare in quella pagina fisica i nostri dati, secondo necessità. Successivamente è possibile mappare alla stessa pagina fisica un'altra pagina logica, associata o no al medesimo handle. I dati che erano presenti nella pagina fisica non vengono persi, perché il driver, in modo trasparente, riflette le operazioni sulla pagina logica associata, e quindi essi sono memorizzati in quest'ultima. E' facile constatare che effettuando nuovamente il mapping della prima pagina logica dell'esempio alla solita pagina fisica ritroviamo in quest'ultima i dati originariamente memorizzati.
Confusi? Niente paura, un programmino ci aiuterà a capire tuttavia, dobbiamo ancora discutere un dettaglio: la deallocazione della memoria espansa. E' importante ricordare che i programmi, prima di terminare, devono sempre disallocare esplicitamente la memoria espansa allocata. Il DOS, infatti, non si immischia affatto nella gestione EMS (e, del resto, il driver EMS e il sistema operativo non interagiscono se non nella fase del caricamento del primo da parte del secondo durante bootstrap): tutta la memoria espansa allocata da un programma e dal medesimo non rilasciata è inutilizzabile per tutti gli altri programmi (e per il DOS medesimo) fino al successivo bootstrap della macchina.
Int 67h, serv. 45h: Dealloca le pagine associate ad uno handle
Input |
AH DX |
45h handle associato alla pagina logica (o gruppo di pagine logiche) da deallocare (sono sempre disallocate tutte le pagine logiche associate allo handle) |
Output |
AH |
Stato dell'operazione (errore se ; pag. |
BARNINGA_Z! - 1991
EMMFREEH.C - freeEMMhandle()
int cdecl freeEMMhandle(int EMMhandle);
int EMMhandle; handle associato alle pagine logiche da rilasciare.
Restituisce: 0 se l'operazione e' stata eseguita correttamente
Se < 0, il valore, cambiato di segno, e' il codice di errore EMS.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx emmfreeh.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#pragma inline
int cdecl freeEMMhandle(int EMMhandle)
Tutte la pagine associate allo handle passato a freeEMMhandle() sono disallocate e lo handle rilasciato: questo non è più utilizzabile per alcuna operazione EMS. Anche la freeEMMhandle() restituisce se l'operazione è stata eseguita correttamente e, in caso di errore, un valore minore di zero che, cambiato di segno, rappresenta il codice di errore.
E' tempo di affrontare la realtà: di seguito è listato il programma che utilizza alcune delle funzioni presentate sin qui per effettuare operazioni di allocazione, utilizzo e deallocazione della memoria espansa.
EMS.C - Barninga Z! - 1994
Programma di test per alcune funzioni di servizio EMS.
Il sorgente non include i listati delle funzioni.
Compilato con Borland C++ 3.1
bcc ems.c
#pragma warn -pia
#include <stdio.h>
#include <dos.h>
#include <string.h>
int allocEMMpages(int pages);
int freeEMMhandle(int EMMhandle);
unsigned getEMMframeAddr(void);
int getEMMhandleName(int EMMhandle,char *EMMhname);
int mapEMMpages(int physicalPage,int logicalPage,int EMMhandle);
int setEMMhandleName(int EMMhandle,char *EMMhname);
void main(void)
frameSeg = getEMMframeAddr();
physicalPage0 = (char far *)MK_FP(frameSeg,0);
printf('Page Frame Address: %Fpn',physicalPage0);
for(i = 0; i < 2; i++)
if(retcode = setEMMhandleName(EMMhandles[i],EMMhandleNames[i]))
}
if(i < 2)
for(i = 0; i < 2; i++)
if(retcode = getEMMhandleName(EMMhandles[i],strBuf))
printf('Error %X getting name of handle %d.n',-retcode,EMMhandles[i]);
else
printf('Handle %d name: %sn',EMMhandles[i],buffer);
if(retcode = mapEMMpages(0,0,EMMhandles[0]))
printf('Error %X mapping Log. page %d of handle %d to Phys. page %d.n',
-retcode,0,EMMhandles[0],0);
_fstrcpy(physicalPage0,'@@@ A String For Logical Page 0 @@@');
if(retcode = mapEMMpages(0,1,EMMhandles[1]))
printf('Error %X mapping Log. page %d of handle %d to Phys. page %d.n',
-retcode,1,EMMhandles[1],0);
_fstrcpy(physicalPage0,'XXXXXXXXXXXXXXXXXXXXX');
if(retcode = mapEMMpages(0,0,EMMhandles[0]))
printf('Error %X mapping Log. page %d of handle %d to Phys. page %d.n',
-retcode,0,EMMhandles[0],0);
_fstrcpy(strBuf,physicalPage0);
printf('Logical Page 0 content: %sn',strBuf);
for(i = 0; i < 2; i++)
if(retcode = freeEMMhandle(EMMhandles[i]))
printf('Error %X deallocating EMM pages of handle %d.n',-retcode,
EMMhandles[i]);
In testa al programma sono dichiarati i prototipi delle funzioni descritte in precedenza (si presume che esse si trovino, già compilate, in un object file o una libreria da specificare in fase di compilazione e linking). La prima operazione svolta da main() consiste nel chiamare la getEMMframeAddr() per conoscere l'indirizzo di segmento della page frame. Dato che le quattro pagine fisiche in cui essa è suddivisa sono numerate in ordine crescente a partire dalla sua 'origine', l'indirizzo della page frame coincide con quello della pagina fisica . Si tratta, ovviamente, di un indirizzo del tipo seg:off (vedere pag. ), che viene ottenuto 'accostando', mediante la macro MK_FP() definita in DOS.H (pag. ), un offset pari a zero al segmento restituito da getEMMframeAddr()
Il ciclo for successivo gestisce l'allocazione di due blocchi di memoria espansa, ciascuno costituito di pagine logiche; è infatti il parametro passato a allocEMMpages(), che, ad ogni chiamata, restituisce lo handle associato al singolo blocco di pagine logiche. Gli handle sono memorizzati negli elementi dell'array di interi EMMhandles. Se il valore restituito è minore o uguale a si è verificato un errore: viene visualizzato un apposito messaggio e il ciclo di allocazione è interrotto (break). Nello stesso ciclo ad ogni handle allocato è assegnato un nome, mediante la setEMMhandleName(): i nomi (normali stringhe) per gli handle sono contenuti nell'array EMMhandleNames; anche in questo caso la restituzione di un codice di errore determina l'interruzione del ciclo. Se in uscita dal ciclo i è minore di , significa che esso è stato interrotto (da un errore): il programma termina, ma prima vengono rilasciati gli handle eventualmente allocati (di fatto, se vi è stato un errore, gli handle allocati sono uno solo, o nessuno).
Il flusso elaborativo prosegue con un altro ciclo for, all'interno del quale sono visualizzati, ricavandoli tramite la getEMMhandleNames(), i nomi precedentemente assegnati agli handle; va inoltre precisato che non è affatto necessario assegnare un nome ad ogni handle per utilizzare le pagine logiche ad esso associate. Il nostro programma non perde alcuna delle sue (strabilianti) funzionalità, pur eliminando le chiamate a setEMMhandleNames() e getEMMhandleNames(). Va ancora precisato che una chiamata a getEMMhandleNames() per richiedere il nome di uno handle che ne è privo non determina un errore, ma semplicemente l'inizializzazione a stringa vuota del buffer il cui indirizzo le è passato come parametro (EMMhname
Siamo così giunti alla parte più interessante del listato, quella, cioè, in cui vengono finalmente effettuati il mapping delle pagine logiche ed il loro utilizzo effettivo.
La prima chiamata a mapEMMpages() mappa la pagina logica sulla pagina fisica . Da questo momento tutte le operazioni effettuate sulla pagina fisica (la prima delle quattro in cui è suddivisa la page frame) si riflettono automaticamente su quella pagina logica. Scopo del programma è proprio verificare quanto appena affermato: si tratta di scrivere qualcosa nella pagina fisica , mappare ad essa un'altra pagina logica, in cui scrivere altri dati, e poi mappare nuovamente la pagina logica attualmente associata, per verificare se i dati originariamente scritti sono ancora lì. In effetti, quelle descritte sono proprio le operazioni che il programma esegue.
La prima chiamata a _fstrcpy() copia nella pagina fisica la stringa '@@@ A String For Logical Page 0 @@@'. Immediatamente dopo, la seconda chiamata a mapEMMpages() mappa alla solita pagina fisica la seconda ( ) delle due ( e ) pagine logiche associate al secondo handle (EMMhandles[1]) e la _fstrcpy() scrive nella pagina fisica la stringa 'XXXXXXXXXXXXXXXXXXXXX', che viene in realtà scritta nella pagina logica appena mappata. Il dubbio che questa operazione di scrittura possa sovrascrivere la stringa precedente (copiata allo stesso indirizzo fisico, ma non allo stesso indirizzo logico) è fugato dalle operazioni immediatamente seguenti.
Il mapping effettuato dalla terza chiamata a mapEMMpages() associa nuovamente alla pagina fisica la prima pagina logica ( ) delle due ( e ) allocate al primo handle (EMMhandles[0]). Questa volta _fstrcpy() copia nel buffer strBuf, che è near, la stringa che si trova all'inizio della pagina fisica . L'operazione è necessaria perché printf(), nell'ipotesi di compilare il programma con modello di memoria tiny, small o medium (pag. ) non può accettare come parametro l'indirizzo far della page frame. Il momento della verità è giunto: printf() visualizza '@@@ A String For Logical Page 0 @@@', proprio come previsto.
Il programma si chiude con un ciclo che chiama freeEMMhandle(), per rilasciare tutta la memoria espansa allocata, al fine di renderla nuovamente disponibile ai processi eseguiti successivamente.
Non crediate di cavarvela così facilmente: i guai, con la memoria EMS, non finiscono qui. Il programma presentato poco fa lavora nella presunzione, peraltro fondata, che nessuno gli mescoli le carte in tavola: in altre parole assume che la situazione di mapping non venga modificata da eventi esterni. In realtà ciò può avvenire in almeno due situazioni: se il programma lancia un'altra applicazione che utilizza memoria EMS oppure se è un TSR (pag. ) a farne un uso poco corretto . Va da sé che nel caso di un TSR o un device driver 'maleducato' c'è poco da fare, ma se sappiamo in partenza che un evento generato dal nostro programma può modificare la mappatura delle pagine EMS è meglio correre ai ripari, salvando lo stato della mappatura delle pagine logiche su quelle fisiche (page map ). Niente di tremendo: l'int 67h mette a disposizione un servizio progettato proprio per questo.
Int 67h, serv. 4Eh: Salva e ripristina la page map
Input |
AH AL |
4Eh 00h salva la page map ES:DI = punta a un buffer in cui salvare la page map 01h rispristina la page map DS:SI = punta a un buffer da cui caricare la page map 02h salva page map e ne carica una salvata precedentemente DS:SI = punta a un buffer da cui caricare la page map ES:DI = punta a un buffer in cui salvare la page map 03h restituisce la dimensione del buffer per la page map |
Output |
AH AL |
Stato dell'operazione (errore se ; pag. Solo per subfunzione 03h: dimensione in byte del buffer necessario a contenere la page map |
Ecco alcuni esempi di funzioni basate sul servizio 4Eh dell'int 67h:
BARNINGA_Z! - 1991
EMMGPMD.C - getEMMpageMapDim()
int cdecl getEMMpageMapDim(void);
Restituisce: Se > 0 è la dimensione in bytes dell'array necessario a
contenere la page map
Se < 0, il valore, cambiato di segno, e' il codice di errore EMS.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx emmgpmd.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#pragma inline
int cdecl getEMMpageMapDim(void)
La getEMMpageMapDim() deve essere chiamata per conoscere la dimensione del buffer che deve contenere la page map; questo può essere allocato, ad esempio, tramite malloc(), prima di chiamare la saveEMMpageMap(), listata di seguito (circa l'uso di DS vedere pag.
BARNINGA_Z! - 1991
EMMSPM.C - saveEMMpageMap(unsigned char *destBuf)
int cdecl saveEMMpageMap(unsigend char *destBuf);
unsigned char *destBuf; buffer che conterra' la page map.
Restituisce: Se 0 l'operazione è stata eseguita correttamente
Se < 0, il valore, cambiato di segno, e' il codice di errore EMS.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx emmspm.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#pragma inline
int cdecl saveEMMpageMap(unsigned char *destBuf)
Dopo avere salvato la page map il programma può lanciare il child process , senza preoccuparsi di come esso utilizza la memoria EMS. Al rientro è sufficiente chiamare la restoreEMMpageMap() per riportare lo stato del driver alla situazione salvata.
BARNINGA_Z! - 1991
EMMRPM.C - restoreEMMpageMap(unsigned char *sourceBuf)
int cdecl saveEMMpageMap(unsigned char *sourceBuf);
unsigned char *sourceBuf; buffer che contiene la page map da riattivare.
Restituisce: Se 0 l'operazione è stata eseguita correttamente
Se < 0, il valore, cambiato di segno, e' il codice di errore EMS.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx emmrpm.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
#pragma inline
int cdecl restoreEMMpageMap(unsigned char *sourceBuf)
Per un esempio di funzione basata sulla subfunzione 02h, vedere pag.
Con la versione 4.0 dello standard LIM sono state introdotte interessanti funzionalità di trasferimento dati tra memoria espansa e aree di memoria convenzionale, rendendo così possibile 'scavalcare' la page frame. Il servizio che implementa tale funzionalità è il 57h; la subfunzione 00h consente di trasferire dati direttamente dalla memoria convenzionale alla memoria EMS e richiede che i registri DS:SI siano caricati con l'indirizzo di un buffer contenente le informazioni necessarie per effettuare il trasferimento. La subfunzione 01h del medesimo servizio permette invece di scambiare il contenuto di due aree di memoria, che possono essere indifferentemente situate in memoria convenzionale o espansa.
Int 67h, serv. 57h: Trasferisce da mem. convenz. a mem. EMS e viceversa
Input |
AH AL |
57h 00h trasferisce da memoria convenzionale a memoria EMS DS:SI = punta al buffer di info per il trasferimento 01h scambia il contenuto di due aree di memoria DS:SI = punta al buffer di info per lo scambio |
Output |
AH |
Stato dell'operazione (errore se ; pag. |
Il formato del buffer richiesto dal servizio analizzato è il seguente:
Formato del buffer utilizzato dall'int 67h, serv. 57h
OFFSET |
byte |
DESCRIZIONE |
00h |
|
Lunghezza in byte dell'area di memoria da trasferire |
04h |
|
Tipo della memoria sorgente ( = convenzionale; = EMS) |
05h |
|
Handle EMS sorgente ( se memoria convenzionale) |
07h |
|
Offset dell'area sorgente (rispetto al segmento se memoria convenzionale; rispetto alla pagina logica se memoria EMS) |
09h |
|
Segmento dell'area sorgente (se memoria convenzionale) o pagina logica (se memoria EMS) |
0Bh |
|
Tipo della memoria destinazione ( = convenzionale; = EMS) |
0Ch |
|
Handle EMS destinazione ( se memoria convenzionale) |
0Eh |
|
Offset dell'area destinazione (rispetto al segmento se memoria convenzionale; rispetto alla pagina logica se memoria EMS) |
10h |
|
Segmento dell'area destinazione (se memoria convenzionale) o pagina logica (se memoria EMS) |
Il buffer può essere facilmente rappresentato con una struttura:
struct EMMmove ;
L'esempio seguente copia direttamente 32 Kb dal buffer video colore (B800:0000) ad offset nella terza pagina del blocco EMS allocato allo handle 09h
#include <dos.h>
.
struct REGPACK r;
struct EMMmove EMMinfoBuf;
.
EMMinfoBuf.length = 32*1024; // 32 Kb
EMMinfoBuf.sourceType = 0; // sorgente: memoria convenzionale
EMMinfoBuf.sourceHandle = 0; // handle sempre 0 per mem. convenz.
EMMinfoBuf.sourceOffset = 0; // segmento:offset
EMMinfoBuf.sourceSegment = 0xB800; // B800:0000
EMMinfoBuf.destType = 1; // destinazione: memoria EMS
EMMinfoBuf.destHandle = 0x09; // handle (da int 67h, servizio 43h)
EMMinfoBuf.destOffset = 256; // offset all'interno della pag.logica
EMMinfoBuf.destSegment = 2; // terza pag.logica
r.r_ax = 0x5700;
r.r_ds = FP_SEG((struct EMMmove far *)&EMMinfoBuf);
r.r_si = (unsigned)&EMMinfoBuf;
intr(0x67,&r);
if(r.r_ax & 0xFF00)
La EMMmoveMem() riceve due parametri: il primo è il puntatore alla struttura EMMmove; il secondo è un intero che, a seconda del valore assunto, stabilisce se debba essere effettuata un'operazione di spostamento del contenuto di un'area di memoria (operation ) o di scambio del contenuto di due aree (operation ). La funzione restituisce se non si è verificato alcun errore, mentre è restituito se il parametro operation contiene un valore illecito; la restituzione di un valore maggiore di indica che si è verificato un errore EMS (del quale il valore stesso rappresenta il codice).
Le specifiche LIM 4.0 hanno introdotto un altro servizio degno di nota: il mapping di pagine multiple. Il servizio 44h dell'int 67h, descritto poco sopra, si limita ad effettuare il mapping di una sola pagina logica su una pagina fisica. Se il programma deve agire su una quantità di dati superiore ai 16 Kb è necessario richiamare più volte il servizio stesso, ad esempio all'interno di un ciclo, per mappare tutte le pagine necessarie (al massimo 4). La subfunzione 00h del servizio 50h supera detta limitazione e consente di mappare in un'unica operazione fino a 4 pagine logiche, non necessariamente consecutive, su 4 diverse pagine fisiche, anch'esse non consecutive. La subfunzione 01h consente di effettuare il mapping delle pagine logiche direttamente su aree di memoria convenzionale, senza necessità di utilizzare la page frame (pagine fisiche).
Int 67h, serv. 50h: Mapping multiplo e su memoria convenzionale
Input |
AH AL
CX DX |
50h 00h mapping di più pagine logiche su più pagine fisiche DS:SI = punta al buffer di info per il mapping 01h mapping di più pagine logiche su memoria convenzionale DS:SI = punta al buffer di info per il mapping Numero di pagine da mappare Handle delle pagine logiche |
Output |
AH |
Stato dell'operazione (errore se != 0; pag. 169). |
Il buffer di informazioni è una sequenza di 4 coppie di word (unsigned int). Per la subfunzione 00h, in ogni coppia di word il primo unsigned rappresenta il numero della pagina logica da mappare, mentre il secondo contiene quello della pagina fisica su cui deve avvenire il mapping. Esempio:
#include <dos.h>
.
struct REGPACK r;
unsigned info[8];
.
info[0] = 4; // mappare la pag. logica 4
info[1] = 0; // sulla pag. fisica 0
info[2] = 6; // mappare la pag. logica 6
info[3] = 3; // sulla pag. fisica 3
info[4] = 7; // mappare la pag. logica 7
info[5] = 1; // sulla pag. fisica 1
r.r_ax = 0x5000; // servizio 50h, subfunzione 00h
r.r_cx = 3; // 3 pagine in tutto
r.r_dx = 09h; // handle delle pag. logiche (da serv. 43h)
r.r_ds = FP_SEG((int far *)info);
r.r_si = (unsigned)info;
intr(0x67,&r);
if(r.r_ax & 0xFF00)
La getXMMaddress() restituisce un puntatore a funzione void, che rappresenta l'entry point del driver XMM.
A differenza dei servizi EMM, gestiti tramite un interrupt (l'int 67h), quelli resi disponibili dal driver XMM sono accessibili invocando direttamente la routine che si trova all'entry point del driver, dopo avere opportunamente caricato i registri. In termini di assembler si tratta di eseguire una CALL; chi preferisce il C può utilizzare l'indirezione del puntatore all'entry point stesso. Alcuni esempi serviranno a chiarire le idee.
Servizio XMS 00h: Versione del driver XMM e esistenza DELLA HMA
Input |
AH |
00h |
Output |
AH AL BH BL DX |
Versione XMS supportata. Revisione XMS supportata. Versione del driver XMM. Revisione del driver XMM. 1 se esiste HMA, 0 altrimenti |
Note |
|
Se in uscita AX = 0, si è verificato un errore. In tal caso BL contiene il numero di errore XMS (vedere pag. 169). |
/********************
BARNINGA_Z! - 1992
XMMVERS.C - getXMMversion()
int cdecl getXMMversion(void (far *XMMdriver)(void));
void (far *XMMdriver)(void); puntatore all'entry point del driver.
Restituisce: un intero il cui byte meno significativo rappresenta la versione
XMS supportata e quello piu' significativo la revisione;
Se < 0 e' il codice di errore XMS cambiato di segno.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- xmmvers.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
int cdecl getXMMversion(void (far *XMMdriver)(void))
La getXMMversion() restituisce un unsigned integer, il cui byte meno significativo rappresenta la versione, e quello più significativo la revisione[39] della specifica XMS supportata. Se il valore restituito è minore di 0 si è verificato un errore: detto valore è il codice di errore XMS cambiato di segno. L'indirezione del puntatore all'entry point del driver trasferisce ad esso il controllo: dal momento che il parametro XMMdriver rappresenta proprio quell'indirizzo, l'istruzione C
(*XMMdriver)();
è equivalente allo inline assembly
asm call dword ptr XMMdriver;
Circa i puntatori a funzione vedere pag. 95.
/********************
BARNINGA_Z! - 1992
XMMDVERS.C - getXMMdrvVersion()
int cdecl getXMMdrvVersion(void (far *XMMdriver)(void));
void (far *XMMdriver)(void); puntatore all'entry point del driver.
Restituisce: un intero il cui byte meno significativo rappresenta la versione
del driver XMM e quello piu' significativo la revisione;
Se < 0 e' il codice di errore XMS cambiato di segno.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- xmmdvers.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
int cdecl getXMMdrvVersion(void (far *XMMdriver)(void))
Anche la getXMMdrvVersion() restituisce un unsigned int, il cui byte meno significativo rappresenta la versione, e quello più significativo la revisione[40] del gestore XMM. Se il valore restituito è minore di 0 si è verificato un errore: detto valore è il codice di errore XMS cambiato di segno.
Il servizio XMS 08h consente di conoscere la quantità di memoria XMS libera.
Servizio XMS 08h: Quantità di memoria XMS disponibile
Input |
AH |
08h |
Output |
AX DX |
La dimensione in Kb del maggiore blocco libero. La quantità totale, in Kb, di memoria XMS libera. |
Note |
|
In caso di fallimento, AX è 0 e BL contiene il codice di errore (vedere pag. 169). La quantità di memoria XMS libera non include mai la HMA, neppure nel caso in cui questa non sia allocata. |
/********************
BARNINGA_Z! - 1992
XMSFREEM.C - getXMSfreeMem()
long getXMSfreeMem(void (far *XMMdriver)(void));
void (far *XMMdriver)(void); puntatore all'entry point del driver XMM.
Restituisce: La quantità di memoria XMS libera, in Kilobytes.
Se < 0 e' il codice di errore XMS.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- xmsfreem.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
long cdecl getXMSfreeMem(void (far *XMMdriver)(void))
/********************
BARNINGA_Z! - 1992
XMSFREEB.C - getXMSfreeBlock()
long getXMSfreeBlock(void (far *XMMdriver)(void));
void (far *XMMdriver)(void); puntatore all'entry point del driver XMM.
Restituisce: la dimensione in Kb del maggiore blocco XMS disponibile;
Se < 0 e' il codice di errore XMS.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- xmsfreeb.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
long cdecl getXMSfreeBlock(void (far *XMMdriver)(void))
Non esiste un servizio XMS per determinare la quantità totale di memoria estesa installata sulla macchina; essa è sempre almeno pari ai Kb liberi ai quali vanno sommati, se la HMA esiste, altri 64 Kb. La dimensione della memoria XMS può essere maggiore di quella fisicamente presente sulla macchina nel caso in cui sia attivo un ambiente in grado di creare memoria virtuale (vedere pag. 218).
La quantità di memoria estesa fisica (la memoria realmente installata sulla macchina) è la word memorizzata nel CMOS[41] ad offset 17h: ecco un suggerimento per conoscerla.
/********************
BARNINGA_Z! - 1992
EXTINST.C - getEXTinstalled()
unsigned getEXTinstalled(void);
Restituisce: il numero di Kb di memoria estesa fisica installati.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- extinst.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
unsigned cdecl getEXTinstalled(void)
return(_AX);
}
La quantità di memoria estesa installata e non gestita da un driver XMM è conoscibile attraverso l'int 15h[42].
Int 15h, Serv. 88h: Memoria estesa (non XMS) disponibile
Input |
AH |
88h |
Output |
AX |
La quantità in Kb di memoria estesa installata non gestita da un driver XMM. |
Note |
|
In caso di errore CarryFlag = 1; il valore in AX non è significativo. |
/********************
BARNINGA_Z! - 1992
EXTFREE.C - getEXTfree()
unsigned getEXTfree(void);
Restituisce: il numero di Kb di memoria estesa installati e attualmente
non sotto il controllo di un driver XMM.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- extfree.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
unsigned cdecl getEXTfree(void)
Il valore restituito da getEXTfree() è sempre minore o uguale di quello restituito da getEXTinstalled(), salvo il caso in cui il sistema utilizzi memoria virtuale.
Quando si conosca la quantità di memoria XMS libera, l'operazione preliminare al suo utilizzo è l'allocazione di un Extended Memory Block (EMB), il quale altro non è che un'area di memoria XMS, della dimensione desiderata, alla quale il driver associa uno handle di riferimento: l'analogia con i servizi EMS (pag. 169) è evidente.
Servizio XMS 09h: Alloca un blocco di memoria XMS
Input |
AH DX |
09h La dimensione del blocco, in Kilobyte |
Output |
AX DX |
1 se l'allocazione è riuscita correttamente. Lo handle associato al blocco. |
Note |
|
In caso di fallimento, AX è 0 e BL contiene il codice di errore (vedere pag. 169). |
Vediamo un esempio di funzione basata sul servizio 09h.
/********************
BARNINGA_Z! - 1992
EMBALLOC.C - allocEMB()
int allocEMB(void (far *XMMdriver)(void),unsigned EMBkb);
void (far *XMMdriver)(void); puntatore all'entry point del driver XMM.
unsigned EMBkb; dimensione in Kb del blocco richiesto.
Restituisce: Lo handle associato al blocco allocato.
Se < 0 e' il codice di errore XMS.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- emballoc.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
int cdecl allocEMB(void (far *XMMdriver)(void),unsigned EMBkb)
La allocEMB() tenta di allocare un Extended Memory Block della dimensione (in Kb) specificata con il parametro EMBkb. Se l'operazione riesce viene restituito lo handle che il driver XMS ha associato al blocco, altrimenti il valore restituito, cambiato di segno, è il codice di errore XMS.
Per copiare dati dalla memoria convenzionale ad un EMB o viceversa è disponibile il servizio XMS 0Bh, che può essere utilizzato anche per copiare dati da un EMB ad un secondo EMB, nonché tra due indirizzi in memoria convenzionale: l'operazione desiderata è infatti descritta al driver tramite un buffer composto di 5 campi, il primo dei quali indica la lunghezza in byte dell'area da copiare; i campi successivi possono essere suddivisi in due coppie (una per l'area sorgente ed una per l'area destinazione), in ciascuna delle quali il significato del secondo campo dipende dal valore contenuto nel primo. In particolare, se il primo campo contiene un valore diverso da 0, questo è interpretato come handle di un EMB, e dunque il secondo campo della coppia indica l'offset lineare a 32 bit all'interno dell'EMB. Se, al contrario, il primo campo è 0, allora il secondo è interpretato come un normale indirizzo far a 32 bit in memoria convenzionale. Valorizzando opportunamente i campi è possibile richiedere operazioni di copia in qualsiasi direzione.
Servizio XMS 0Bh: Copia tra aree di memoria convenzionale o XMS
Input |
AH DS:SI |
0Bh Buffer descrittivo dell'operazione (vedere tabella seguente) |
Output |
AX |
1 se l'operazione di copia è riuscita correttamente. |
Note |
|
In caso di fallimento, AX è 0 e BL contiene il codice di errore (vedere pag. 169). |
Il formato del buffer richiesto dal servizio è il seguente:
Formato del buffer utilizzato dal servizio XMS 0Bh
OFFSET |
byte |
DESCRIZIONE |
00h |
4 |
Lunghezza in byte dell'area di memoria da trasferire (deve essere un numero pari) |
04h |
2 |
Handle XMS sorgente (0 se memoria convenzionale) |
06h |
4 |
Se il campo precedente è 0 questo campo è un puntatore far ad un'area di memoria convenzionale; se invece il campo predecente è diverso da 0, questo campo rappresenta un offset lineare a 32 bit nel blocco di memoria estesa associato allo handle. In entrambi i casi l'indirizzo è inteso come sorgente. |
0Ah |
2 |
Handle XMS destinazione (0 se memoria convenzionale) |
0Ch |
4 |
Se il campo precedente è 0 questo campo è un puntatore far ad un'area di memoria convenzionale; se invece il campo predecente è diverso da 0, questo campo rappresenta un offset lineare a 32 bit nel blocco di memoria estesa associato allo handle. In entrambi i casi l'indirizzo è inteso come destinazione. |
Il buffer può essere rappresentato da una struttura:
struct EMBmove ;
La struttura EMBmove è il secondo parametro della funzione XMSmoveMem():
/********************
BARNINGA_Z! - 1992
XMSMOVM.C - XMSmoveMem()
int XMSmoveMem(void (far *XMMdriver)(void),struct EMBmove *EMBbuffer);
void (far *XMMdriver)(void); puntatore all'entry point del driver XMM.
struct EMBmove *EMBbuffer; puntatore al buffer che descrive l'operazione.
Restituisce: 0 se l'operazione e' stata eseguita correttamente.
Se < 0 e' il codice di errore XMS.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- xmsmovm.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
int cdecl XMSmoveMem(void (far *XMMdriver)(void),struct EMBmove *EMBbuffer)
L'esempio che segue utilizza la XMSmoveMem() per copiare 96 Kb da memoria convenzionale a memoria estesa. I dati sono copiati ad offset 10000h nell'EMB.
.
unsigned char far *convMemPtr;
void (far *XMMdriver)(void); // puntatore all'entry point del driver XMM
struct EMBmove EMBbuffer;
unsigned EMBhandle;
.
EMBbuffer.length = 96 * 1024; // lunghezza in bytes dell'area da copiare
EMBbuffer.srcHandle = 0; // sorgente memoria convenzionale
EMBbuffer.srcOffset = (long)convMemPtr; // indirizzo sorgente in mem. conv.
EMBbuffer.dstHandle = EMBhandle; // restituito da allocEMB()
EMBbuffer.dstOffset = 0x10000; // offset nell'EMB associato a EMBhandle
if(XMSmoveMem(XMMdriver,&EMBbuffer) < 0)
. // gestione errore XMS
Si tenga presente che il servizio XMS 0Bh non garantisce che la copia sia effettuata correttamente se l'area sorgente e l'area destinazione si sovrappongono anche parzialmente e l'indirizzo della prima è maggiore dell'indirizzo della seconda. Inoltre non è necessario abilitare la A20 line per utilizzare il servizio.
Prima di terminare i programmi devono esplicitamente liberare gli EMB eventualmente allocati e la HMA se utilizzata, al fine di renderli nuovamente disponibili agli altri processi. Il DOS non interagisce con il driver XMM nella gestione della memoria estesa, pertanto in uscita dai programmi non effettua alcuna operazione di cleanup riguardante gli EMB e la HMA: se non rilasciati dal processo che termina, questi risultano inutilizzabili da qualsiasi altro programma sino al reset della macchina (la situazione è analoga a quella descritta con riferimento ai servizi EMS: vedere pag. 169).
Servizio XMS 0Ah: Dealloca uno handle XMS
Input |
AH DX |
0Ah Handle XMS da deallocare |
Output |
AX |
1 se la disallocazione è riuscita correttamente. Tutta la memoria estesa a cui è associato lo handle è liberata. |
Note |
|
In caso di fallimento, AX è 0 e BL contiene il codice di errore (vedere pag. 169). |
Segue, come sempre, un esempio di funzione basata sul servizio appena descritto.
/********************
BARNINGA_Z! - 1992
EMBFREE.C - freeEMB()
int freeEMB(void (far *XMMdriver)(void),unsigned EMBhandle);
void (far *XMMdriver)(void); puntatore all'entry point del driver XMM.
unsigned EMBhandle; handle EMB da deallocare.
Restituisce: 0 se l'operazione è stata eseguita con successo.
Se < 0 e' il codice di errore XMS.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- embfree.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
int cdecl freeEMB(void (far *XMMdriver)(void),unsigned EMBhandle)
Anche in questo caso è restituito 0 se l'operazione è stata eseguita correttamente, mentre un valore minore di 0 segnala che si è verificato un errore XMS e ne rappresenta, al tempo stesso, il codice cambiato di segno.
Vale ancora la pena di aggiungere che è possibile richiedere la modifica della dimensione di un EMB mediante il servizio XMS 0Fh:
Servizio XMS 0Fh: Modifica la dimensione di un EMB
Input |
AH BX DX |
0Fh Nuova dimensione richiesta in Kilobyte Handle XMS associato all'EMB |
Output |
AX |
1 se la disallocazione è riuscita correttamente. Tutta la memoria estesa a cui è associato lo handle è liberata. |
Note |
|
In caso di fallimento, AX è 0 e BL contiene il codice di errore (vedere pag. 169). |
Segue esempio:
/********************
BARNINGA_Z! - 1992
EMBRESIZ.C - resizeEMB()
int resizeEMB(void (far *XMMdriver)(void),unsigned EMBhandle,unsigned newKb);
void (far *XMMdriver)(void); puntatore all'entry point del driver XMM.
unsigned EMBhandle; handle XMS associato all'EMB da modificare.
unsigned newKb; nuova dimensione desiderata, in Kilobytes.
Restituisce: 0 se l'operazione è stata eseguita con successo.
Se < 0 e' il codice di errore XMS.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- embresiz.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
int cdecl resizeEMB(void (far *XMMdriver)(void),unsigned EMBhandle,unsigned newKb)
La resizeEMB() restituisce un valore secondo criteri coerenti con quelli adottati dalle funzioni sopra listate[43].
Una parte dei servizi XMS è implementa la gestione della High Memory Area. Vediamo i listati di alcune funzioni.
/********************
BARNINGA_Z! - 1992
XMMISHMA.C - isHMA()
int cdecl isHMA(void (far *XMMdriver)(void));
void (far *XMMdriver)(void); puntatore all'entry point del driver.
Restituisce: 1 HMA esistente
0 HMA non esistente
Se < 0 e' il codice di errore XMS cambiato di segno.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- xmmishma.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
int cdecl isHMA(void (far *XMMdriver)(void))
La isHMA() restituisce 1 se la HMA è presente, 0 in caso contrario. Un valore minore di 0 rappresenta il codice di errore XMS.
In caso di esistenza della HMA è possibile determinare se essa è allocata o libera tentando di allocarla: se l'operazione ha successo la HMA era disponibile (occorre allora disallocarla per ripristinare la situazione iniziale).
Servizio XMS 01h: Alloca la HMA (se disponibile)
Input |
AH DX |
01h Byte necessari al programma nella HMA (valori possibili 0‑FFF0). |
Output |
AX BL |
1 se la HMA è stata allocata con successo. In caso di fallimento, AX è 0 e BL contiene il codice di errore: se BL = 91h, la HMA è già allocata (vedere pag. 169). |
Note |
|
Il numero di byte specificato in DX viene confrontato con il valore attribuito al parametro /HMAMIN sulla riga di comando del driver XMM: se è inferiore a quest'ultimo la HMA non viene allocata neppure se libera, in caso contrario è allocata tutta la HMA. |
Circa il servizio XMS 01h, va precisato che, secondo la documentazione ufficiale, non è possibile dividere la HMA in subaree: essa è allocata interamente, anche se sono richiesti meno di 64Kb. Esiste però un servizio non documentato[44] dell'int 2Fh, supportato dal driver HIMEM.SYS del DOS, che dispone di due subfunzioni utili per allocare la parte libera di HMA quando questa sia già allocata:
Int 2Fh, serv. 4Ah: Gestisce la porzione libera di una HMA già allocata
Input |
AH AL |
4Ah 01h richiede quanti byte sono ancora liberi nella HMA 02h alloca spazio nella HMA BX numero di byte richiesti |
Output |
ES:DI BX |
Indirizzo del primo byte libero nella HMA (FFFF:FFFF in caso di errore). Numero di byte liberi in HMA (solo subfunzione 01h). |
Note |
|
Questo servizio è disponibile solo se il DOS è caricato in HMA: in caso contrario viene restituito 0 in BX e FFFF:FFFF in ES:DI. |
Servizio XMS 02h: Dealloca la HMA
Input |
AH |
02h |
Output |
AX
BL |
1 se la HMA è stata liberata con successo. In caso di fallimento, AX è 0 e BL contiene il codice di errore (vedere pag. 169). codice di errore |
Note |
|
Il programma che alloca la HMA deve disallocarla prima di restituire il controllo al sistema; in caso contrario la HMA rimane inutilizzabile fino al primo bootstrap. |
/********************
BARNINGA_Z! - 1992
ISHMAFRE.C - isHMAfree()
int isHMAfree(void (far *XMMdriver)(void));
void (far *XMMdriver)(void); puntatore all'entry point del driver XMM.
Restituisce: 1 se la HMA e' libera;
0 se gia' allocata;
Se < 0 e' il codice di errore XMS cambiato di segno.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- ishmafre.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
int cdecl isHMAfree(void (far *XMMdriver)(void))
La isHMAfree() restituisce 0 se la HMA è già allocata o, al contrario, 1 se è libera. In caso di errore è restituito un valore negativo che rappresenta, cambiato di segno, il codice di errore XMS.
E' facile, 'spezzando' la isHMAfree(), realizzare due funzioni in grado di allocare la HMA (HMAalloc()) e, rispettivamente, disallocarla (HMAdealloc()).
/********************
BARNINGA_Z! - 1992
HMAALLOC.C - HMAalloc()
int *HMAalloc(void (far *XMMdriver)(void),unsigned nBytes);
void (far *XMMdriver)(void); puntatore all'entry point del driver XMM.
unsigned nBytes; numero di bytes da allocare.
Restituisce: 0 se l'allocazione e' stata effettuata con successo;
Se < 0 e' il codice di errore XMS
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- hmaalloc.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
int cdecl HMAalloc(void (far *XMMdriver)(void),unsigned nBytes)
/********************
BARNINGA_Z! - 1992
HMADEALL.C - HMAdealloc()
int HMAdealloc(void (far *XMMdriver)(void));
void (far *XMMdriver)(void); puntatore all'entry point del driver XMM.
Restituisce: 0 se la HMA e' stata deallocata con successo;
Se < 0 e' il codice di errore XMS cambiato di segno.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- hmadeall.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
int cdecl HMAdealloc(void (far *XMMdriver)(void))
Lo stato della A20 Line, attraverso la quale è possibile indirizzare la HMA, può essere indagato tramite il servizio 07h del driver XMM.
Servizio XMS 07h: Stato della A20 Line
Input |
AH |
07h |
Output |
AX
BL |
1 se la A20 Line è abilitata, 0 se non lo è (in questo caso anche BL = 0). In caso di fallimento, AX è 0 e BL contiene il codice di errore (vedere pag. 169). codice di errore |
/********************
BARNINGA_Z! - 1992
ISA20ON.C - isA20enabled()
int cdecl isA20enabled(void (far *XMMdriver)(void));
void (far *XMMdriver)(void); puntatore all'entry point del driver XMM.
Restituisce: 1 se la A20 line e' abilitata;
0 se la A20 line e' disabilitata;
Se < 0 e' il codice di errore XMS.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- isa20on.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
int cdecl isA20enabled(void (far *XMMdriver)(void))
La isA20enabled() restituisce 1 se la A20 line è abilitata, 0 se non lo è. In caso di errore è restituito un valore negativo che, cambiato di segno rappresenta il codice di errore XMS.
Per utilizzare la HMA occorre che la linea A20 sia abilitata; i servizi XMS 03h e 04h la abilitano e disabilitano specificamente per consentire l'accesso alla HMA[45].
Servizio XMS 03h: Abilita la A20 Line
Input |
AH |
03h |
Output |
AX |
1 se la A20 Line è stata attivata correttamente. |
Note |
|
In caso di fallimento, AX è 0 e BL contiene il codice di errore (vedere pag. 169). Questo servizio deve essere utilizzato solo se il programma ha allocato con successo la HMA mediante il servizio 01h. |
Servizio XMS 04h: Disabilita la A20 Line
Input |
AH |
04h |
Output |
AX |
1 se la A20 Line è stata disattivata correttamente. |
Note |
|
In caso di fallimento, AX è 0 e BL contiene il codice di errore (vedere pag. 169). Questo servizio deve essere utilizzato solo se il programma ha allocato con successo la HMA mediante il servizio 01h. |
I due esempi di funzione che seguono utilizzano i servizi testè descritti.
/********************
BARNINGA_Z! - 1992
A20ENABL.C - enableA20()
int cdecl enableA20(void (far *XMMdriver)(void));
void (far *XMMdriver)(void); puntatore all'entry point del driver XMM.
Restituisce: 0 se la A20 line e' stata abilitata con successo;
Se < 0 e' il codice di errore XMS.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- a20enabl.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
int cdecl enableA20(void (far *XMMdriver)(void))
/********************
BARNINGA_Z! - 1992
A20DISAB.C - disableA20()
int cdecl disableA20(void (far *XMMdriver)(void));
void (far *XMMdriver)(void); puntatore all'entry point del driver XMM.
Restituisce: 0 se la A20 line e' stata disabilitata con successo;
Se < 0 e' il codice di errore XMS.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- a20disab.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
int cdecl disableA20(void (far *XMMdriver)(void))
Le funzioni enableA20() e disableA20() restituiscono 0 se hanno eseguito correttamente il loro compito; in caso di errore è restituito un valore negativo che rappresenta, cambiato di segno, il codice di errore XMS.
Riportiamo la descrizione dei servizi XMS (e gli esempi relativi) che consentono di allocare e deallocare gli Upper Memory Block. Per ulteriori e più approfonditi particolari circa gli UMB vedere pag. 169 e seguenti.
Servizio XMS 0Fh: Alloca un UMB
Input |
AH DX |
10h Dimensione richiesta in paragrafi (blocchi di 16 byte) |
Output |
AX BX DX |
1 se la disallocazione è riuscita correttamente. Indirizzo di segmento dell'UMB allocato Dimensione reale in paragrafi |
Note |
|
In caso di fallimento, AX è 0 e BL contiene il codice di errore (vedere pag. 169), mentre DX contiene la dimensione in paragrafi del massimo UMB disponibile. |
Segue esempio:
/********************
BARNINGA_Z! - 1992
UMBALLOC.C - allocUMB()
int allocUMB(void (far *XMMdriver)(void),unsigned UMBKb,unsigned *UMBseg);
void (far *XMMdriver)(void); puntatore all'entry point del driver XMM.
unsigned UMBKb; dimensione desiderata, in paragrafi.
unsigned *UMBseg; usata per restituire l'indirizzo di segmento
dell'UMB allocato. In caso di errore contiene la
dimensione del massimo UMB disponibile.
Restituisce: 0 se l'operazione e' stata eseguita con successo. All'indirizzo
UMBseg e' memorizzato l'indirizzo di segmento dell'UMB allocato;
Se < 0 e' il codice di errore XMS; All'indirizzo UMBseg e'
memorizzata la dimensione del massimo UMB disponibile.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- umballoc.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
int cdecl allocUMB(void (far *XMMdriver)(void),unsigned UMBkb,unsigned *UMBseg)
Servizio XMS 11h: Dealloca un un UMB
Input |
AH DX |
11h Indirizzo di segmento dell'UMB |
Output |
AX |
1 se la disallocazione è riuscita correttamente. L'UMB è nuovamente libero |
Note |
|
In caso di fallimento, AX è 0 e BL contiene il codice di errore (vedere pag. 169). |
Segue esempio:
/********************
BARNINGA_Z! - 1992
UMBFREE.C - freeUMB()
int freeUMB(void (far *XMMdriver)(void),unsigned UMBseg);
void (far *XMMdriver)(void); puntatore all'entry point del driver XMM.
unsigned UMBseg; indirizzo di segmento dell'UMB da liberare.
Restituisce: 0 se l'operazione è stata eseguita con successo.
Se < 0 e' il codice di errore XMS.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- umbfree.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
int cdecl freeUMB(void (far *XMMdriver)(void),unsigned UMBseg)
Un UMB allocato con allocUMB() può essere referenziato mediante un puntatore far o huge, costruito con la macro MK_FP() (pag. 25):
#include <dos.h> // per MK_FP()
.
unsigned umbSeg;
char far *umbPtr;
.
if(allocUMB(XMMdriver,10000,&umbSeg) < 0)
else
}
La tabella che segue riporta i codici di errore XMS.
Codici di errore XMS
Codice |
Descrizione |
80h |
Funzione non valida |
81h |
E' installato VDISK |
82h |
Errore nella A20 line |
8Eh |
Errore interno al driver |
8Fh |
Errore irrecuperabile del driver |
90h |
La HMA non esiste |
91h |
La HMA è già in uso |
92h |
Richiesta allocazione in HMA di un numero di byte minore del parametro associato a /HMAMIN sulla riga di comando del driver XMM |
93h |
La HMA è libera |
94h |
Non è stato possibile disattivare la A20 line |
A0h |
Tutta la memoria estesa è già allocata |
A1h |
Tutti gli handles disponibili per la memoria estesa sono già utilizzati |
A2h |
Handle non valido |
A3h |
Handle sorgente non valido |
A4h |
Offset sorgente non valido |
A5h |
Handle destinazione non valido |
A6h |
Offset destinazione non valido |
A7h |
La lunghezza del blocco non è valida |
A8h |
Le aree sorgente e destinazione si sovrappongono |
A9h |
Errore di parità |
AAh |
Il blocco non è locked |
ABh |
Il blocco è locked |
ACh |
Overflow nel conteggio dei lock del blocco |
ADh |
Operazione di lock fallita |
B0h |
E' disponibile un UMB di dimensione minore a quella richiesta |
B1h |
Non sono disponibili UMB |
B2h |
Segmento UMB non valido |
[1] Che cosa vi aspettavate? Questa non è una guida di riferimento tecnico per il sistema operativo, né un supplemento alla manualistica dei compilatori C. Questa è beh chissà.
[2] Il C Borland include ALLOC.H; il C Microsoft MALLOC.H.
[3] Si noti che i servizi DOS gestiscono la memoria in unità minime di 16 byte, dette paragrafi. Inoltre, ogni blocco allocato dal DOS si trova sempre ad un indirizzo allineato a paragrafo (divisibile, cioè, per 16), esprimibile con un'espressione del tipo segmento:0000.
[4] Forse è opportuno ricordare che gli indirizzi segmento:offset sono una rappresentazione (coerente con i registri a 16 bit della CPU) di un indirizzo a 20 bit; 9FFF:000F equivale a 9FFFF.
[5] Tutte le versioni di DOS, inclusa la 6.2, sembrano caricare il primo file nascosto proprio all'indirizzo 0070:0000.
[6] Il DOS non distingue le due aree: dopo il bootstrap, tutta la RAM al di sopra dell'environment di COMMAND.COM è considerata un'unica area, libera, a disposizione dei programmi. La parte transiente di COMMAND.COM, se sovrascritta, viene ricaricata da disco all'occorrenza.
[7] In sostanza, il DOS gestisce la RAM per aree (che possono essere allocate ad un programma oppure libere), in testa ad ognuna delle quali crea un MCB. Un po' di pazienza, tra breve analizzeremo i MCB in dettaglio.
[8] Questa è la regola generale. A partire dal DOS 4.0, però, l'area di RAM allocata ai device driver ha un MCB regolare, recante la lettera 'M' nel campo POS, ma è a sua volta suddivisa in tante sub‑aree quanti sono i driver installati, ciascuna dotata, in testa, di un proprio MCB. In tali Memory Control Block il campo POS indica il tipo di driver; il suo contenuto può essere: 'D' blocco device driver (installato dal comando DEVICE in CONFIG.SYS), 'F' blocco FILES, 'X' blocco FCBS, 'B' blocco BUFFERS, 'C' blocco buffer EMS, 'I' blocco IFS, 'L' blocco LASTDRIVE, 'S' blocco STACKS, 'E' blocco device driver appendage.
[9] Il PSP è un record di 256 byte che il DOS prepara in testa al codice del programma eseguito. Per ogni programma caricato in memoria si ha dunque un MCB, immediatamente seguito dal PSP, a sua volta seguito dal codice del programma stesso. L'indirizzo di segmento del PSP di un programma è pertanto pari a quello del suo MCB, incrementato di uno.
[10] In effetti, l'area dei device driver è quella che immediatamente segue la RAM riservata al secondo file nascosto, come evidenziato in figura 1.
[11] Tutte le aree di RAM gestite mediante servizi DOS hanno dimensione (in byte) divisibile per 16 (multiple per paragrafi: non è più una novità). Anche il loro indirizzo è divisibile per 16 (cioè allineato a paragrafo) ed è esprimibile mediante la sola parte segmento (seg:0000). Ne segue che anche i MCB sono allineati a paragrafo, dal momento che occupano il paragrafo immediatamente precedente l'area allocata. Vale infine la pena di sottolineare che i servizi 48h, 49h e 4Ah dell'int 21h restituiscono e/o richiedono in input l'indirizzo (sotto forma di word, la sola parte segmento) dell'area e non quello del MCB (ricavabile decrementando di uno quello dell'area).
[12] Le versioni di DOS anteriori alla 4.0 non utilizzano questo campo; con esse il solo metodo per conoscere il nome del programma è andare a curiosare in coda all'environment di questo (vedere pag. 169 per un esempio di metodo valido, comunque, anche con DOS 4 e successivi). Qui il nome è memorizzato completo di drive e pathname; tuttavia esso scompare se la RAM allocata all'environment viene liberata.
[13] Alcune interessanti particolarità relative all'InDOS Flag sono discusse alle pagine 297 e seguenti.
[14] Il puntatore mcb non è dichiarato near né far (pag. 22): il suo tipo dipende perciò dal modello di memoria scelto per la compilazione (pag. 151); esso, in particolare, è near nei modelli tiny, small e medium. All'interno della funzione non è possibile sapere se la struttura a cui mcb punta è stata allocata nello heap con una chiamata a malloc() (con indirizzo relativo a DS), nell'area dati statici e globali (indirizzo ancora relativo a DS) o nello stack come variabile automatica (indirizzo relativo a SS). In tutti gli esempi di funzione presentati nel testo, in casi come quello analizzato, si assume che nei modelli small e medium DS e SS coincidano (e si utilizza dunque DS per ricavare la parte segmento dell'indirizzo), in quanto questo è il default di comportamento del compilatore. Solo con particolari e pericolose opzioni della riga di comando è infatti possibile richiedere che DS e SS, in detti modelli di memoria, non siano necessariamente uguali.
[15] Per la precisione: un megabyte e 64 Kb meno 16 byte (FFFF:FFFF). I (circa) 64 Kb eccedenti il Mb sono denominati HMA (High Memory Area; vedere pag. 230 e seguenti). Le macchine a 32 bit (80386, 80486, etc.) possono indirizzare linearmente grandi quantità di RAM, ma il limite descritto permane in ambiente DOS.
[16] Quanto detto è vero per le macchine 80286. Le macchine basate su processore 80386 o 80486 (comprese le versioni SX) possono utilizzare anche memoria estesa, se al bootstrap è installato un driver in grado di emulare la memoria espansa attraverso quella estesa.
[17] Forse vale la pena di ricordare che la parte segmento di un indirizzo equivale alla word più significativa di un puntatore C far o huge.
[18] Il primo MCB ha lo scopo di escludere dal remapping il buffer video EGA/VGA (A000:0‑AFFF:000F). La dimensione del MCB può variare a seconda delle opzioni presenti sulla riga di comando del driver che gestisce la Upper Memory.
[19] La logica è analoga a quella descritta circa il caricamento dei device driver in memoria convenzionale.
[20] Riprendendo l'esempio precedente: a 9FFF:0 vi è il primo MCB dell'Upper Memory; la formula indirizzo+DIM+1 fornisce B001h. Se sulla macchina è installato un video a colori, l'intervallo B001:0‑B7FF:0 costituisce il primo UMB (a B000:0 vi è il suo proprio MCB), che può contenere uno o più MCB. A B7FF:0 si trova un MCB che ha lo scopo di proteggere l'intervallo B800:0‑C7FF:000F (nell'ipotesi di scheda VGA presente): la formula indirizzo+DIM+1 fornisce C801h. A C801:0 vi è un altro UMB (il suo proprio MCB è a C000:0), che può contenere diversi MCB, e così via.
[21] La word a 0:0413 contiene i Kb di memoria convenzionale installati.
[22] Esempio: se sulla macchina è installato un video a colori, a B000:0 vi è il primo MCB dell'Upper Memory (l'area UMB è a B001:0); la formula indirizzo+DIM+1 fornisce l'indirizzo del successivo MCB. A B7FF:0 si trova un MCB che ha lo scopo di proteggere l'intervallo B800:0‑C7FF:000F (nell'ipotesi di scheda VGA presente): la formula indirizzo+DIM+1 fornisce C800h. Qui vi è un altro MCB (l'area UMB è a C001:0), per il quale la formula indirizzo+DIM+1 fornisce l'indirizzo del successivo MCB, e così via.
[23] Gli esempi su allocazione e disallocazione degli UMB presumono la conoscenza della modalità di chiamata dei servizi XMS, descritta proprio nel capitolo dedicato alla memoria estesa.
[24] Una pagina equivale a 16 Kb.
[25] Lo standard industriale di specifiche per la gestione della memoria espansa definito da Lotus, Intel e Microsoft.
[26] Parte delle funzioni è scritta in C puro, parte, a scopo esemplificativo, si basa sull'inline assembly.
[27] Se il bit 7 di DX è 0, allora esiste nella directory corrente un file avente nome EMMXXXX0: la open() ha aperto detto file (e non il device EMM). Quando si dice la sfortuna
[28] Tra l'altro questo algoritmo è facilmente implementabile anche all'interno di gestori di interrupt.
[29] Il valore restituito dall'int 67h è 'risistemato' in modo coerente con il servizio 30h dell'int 21h, che restituisce in AL la versione e in AH la revisione del DOS.
[30] In grado, cioè, di utilizzare porzioni dello spazio libero su disco come RAM aggiuntiva. E' il caso, ad esempio, di Microsoft Windows 3.x su macchine 80386 o superiori, se attivo in modalità '80386 Avanzata'.
[31] Se la sua dimensione supera i 9 byte, sono comunque utilizzati solamente i primi 9.
[32] Se, ad esempio, si richiede al driver di allocare un gruppo di 3 pagine logiche, queste saranno numerate da 0 a 2. Ciascuna di esse è identificabile univocamente mediante lo handle e il proprio numero. Nonostante il paragone sia un po' azzardato, si può pensare ad un blocco di pagine logiche come ad un array: lo handle identifica l'array stesso, ed ogni pagina ne è un elemento, che può essere referenziato tramite il proprio numero (l'indice).
[33] Mappare. orrendo!
[34] Alcune notizie relative all'utilizzo della memoria EMS nei TSR sono date a pag. 169.
[35] I programmi DOS possono operare in modo protetto solo su macchine dotate di processore 80286 o superiore. La modalità standard di lavoro in ambiente DOS, possibile su tutte le macchine, è la cosiddetta reale (real mode).
[36] XMS è acronimo di eXtended Memory Specification, standard industriale per la gestione della memoria estesa. La XMS include anche regole per la gestione degli UMB.
[37] Ne segue che la memoria estesa in senso stretto si trova oltre i primi 1088 Kb (FFFF:FFFF).
[38] Forse è il caso di spendere due parole di chiarimento circa la HMA. Questa è l'unica parte di memoria estesa indirizzabile da un processore 80286 o superiore senza necessità di lavorare in modalità protetta. Infatti l'indirizzo F000:FFFF punta all'ultimo byte di memoria entro il primo Mb: detto indirizzo è normalizzato in FFFF:000F. Incrementandolo di uno si ottiene FFFF:0010, cioè l'indirizzo del primo byte di memoria estesa, equivalente all'indirizzo lineare 100000h, esprimibile mediante 21 bit. Le macchine 8086 dispongono di sole 20 linee di indirizzamento della RAM e possono quindi gestire indirizzi 'contenuti' in 20 bit. Per questo motivo l'indirizzo FFFF:0010 subisce su di esse il cosiddetto address wrapping e diviene 0000:0000. Al contrario, le macchine 80286 dispongono di 24 bit per l'indirizzamento della memoria; quelle basate sul chip 80386 ne hanno 32. La A20 line è la linea hardware (sono convenzionalmente indicate con A0A31) di indirizzamento della RAM corrispondente al ventunesimo bit: essa consente, se attiva (i servizi XMS lo consentono anche in modalità reale), di indirizzare i primi FFF0h byte (65520) al di là del primo Mb, che rappresentano, appunto, la HMA. Per ulteriori notizie circa l'indirizzamento della RAM vedere pag. 16.
[39] Il valore restituito dall'int 67h è 'risistemato' in modo coerente con il servizio 30h dell'int 21h, che restituisce in AL la versione e in AH la revisione del DOS.
[40] Il valore restituito dall'int 67h è 'risistemato' in modo coerente con il servizio 30h dell'int 21h, che restituisce in AL la versione e in AH la revisione del DOS.
[41] Del CMOS si parla, con maggiore dettaglio, a pagina 555.
[42] L'uso dell'int 15h rappresenta uno standard (precedente al rilascio delle specifiche XMS) in base al quale il programma che intende allocare memoria estesa chiama l'int 15h e memorizza la quantità di memoria estesa esistente: tale valore rappresenta anche l'indirizzo dell'ultimo byte di memoria fisicamente installata. Il programma stesso deve poi installare un proprio gestore dell'int 15h, che restituisce al successivo chiamante un valore inferiore: la differenza rappresenta proprio la quantità di memoria estesa che il programma intende riservare a sé. Un altro standard, anch'esso precedente a quello XMS, è il cosiddetto sistema 'VDISK', dal nome del driver Microsoft per ram‑disk che lo implementò per primo. L'algoritmo è analogo a quello dell'int 15h, ma la memoria estesa è allocata al programma a partire 'dal basso', cioè dall'indirizzo 100000h (con lo standard dell'int 15h la memoria è, evidentemente, allocata a partire dall'alto). I tre sistemi (int 15h, VDISK e XMS) sono beatamente incompatibili tra loro c'era da dubitarne?
[43] Non è davvero il caso di ripetere sempre la medesima litania.
[44] E, pertanto, pericoloso. Non sembrano inoltre essere disponibili servizi per la disallocazione delle porzioni di HMA allocate mediante la subfunzione 02h del servizio 4Ah.
[45] Quando la A20 line è attiva, una coppia di registri a 16 bit può esprimere indirizzi lineari a 21 bit, fino a FFFF:FFFF (non è più una novità). Ne segue che è possibile referenziare direttamente indirizzi all'interno della HMA tramite normali puntatori far o huge (pag. 22). Ad esempio l'istruzione
#include <dos.h> // per MK_FP()
.
register i;
unsigned char *cPtr;
for(i = 0x10; i < 0x20; i++)
*(unsigned char far *)MK_FP(0xFFFF,i) = *cPtr++;
copia 16 byte da un indirizzo in memoria convenzionale (referenziato da un puntatore near) all'inizio della HMA. I servizi XMS 05h e 06h, del tutto analoghi ai servizi 03h e 04h, consentono di attivare e, rispettivamente, disattivare la A20 line per indirizzare direttamente, mediante puntatori lineari a 32 bit, circa 1 Mb di memoria estesa (al di fuori della HMA).
Appunti su: |
|