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[1].
Alcuni cenni di carattere tecnico sono, tuttavia, indispensabili.
Il compilatore C
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 libreriaC:
malloc(), realloc(), free() e, in sostanza,
tutte le funzioni dichiarate nel file ALLOC.H (o MALLOC.H)[2].
Dall'altro lato troviamo le funzioni basate sui servizi di allocazione
della memoria resi disponibili dall'int21h[3]:
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
48h
BX
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
49h
ES
Segmento dell'indirizzo dell'area da liberare
Output
AX
Codice di errore, se il CarryFlag = 1.
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
4Ah
BX
Nuova dimensione in paragrafi del blocco
ES
Segmento dell'indirizzo del blocco da modifcare
Output
AX
Codice di errore, se CarryFlag = 1. 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.
Memoria convenzionale
La memoria convenzionale è costituita
dai primi 640 Kb (o meno di 640) di RAM installati sulla macchina: essi
sono compresi tra gli indirizzi 0000:0000 e 9FFF:000F[4]. 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 7.
Il
primo Kilobyte, dall'indirizzo 0000:0000 (origine) a 003F:000F,
è occupato dalla tavola dei vettori.
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 0070:0000
è 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[5].
Tutti i restanti oggetti evidenziati in figura 7 (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[6]. 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[7].
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 8 ne descrive la struttura.
Come si vede, ciascuno di essi ha ampiezza pari a un paragrafo (16 byte)
ed è suddiviso in campi.
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' [8].
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[9]. 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 6 e 8 indicano che l'area
è riservata al DOS; in particolare, 8 è il valore
che assume il campo PSP del MCB dell'area allocata ai device driver.
Detto MCB è, tra l'altro, il primo della
catena[10].
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)[11].
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[12] 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 32, esadecimale 20). 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
256 (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[13]:
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)
{
union REGS regs;
struct SREGS sregs;
regs.h.ah = 0x34;
intdos(®s,®s);
segread(&sregs);
return(sregs.es);
}
Circa la parola chiave cdecl vedere il capitolo
dedicato alle funzioni. 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)
{
_AX = 0x34;
asm int 21h;
return(_ES);
}
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)
{
union REGS regs;
struct SREGS sregs;
regs.h.ah = 0x52;
intdos(®s,®s);
segread(&sregs);
return(*(unsigned far *)MK_FP(sregs.es,regs.x.bx-2));
}
La macro MK_FP() è descritta in
tema di puntatori. 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)
{
asm mov ah,52h;
asm int 21h;
asm mov ax,es:[bx-2];
return(_AX);
}
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:
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)
{
asm push ds;
#if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__)
asm push ds; // il compilatore assume che nei modelli "piccoli"
asm pop es; // SS e DS coincidano, percio' si puo' usare DS
asm mov di,mcb;
#else
asm les di,dword ptr mcb; // modello dati "grandi": mcb e' una doubleword
#endif
asm mov ds,mcbseg; // DS:SI punta al Memory Control Block
asm xor si,si; // ES:DI punta alla struttura mcb
asm mov cx,8; // copia 8 words (16 bytes)
asm cld;
asm cli;
asm rep movsw;
asm sti;
asm pop ds;
return(mcbseg+1);
}
Lo inline assembly rende il codice compatto e veloce[14];
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)
{
register i;
unsigned mcbseg;
struct MCB *mcb;
if(!(mcb = (struct MCB *)malloc(sizeof(struct MCB))))
return(NULL);
mcbseg = parsemcb(mcb,basemcb);
mcbseg += mcb->dim; // segmento MCB + 1 + dimensione MCB = segmento
i = 1; // del MCB successivo
do {
if(!(mcb = (struct MCB *)realloc(mcb,(i+1)*sizeof(struct MCB))))
return(NULL);
mcbseg = parsemcb(mcb+i,mcbseg);
mcbseg += mcb[i].dim;
} 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
58h
AL
00h: ottiene la strategia di allocazione
01h: determina la strategia di allocazione
BX
solo per AL=01h (set strategy):
0 : FirstFit
1 : BestFit
>= 2 : LastFit
Output
AX
codice di errore se CarryFlag = 1; altrimenti:
solo per AL = 00h (get strategy):
0 : FirstFit
1 : BestFit
>= 2 : 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)
{
_AX = 0x5800;
asm int 21h;
asm jnc EXITFUNC;
_AX = -1;
EXITFUNC:
return(_AX);
}
Per un esempio di modifica della strategia DOS di allocazione vedere il
paragrafo dedicato all'utilizzo dei vettori di
interrupt come puntatori.
Upper memory
Il metodo di puntamento basato sulla coppia segmento:offset
consente al DOS di indirizzare, su macchine a 16 bit, un
megabyte di RAM[15]. I 384 Kb compresi tra i primi 640 Kb e il Mb sono,
di norma, utilizzati come indirizzi per il ROMBIOS o sue estensioni,
per la gestione del video, etc.: uno schema è riprodotto in figura
9.
Gli
indirizzi compresi tra C000:0 e EFFF:000F sono disponibili
per le estensioni ROMBIOS: 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
ROMBIOS, 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[16]). 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 8; 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[17] dell'indirizzo
di un successivo MCB[18], 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[19]. 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[20]. 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)
{
unsigned segbase, umbseg;
struct MCB umb; // struct MCB e parsemcb() sono gia' note
segbase = ((*(unsigned far *)0x413)*64)-1;
parsemcb(&umb,segbase);
if((umb.pos == 'M') && (umb.psp == 0x0008)) {
parsemcb(&umb,umbseg = segbase+umb.dim);
if(((umb.pos == 'M') || (umb.pos == 'Z')) &&
(umb.psp == umbseg+1) && (!strncmp(umb.name,"UMB ",8)))
return(segbase);
}
return(NULL);
}
La testforDOS() ipotizza che gli utlimi 16 byte di memoria
convenzionale[21] siano un MCB: se il presunto campo POS vale
'M' e il presunto PSP è 8, 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() 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[22]. 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)
{
asm {
mov ax,0D200h;
mov bx,05144h;
mov cx,04D45h;
mov dx,04D30h;
int 2Fh;
cmp ax,0D2FFh;
jne NOTQEMM386;
cmp bx,04D45h;
jne NOTQEMM386;
cmp cx,04D44h;
jne NOTQEMM386;
cmp dx,05652h;
jne NOTQEMM386;
mov ax,0D201h;
mov bx,04849h;
mov cx,05241h;
mov dx,04D30h;
int 0x2F;
cmp ax,0D201h;
jne NOTQEMM386;
cmp bx,04F4Bh;
jne NOTQEMM386;
jmp EXITFUNC;
}
NOTQEMM386:
asm xor cx,cx;
EXITFUNC:
return(_CX);
}
La testforQEMM386() invoca due volte l'int 2Fh. Nella prima richiede
il servizio 0 (AL = 0; 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, il
paragrafo dedicato). 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 = 1; 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.
La memoria espansa consiste in pagine[24]
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
LIM4.0[25] 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 relativo alla gestione dei file su disco),
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 (figura10); 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.
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[26] 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 */
{
int handle, retcode;
struct REGPACK r;
if((handle = open("EMMXXXX0",O_RDONLY)) == -1) // apre handle
return(NULL);
retcode = NULL;
r.r_ax = 0x4400; // e' proprio un device?
r.r_bx = handle;
intr(0x21,&r);
if(!(r.r_flags & 1) && (r.r_dx & 0x80)) {
r.r_ax = 0x4407; // il device e' ready?
intr(0x21,&r);
if(!(r.r_flags & 1) && (r.r_ax & 0xFF))
retcode = 1;
}
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 = 1) e non un file[27].
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. Vediamo una seconda versione di testEMM(),
più snella della precedente[28].
#include <dos.h>
#include <string.h>
#define EMMNAME "EMMXXXX0"
int cdecl testEMM2(void)
{
struct REGPACK r;
r.r_ax = 0x3567; // richiede il vettore dell'int 67h
intr(0x21,&r);
return(!_fstrncmp((char far *)MK_FP(r.r_es,10),EMMNAME,strlen(EMMNAME)));
}
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
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
0 se le stringhe confrontate sono uguali, cioè nel caso
in cui il driver sia installato: l'operatore di not
logico "!" capovolge il risultato, perciò
anche questa versione di testEMM() restituisce un valore non nullo
se il driver è presente e 0 se non lo è (circa MK_FP()
vedere il paragrafo sui puntatori.
La versione del gestore EMM installato è ottenibile via int 67h,
servizio 46h, sul quale si basa la getEMMversion().
Versione e revisione del gestore EMM. La versione è
nei bit 47, 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 */
{
asm {
mov ah,0x46;
int 0x67;
cmp ah,0;
je SETVER;
mov al,ah;
xor ah,ah;
neg ax;
jmp EXITFUNC;
}
SETVER:
asm {
mov cx,4;
mov ah,al;
shr al,cl;
and ax,0F0Fh;
}
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[29]. 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.
/********************
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)
{
asm {
mov ah,0x41;
int 0x67;
cmp ah,0;
je EXITFUNC;
mov al,ah;
xor ah,ah;
neg ax;
mov bx,ax;
}
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.).
/********************
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)
{
struct REGPACK r;
r.r_ax = 0x4200;
intr(0x67,&r);
if(!(r.r_ax & 0xFF00))
return(r.r_dx);
return(-((r.r_ax >> 8) & 0xFF));
}
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[30]. 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)
{
struct REGPACK r;
r.r_ax = 0x4200;
intr(0x67,&r);
if(!(r.r_ax & 0xFF00))
return(r.r_bx);
return(-((r.r_ax >> 8) & 0xFF));
}
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)
{
struct REGPACK r;
r.r_ax = 0x4B00;
intr(0x67,&r);
if(!(r.r_ax & 0xFF00))
return(r.r_bx);
return(-((r.r_ax >> 8) & 0xFF));
}
Anche la getEMMopenHandles() in caso di errore restituisce il
codice d'errore EMS cambiato di segno (negativo); un valore maggiore o
uguale a 0 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 { // struttura per i dati relativi agli handles
int handle; // n. dello handle per Expanded Memory
unsigned pages; // pagine assegnate allo handle
};
L'array di strutture deve essere allocato a cura del programmatore, ad
esempio con una chiamata a malloc():
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
{
struct REGPACK r;
r.r_ax = 0x4D00;
#if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__)
r.r_es = _DS;
r.r_di = (unsigned)emmHnd;
#else
r.r_es = FP_SEG(emmHnd);
r.r_di = FP_OFF(emmHnd);
#endif
intr(0x67,&r);
if(!(r.r_ax & 0xFF00))
return(r.r_bx);
return(-((r.r_ax >> 8) & 0xFF));
}
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
53h
AL
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).
/********************
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)
{
#if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__)
asm push ds;
asm pop es;
asm mov di,EMMhname;
#else
asm les di,dword ptr EMMhname;
#endif
asm mov dx,EMMhandle;
asm mov ax,0x5300;
asm int 0x67;
asm cmp ah,00h;
asm jne EXITERROR;
EMMhname[8] = NULL;
return(0);
EXITERROR:
asm mov al,ah;
asm xor ah,ah;
asm neg ax;
return(_AX);
}
La getEMMhandleName() utilizza il servizio descritto (subfunzione
0) per conoscere il nome associato allo handle EMMhandle. Il buffer
EMMhname deve comprendere 9 byte[31]:
nei primi 8 è copiato il nome, mentre l'ultimo è valorizzato
a NULL per costruire una normale stringa (circa l'uso di DS
vedere gli esempi di funzione per la memoria convenzionale).
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)
{
#if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__)
asm mov si,EMMhname;
#else
asm push ds;
asm lds si,dword ptr EMMhname;
#endif
asm mov dx,EMMhandle;
asm mov ax,0x5301;
asm int 0x67;
asm mov al,ah;
asm cmp ax,00h;
asm je EXITFUNC;
asm xor ah,ah;
asm neg ax;
EXITFUNC:
#if defined(__COMPACT__) || defined(__LARGE__) || defined(__HUGE__)
asm pop ds;
#endif
return(_AX);
}
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[32]. L'allocazione delle pagine logiche è effettuata
dal servizio 43h dell'int 67h:
INT 67H, SERV. 43H: ALLOCA PAGINE LOGICHE NELLA MEMORIA ESPANSAInt 67h,
serv. 43h: Alloca pagine logiche nella memoria espansa
/********************
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)
{
asm mov ah,043h;
asm mov bx,pages;
asm int 067h;
asm cmp ah,00h;
asm je EXITFUNC;
asm mov dl,ah;
asm xor dh,dh;
asm neg dx;
EXITFUNC:
return(_DX);
}
La allocEMMpages() restituisce lo handle associato alle pagine
allocate: si tratta di un numero maggiore di 0. 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 0 è riservato al sistema operativo. Un
valore minore di zero, cambiato di segno, rappresenta invece un normale
codice di errore EMS.
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
44h
AL
numero della pagina fisica su cui mappare la pagina logica
BX
numero della pagina logica da mappare su quella fisica
DX
handle associato alla pagina logica (o al gruppo di pagine
logiche di cui questa fa parte)
/********************
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)
{
asm mov dx,EMMhandle;
asm mov bx,logicalPage;
asm mov al,byte ptr physicalPage;
asm mov ah,044h;
asm int 067h;
asm mov al,ah;
asm cmp ax,00h;
asm je EXITFUNC;
asm xor ah,ah;
asm neg ax;
EXITFUNC:
return(_AX);
}
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[33] alla pagina fisica 0
la pagina logica 2 associata ad un certo handle e memorizzare
in quella pagina fisica i nostri dati, secondo necessità. Successivamente
è possibile mappare alla stessa pagina fisica 0 un'altra
pagina logica, associata o no al medesimo handle. I dati che erano presenti
nella pagina fisica 0 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 0 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
45h
DX
handle associato alla pagina logica (o gruppo di pagine logiche)
da deallocare (sono sempre disallocate tutte le pagine logiche associate
allo handle)
/********************
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)
{
asm mov ah,045h;
asm mov dx,EMMhandle;
asm int 067h;
asm mov al,ah;
asm cmp ax,00h;
asm je EXITFUNC;
asm xor ah,ah;
asm neg ax;
EXITFUNC:
return(_AX);
}
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 0 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
5
#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)
{
register i, j;
int retcode;
unsigned frameSeg;
char far *physicalPage0;
char strBuf[80];
int EMMhandles[2];
static char *EMMhandleNames[] = {"Handle_1","Handle_2"};
frameSeg = getEMMframeAddr();
physicalPage0 = (char far *)MK_FP(frameSeg,0);
printf("Page Frame Address: %Fp\n",physicalPage0);
for(i = 0; i < 2; i++) {
if((EMMhandles[i] = allocEMMpages(2)) <= 0) {
printf("Error %X allocating 2 EMM pages.\n",-EMMhandles[i]);
break;
}
if(retcode = setEMMhandleName(EMMhandles[i],EMMhandleNames[i])) {
printf("Error %X setting name of handle %d.\n",-retcode,EMMhandles[i]);
break;
}
}
if(i < 2) {
for(j = 0; j < i; j++)
if(retcode = freeEMMhandle(EMMhandles[j]))
printf("Error %X deallocating EMM pages of handle %d.\n",-retcode,
EMMhandles[j]);
return;
}
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: %s\n",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: %s\n",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 0.
Si tratta, ovviamente, di un indirizzo del tipo seg:off,
che viene ottenuto "accostando", mediante la macro MK_FP()
definita in DOS.H (e descritta a proposito
di puntatori), 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 2 pagine logiche; 2
è 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 0 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 2,
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 0
sulla pagina fisica 0. Da questo momento tutte le operazioni effettuate
sulla pagina fisica 0 (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 0, 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 0
la stringa "@@@ A String For Logical Page 0 @@@". Immediatamente
dopo, la seconda chiamata a mapEMMpages() mappa alla solita pagina
fisica 0 la seconda (1) delle due (0 e 1)
pagine logiche associate al secondo handle (EMMhandles[1]) e la
_fstrcpy() scrive nella pagina fisica 0 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 0 la prima pagina logica (0)
delle due (0 e 1) 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 0. L'operazione è necessaria perché printf(),
nell'ipotesi di compilare il programma con modello di memoria tiny,
small o medium
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 a farne un
uso poco corretto[34]. 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
4Eh
AL
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
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)
{
asm mov ax,04E03h;
asm int 067h;
asm mov bl,al;
asm mov al,ah;
asm xor ah,ah;
asm cmp ax,0;
asm je EXITFUNC;
asm neg ax;
return(_AX);
EXITFUNC:
return(_BL);
}
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 gli esempi
di funzione per la memoria convenzionale).
/********************
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)
{
#if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__)
asm push ds;
asm pop es;
asm mov di,destBuf;
#else
asm les di,dword ptr destBuf;
#endif
asm mov ax,04E00h;
asm int 067h;
asm mov al,ah;
asm cmp ax,00h;
asm je EXITFUNC;
asm xor ah,ah;
asm neg ax;
EXITFUNC:
return(_AX);
}
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)
{
#if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__)
asm mov si,sourceBuf;
#else
asm push ds;
asm lds si,dword ptr sourceBuf;
#endif
asm mov ax,04E00h;
asm int 067h;
asm mov al,ah;
asm cmp ax,00h;
asm je EXITFUNC;
asm xor ah,ah;
asm neg ax;
EXITFUNC:
#if defined(__COMPACT__) || defined(__LARGE__) || defined(__HUGE__)
asm pop ds;
#endif
return(_AX);
}
Per un esempio di funzione basata sulla subfunzione 02h, vedere quanto
detto circa i TSR.
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
57h
AL
00h trasferisce da memoria convenzionale a memoria
EMS
DS:SI = punta al buffer di info per il trasferimento
Il formato del buffer richiesto dal servizio analizzato è il seguente:
FORMATO DEL BUFFER UTILIZZATO DALL'INT 67H, SERV. 57H
OFFSET
byte
DESCRIZIONE
00h
4
Lunghezza in byte dell'area di memoria da trasferire
04h
1
Tipo della memoria sorgente (0 = convenzionale;
1 = EMS)
05h
2
Handle EMS sorgente (0 se memoria convenzionale)
07h
2
Offset dell'area sorgente (rispetto al segmento se memoria
convenzionale; rispetto alla pagina logica se memoria EMS)
09h
2
Segmento dell'area sorgente (se memoria convenzionale) o
pagina logica (se memoria EMS)
0Bh
1
Tipo della memoria destinazione (0 = convenzionale;
1 = EMS)
0Ch
2
Handle EMS destinazione (0 se memoria convenzionale)
0Eh
2
Offset dell'area destinazione (rispetto al segmento se memoria
convenzionale; rispetto alla pagina logica se memoria EMS)
10h
2
Segmento dell'area destinazione (se memoria convenzionale)
o pagina logica (se memoria EMS)
Il buffer può essere facilmente rappresentato con una struttura:
struct EMMmove {
unsigned long length;
unsigned char sourceType;
unsigned int sourceHandle;
unsigned int sourceOffset;
unsigned int sourceSegment;
unsigned char destType;
unsigned int destHandle;
unsigned int destOffset;
unsigned int destSegment;
};
L'esempio seguente copia direttamente 32 Kb dal buffer video colore (B800:0000)
ad offset 256 nella terza pagina del blocco EMS allocato allo
handle 09h.
Per effettuare l'operazione inversa è sufficiente scambiare tra
loro i valori dei campi "source..." e "dest..."
omologhi nella struttura EMMinfoBlock.
Vediamo un esempio di funzione basata sul servizio testè descritto.
/********************
BARNINGA_Z! - 1992
EMMMOVM.C - EMMmoveMem()
int EMMmoveMem(struct EMMmove *EMMbuffer,int operation);
struct EMMmove *EMMbuffer; puntatore al buffer che descrive l'operaz.
int operation; 0 = sposta il contenuto della memoria
1 = scambia il contenuto di due aree di
memoria
Restituisce: 0 se l'operazione e' stata eseguita correttamente.
-1 parametro operation errato
> 0 e' il codice di errore EMS.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- emmmovm.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
int cdecl EMMmoveMem(struct EMMmove *EMMbuffer,int operation)
{
if((operation < 0) || (operation > 1))
return(-1);
_AL = (char)operation;
_AH = 0x57;
#if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__)
asm mov si,EMMbuffer;
#else
asm push ds;
asm lds si,dword ptr EMMbuffer;
#endif
asm int 067h;
#if defined(__COMAPCT__) || defined(__LARGE__) || defined(__HUGE__)
asm pop ds;
#endif
asm xor al,al;
asm cmp ah,0;
asm je EXITFUNC;
asm xchg ah,al;
EXITFUNC:
return(_AX);
}
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
= 0) o di scambio del contenuto di due aree (operation
= 1). La funzione restituisce 0 se non si è verificato
alcun errore, mentre è restituito 1 se il parametro
operation contiene un valore illecito; la restituzione di un valore
maggiore di 0 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
50h
AL
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
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) {
.... // Gestione dell'errore (AH != 0)
Il codice dell'esempio richiede il mapping di tre pagine logiche (la 4,
la 6 e la 7, associate allo handle 09h) rispettivamente sulle pagine fisiche
0, 3 e 1. Gli utlimi due elementi dell'array info non vengono
inizializzati, in quanto il servizio conosce (attraverso r.r_cx)
il numero di pagine da mappare.
Per la subfunzione 01h varia leggermente il significato dei valori contenuti
nel buffer: la seconda word di ogni coppia contiene l'indirizzo di segmento
a cui mappare la pagina logica specificata nella prima. Il limite evidente
è che il mapping multiplo di pagine logiche EMS su memoria convenzionale
può essere effetuato solo ad indirizzi il cui offset (implicito)
è 0.
A completamento di queste note sulla memoria espansa e la sua gestione
in C vale la pena di elencare i codici restituiti in AH dall'int
67h in uscita dai diversi servizi che esso implementa. La tabella contiene
solamente i codici relativi ai servizi descritti.
Richiesta l'allocazione di 0 pagine (allocazione
fallita)
8Ah
Numero di pagina logica fuori dall'intervallo valido per
lo handle
8Bh
Numero di pagina fisica fuori dall'intervallo valido (03)
8Ch
Memoria insufficiente per gestire il mapping
8Dh
Errore nella gestione dei dati di mapping (in fase di salvataggio)
8Eh
Errore nella gestione dei dati di mapping (in fase di ripristino)
8Fh
Subfunzione non supportata
92h
Trasferimento avvenuto; parte della regione sorgente è
stata sovrascritta
93h
E' stato richiesto il trasferimento di un'area di dimensioni
maggiori della memoria allocata
94h
L'area di memoria convenzionale e l'area di memoria espansa
si sovrappongono
95h
L'offset indicato è fuori dall'intervallo valido
96h
La lunghezza indicata è maggiore di un Mb
97h
Trasferimento non avvenuto: le aree sorgente e destinazione
si sovrappongono
98h
Il tipo di memoria specificato non è valido (deve
essere 0 o 1)
A2h
L'indirizzo di memoria iniziale sommato alla lunghezza supera
il Mb
A3h
Puntatore non valido o contenuto dell'array sorgente corrotto
Memoria estesa, High Memory Area e UMB
E' detta memoria estesa la RAM oltre il primo megabyte: essa è indirizzabile
linearmente dai programmi solo se operanti in modalità
protetta[35]; in modalità reale l'accesso alla memoria estesa
è possibile, grazie a un driver XMM (eXpanded Memory Manager) in
grado di fornire i servizi XMS[36]. I primi
64 Kb (meno 16 byte) di memoria estesa costituiscono la High Memory Area
(HMA), che occupa gli indirizzi FFFF:0010FFFF:FFFF[37];
questa è indirizzabile da una coppia di registri a 16 bit,
quando laA20 Line è abilitata[38].
I servizi XMS per la memoria estesa
Per utilizzare i servizi XMS occorre innanzitutto determinare, mediante
l'int 2Fh, se un driver XMM è installato e, in caso affermativo,
individuare l'indirizzo (entry point) della routine di servizio driver.
INT 2FH: PRESENZA DEL DRIVER XMM
Input
AX
4300h
Output
AL
80h se il driver è installato.
Note
Non si tratta di un servizio XMS.
INT 2FH: ENTRY POINT DEL DRIVER XMM
Input
AX
4310h
Output
ES:BX
Indirizzo (seg:off) del driver XMM.
Note
L'indirizzo restituito in ES:BX è l'entry
point del driver, cioè l'indirizzo al quale il programma che richiede
un servizio XMS deve trasferire il controllo.
Ecco un esempio di utilizzo delle chiamate descritte.
/********************
BARNINGA_Z! - 1992
XMMADDR.C - getXMMaddress()
void (far *cdecl getXMMaddress(void))(void);
Restituisce: l'entry point del driver XMM. Se non è installato alcun
driver XMM restituisce NULL.
COMPILABILE CON TURBO C++ 2.0
tcc -O -d -c -mx -k- xmmaddr.c
dove -mx puo' essere -mt -ms -mc -mm -ml -mh
********************/
#pragma inline
#pragma option -k-
#include <stdio.h> /* per NULL */
void (far *cdecl getXMMaddress(void))(void)
{
_AX = 0x4300;
asm int 2Fh;
if(_AL != 0x80)
return((void(far *)(void))NULL);
_AX = 0x4310;
asm int 2Fh;
return((void _es *)_BX);
}
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
Versione XMS supportata.
AL
Revisione XMS supportata.
BH
Versione del driver XMM.
BL
Revisione del driver XMM.
DX
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.
/********************
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))
{
_AH = 0;
(*XMMdriver)();
asm cmp ax,0;
asm jne EXITFUNC;
asm xor bh,bh;
asm neg bx;
return(_BX);
EXITFUNC:
asm xchg ah,al;
return(_AX);
}
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
/********************
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))
{
_AH = 0;
(*XMMdriver)();
asm cmp ax,0;
asm jne EXITFUNC;
asm xor bh,bh;
asm neg bx;
return(_BX);
EXITFUNC:
asm xchg bh,bl;
return(_BX);
}
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&AGRAVE; DI MEMORIA XMS DISPONIBILE
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))
{
_AH = 8;
(*XMMdriver)();
asm cmp ax,0;
asm jne EXITFUNC;
asm xor bh,bh;
asm neg bx;
asm mov dx,bx;
EXITFUNC:
return((long)_DX);
}
/********************
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))
{
_AH = 8;
(*XMMdriver)();
asm cmp ax,0;
asm jne EXITFUNC;
asm xor bh,bh;
asm neg bx;
asm mov ax,bx;
EXITFUNC:
return((long)_AX);
}
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.
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)
{
asm {
mov al,17h;
out 70h,al; // prepara lettura CMOS offset 17h
jmp $+2; // introduce ritardo
in al,71h; // legge CMOS, byte ad offset 17h
mov ah,al; // salva byte letto
mov al,18h; // ripete procedura per byte ad offset 18h
out 70h,al;
jmp $+2;
in al,71h;
xchg ah,al; // ricostruisce la word (backwords)
}
return(_AX);
}
La quantità di memoria estesa installata e non gestita da un driver
XMM è conoscibile attraverso l'int15h[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)
{
_AH = 0x88;
asm int 15h;
return(_AX);
}
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 è evidente.
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)
{
_DX = EMBkb;
_AH = 9;
(*XMMdriver)();
asm cmp ax,0;
asm jne EXITFUNC;
asm xor bh,bh;
asm neg bx;
asm mov dx,bx;
EXITFUNC:
return(_DX);
}
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
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 {
unsigned long length; // lunghezza in bytes dell'area
unsigned srcHandle; // handle EMB sorgente (0 se mem. convenzionale)
long srcOffset; // offset nell'EMB sorgente o far pointer a mem. convenzionale
unsigned dstHandle; // handle EMB destinaz. (0 se mem. convenzionale)
long dstOffset; // offset nell'EMB destinaz. o far pointer a mem. convenzionale
};
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)
{
#if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__)
asm mov si,EMBbuffer;
#else
asm push ds;
asm lds si,dword ptr EMBbuffer;
#endif
_AH = 0x0B;
(*XMMdriver)();
#if defined(__COMAPCT__) || defined(__LARGE__) || defined(__HUGE__)
asm pop ds;
#endif
asm cmp ax,0;
asm je EXITFUNC;
return(0);
EXITFUNC:
asm xor bh,bh;
asm neg bx;
return(_BX);
}
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).
SERVIZIO XMS 0AH: DEALLOCA UNO HANDLE XMS
Input
AH
0Ah
DX
Handle XMS da deallocare
Output
AX
1 se la disallocazione è riuscita correttamente.
Tutta la memoria estesa a cui è associato lo handle è liberata.
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)
{
_DX = EMBhandle;
_AH = 0x0A;
(*XMMdriver)();
asm cmp ax,0;
asm je EXITFUNC;
return(0);
EXITFUNC:
asm xor bh,bh;
asm neg bx;
return(_BX);
}
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
0Fh
BX
Nuova dimensione richiesta in Kilobyte
DX
Handle XMS associato all'EMB
Output
AX
1 se la disallocazione è riuscita correttamente.
Tutta la memoria estesa a cui è associato lo handle è liberata.
/********************
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)
{
_DX = EMBhandle;
_BX = newKb;
_AH = 0x0F;
(*XMMdriver)();
asm cmp ax,0;
asm je EXITFUNC;
return(0);
EXITFUNC:
asm xor bh,bh;
asm neg bx;
return(_BX);
}
La resizeEMB() restituisce un valore secondo criteri coerenti
con quelli adottati dalle funzioni sopra listate[43].
I servizi XMS per la HMA
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))
{
_AH = 0;
(*XMMdriver)();
asm cmp ax,0;
asm jne EXITFUNC;
asm xor bh,bh;
asm neg bx;
return(_BX);
EXITFUNC:
return(_DX);
}
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
01h
DX
Byte necessari al programma nella HMA (valori 0FFF0).
Output
AX
1 se la HMA è stata allocata con successo.
BL
In caso di fallimento, AX è 0 e
BL contiene il codice di errore XMS:
se BL = 91h, la HMA è già allocata.
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&AGRAVE;
ALLOCATA
Input
AH
4Ah
AL
@
@
01h richiede quanti byte sono ancora liberi
nella HMA
02h alloca spazio nella HMA
BX numero di byte richiesti
Output
ES:DI
Indirizzo del primo byte libero nella HMA (FFFF:FFFF
in caso di errore).
BX
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.
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))
{
_AH = 1;
_DX = 0xFFF0;
(*XMMdriver)();
asm cmp ax,0;
asm jne DEALLOC;
asm cmp bl,091h;
asm jne ERROR;
return(0); // HMA gia' allocata
ERROR:
asm xor bh,bh;
asm neg bx;
return(_BX);
DEALLOC:
_AH = 2;
(*XMMdriver)();
asm cmp ax,0;
asm jne EXITFUNC;
asm jmp ERROR;
EXITFUNC:
return(1);
}
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)
{
_DX = nBytes;
_AH = 1;
(*XMMdriver)();
asm cmp ax,0;
asm jne EXITFUNC;
asm xor bh,bh;
asm neg bx;
return(_BX); // HMA gia' allocata o errore
EXITFUNC:
return(0);
}
/********************
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))
{
_AH = 2;
(*XMMdriver)();
asm cmp ax,0;
asm jne EXITFUNC;
asm xor bh,bh;
asm neg bx;
return(_BX);
EXITFUNC:
return(0);
}
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
1 se la A20 Line è abilitata, 0
se non lo è (in questo caso anche BL = 0).
/********************
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))
{
_AH = 7;
(*XMMdriver)();
asm cmp ax,0;
asm jne EXITFUNC;
asm cmp bl,0;
asm je EXITFUNC;
asm xor bh,bh;
asm neg bx;
asm mov ax,bx;
EXITFUNC:
return(_AX);
}
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].
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))
{
_AH = 3;
(*XMMdriver)();
asm cmp ax,0;
asm je EXITFUNC;
return(0);
EXITFUNC:
asm xor bh,bh;
asm neg bx;
return(_BX);
}
/********************
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))
{
_AH = 4;
(*XMMdriver)();
asm cmp ax,0;
asm je EXITFUNC;
return(0);
EXITFUNC:
asm xor bh,bh;
asm neg bx;
return(_BX);
}
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.
I servizi XMS per gli UMB
Riportiamo la descrizione dei servizi XMS (e gli esempi relativi) che consentono
di allocare e deallocare gli Upper Memory Block.
SERVIZIO XMS 0FH: ALLOCA UN UMB
Input
AH
10h
DX
Dimensione richiesta in paragrafi (blocchi di 16 byte)
Output
AX
1 se la disallocazione è riuscita correttamente.
BX
Indirizzo di segmento dell'UMB allocato
DX
Dimensione reale in paragrafi
Note
In caso di fallimento, AX è 0 e
BL contiene il codice di errore XMS,
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)
{
_DX = UMBkb;
_AH = 0x10;
(*XMMdriver)();
#if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__)
asm mov si,UMBseg;
#else
asm push ds;
asm lds si,dword ptr UMBseg;
#endif
asm cmp ax,0;
asm je EXITFUNC;
asm mov [si],bx;
#if defined(__COMPACT__) || defined(__LARGE__) || defined(__HUGE__)
asm pop ds;
#endif
return(0);
EXITFUNC:
asm mov [si],dx;
asm xor bh,bh;
asm neg bx;
#if defined(__COMPACT__) || defined(__LARGE__) || defined(__HUGE__)
asm pop ds;
#endif
return(_BX);
}
SERVIZIO XMS 11H: DEALLOCA UN UN UMB
Input
AH
11h
DX
Indirizzo di segmento dell'UMB
Output
AX
1 se la disallocazione è riuscita correttamente.
L'UMB è nuovamente libero
/********************
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)
{
_DX = UMBseg;
_AH = 0x11;
(*XMMdriver)();
asm cmp ax,0;
asm je EXITFUNC;
return(0);
EXITFUNC:
asm xor bh,bh;
asm neg bx;
return(_BX);
}
Un UMB allocato con allocUMB() può essere referenziato
mediante un puntatore far o huge, costruito con la macro
MK_FP() (descritta con riferimento ai
puntatori):