Gestione a basso livello della memoria

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

La memoria convenzionaleIl 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. 

Struttura dei Memory Control Block

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(&regs,&regs);
    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(&regs,&regs);
    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: 
struct MCB {
    char     pos;
    unsigned psp;
    unsigned dim;
    char     reserved[3];
    char     name[8];
};
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 ROM­BIOS o sue estensioni, per la gestione del video, etc.: uno schema è riprodotto in figura 9

La Upper MemoryGli indirizzi compresi tra C000:0 e EFFF:000F sono disponibili per le estensioni ROM­BIOS: possono, cioè, essere utilizzati dalle schede di supporto per il networking o per particolari periferiche (fax, scanner, etc.). Ad esempio, l'intervallo che si estende da da C000:0 a C7FF:000F è di norma occupato dal BIOS delle schede VGA; inoltre molti calcolatori tipo notebook o laptop dispongono di estensioni ROM­BIOS, spesso dedicate al controllo del video LCD, nel range da E000:0 a EFFF:000F

Gli indirizzi non occupati possono essere impiegati per simulare l'esistenza di aree di memoria che il DOS gestisce in modo analogo a quelle presenti nella memoria convenzionale: a tal fine è indispensabile un driver in grado di rimappare gli indirizzi tra A000:0 e FFFF:000F alla memoria fisicamente presente, in quanto a detti indirizzi non corrisponde RAM installata. Tali driver utilizzano, per effettuare il remapping, memoria espansa: ne consegue la necessità che sulla macchina ne sia installata una quantità sufficiente (almeno pari all'ampiezza totale delle aree da simulare[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. 

Gli UMB sono definiti dalla XMS (eXtended Memory Services) Specification, a cui si rimanda per la descrizione dei servizi che consentono la loro allocazione e deallocazione[23]

Memoria espansa 

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

Memoria EMS

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

INT 67H, SERV. 46H: VERSIONE EMM 
Input  AH  46h 
Output  AH  Stato dell'operazione (errore se != 0). 
AL  Versione e revisione del gestore EMM. La versione è nei bit 4­7, la revisione nei bit 0-3. 
/********************

    BARNINGA_Z! - 1991

    EMMVER.C - getEMMversion()

    unsigned cdecl getEMMversion(void);
    Restituisce: la versione del driver EMM (versione nel byte meno
                 significativo, revisione nel byte piu' significativo:
                 4.0 e' restituito come 0x0004).
                 Se si e' verificato un errore restituisce un numero
                 negativo (il codice d'errore EMS cambiato di segno).

    COMPILABILE CON TURBO C++ 2.0

        tcc -O -d -c -mx -k- emmver.c

    dove -mx puo' essere -mt -ms -mc -mm -ml -mh

********************/
#pragma  inline
#pragma  option -k-

unsigned cdecl getEMMversion(void)      /* ottiene la versione LIM */
{
    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. 

INT 67H, SERV. 41H: INDIRIZZO DELLA PAGE FRAME 
Input  AH  41h 
Output  AH  Stato dell'operazione (errore se != 0). 
BX  Indirizzo (segmento) della page frame. 
/********************

    BARNINGA_Z! - 1991

    EMMFRAME.C - getEMMframeAddr()

    unsigned cdecl getEMMframeAddr(void);
    Restituisce: l'indirizzo (segmento) della Page Frame.
                 Se si e' verificato un errore restituisce un numero
                 negativo (il codice d'errore EMS cambiato di segno).

    COMPILABILE CON TURBO C++ 2.0

        tcc -O -d -c -mx -k- emmframe.c

    dove -mx puo' essere -mt -ms -mc -mm -ml -mh

********************/
#pragma  inline
#pragma  option -k-      /* non indispensabile; accresce l'efficienza */

unsigned cdecl getEMMframeAddr(void)
{
    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.). 

INT 67H, SERV. 42H: NUMERO DI PAGINE 
Input  AH  42h 
Output  AH  Stato dell'operazione (errore se != 0). 
BX  Numero di pagine non allocate. 
DX  Numero totale di pagine EMS. 
/********************

    BARNINGA_Z! - 1991

    EMMTOTP.C - getEMMtotPages()

    int cdecl getEMMtotPages(void);
    Restituisce: il numero di pagine totali EMS
                 Se < 0 si e' verificato un errore; il valore cambiato di segno
                 e' il codice di errore EMS

    COMPILABILE CON TURBO C++ 2.0

        tcc -O -d -c -mx emmtotp.c

    dove -mx puo' essere -mt -ms -mc -mm -ml -mh

********************/
#include <dos.h>

int cdecl getEMMtotPages(void)
{
    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));
}
INT 67H, SERV. 4BH: NUMERO DI HANDLE EMM APERTI 
Input  AH  4Bh 
Output  AH  Stato dell'operazione (errore se != 0). 
BX  Numero di handle aperti. 
INT 67H, SERV. 4DH: PAGINE ALLOCATE AGLI HANDLE 
Input  AH  4Dh 
ES:DI  Buffer costituito da tante coppie di word quanti sono gli handle aperti. 
Output  AH  Stato dell'operazione (errore se != 0). 
BX  Numero di handle attivi. 

Le words dell'array in ES:DI sono riempite, alternativamente, con un numero di handle e il numero delle pagine allocate a quello handle. 
/********************

    BARNINGA_Z! - 1991

    EMMOHNDL.C - getEMMopenHandles()

    int cdecl getEMMopenHandles(void);
    Restituisce: il numero di handles EMS aperti
                 Se < 0 si e' verificato un errore; il valore cambiato di segno
                 e' il codice di errore EMS

    COMPILABILE CON TURBO C++ 2.0

        tcc -O -d -c -mx emmohndl.c

    dove -mx puo' essere -mt -ms -mc -mm -ml -mh

********************/
#include <dos.h>

int cdecl getEMMopenHandles(void)
{
    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()
    struct EMMhnd *emmHnd;
    unsigned oHnd;
    ....
    if((oHnd = getEMMopenHandles()) < 0) {
        ....   // gestione dell'errore EMS
    }
    else
        if(!(emmHnd = (struct EMMhnd *)malloc(oHnd*sizeof(struct EMMhnd)))) {
            ....   // gestione dell'errore di allocazione
        }
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));
}
Circa l'uso di DS nella funzione vedere gli esempi di funzione per la memoria convenzionale; le macro FP_SEG() e FP_OFF() sono descritte con riferimento ai puntatori

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). 
Output  AH  Stato dell'operazione (errore se != 0). 
/********************

    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 
Input  AH  43h 
BX  numero di pagine logiche che si desidera allocare 
Output  AH  Stato dell'operazione (errore se != 0). 
DX  Handle (se AX = 0
/********************

    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) 
Output  AH  Stato dell'operazione (errore se != 0). 
/********************

    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) 
Output  AH  Stato dell'operazione (errore se != 0). 
/********************

    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 
Output  AH  Stato dell'operazione (errore se != 0). 
AL  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 

01h scambia il contenuto di due aree di memoria 

    DS:SI = punta al buffer di info per lo scambio 
Output  AH  Stato dell'operazione (errore se != 0). 
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
#include <dos.h>
    ....
    struct REGPACK r;
    struct EMMmove EMMinfoBuf;
    ....
    EMMinfoBuf.length = 32*1024;   // 32 Kb
    EMMinfoBuf.sourceType = 0;     // sorgente: memoria convenzionale
    EMMinfoBuf.sourceHandle = 0;   // handle sempre 0 per mem. convenz.
    EMMinfoBuf.sourceOffset = 0;   // segmento:offset
    EMMinfoBuf.sourceSegment = 0xB800;     // B800:0000
    EMMinfoBuf.destType = 1;       // destinazione: memoria EMS
    EMMinfoBuf.destHandle = 0x09;  // handle (da int 67h, servizio 43h)
    EMMinfoBuf.destOffset = 256;   // offset all'interno della pag.logica
    EMMinfoBuf.destSegment = 2;    // terza pag.logica
    r.r_ax = 0x5700;
    r.r_ds = FP_SEG((struct EMMmove far *)&EMMinfoBuf);
    r.r_si = (unsigned)&EMMinfoBuf;
    intr(0x67,&r);
    if(r.r_ax & 0xFF00) {
        ....   // Gestione dell'errore (AH != 0)
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 

    DS:SI = punta al buffer di info per il mapping 
CX  Numero di pagine da mappare 
DX  Handle delle pagine logiche 
Output  AH  Stato dell'operazione (errore se != 0). 
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. 

CODICI DI ERRORE EMS 
AH
Significato del codice
00h
Operazione eseguita correttamente 
80h
Errore interno 
81h
Errore hardware 
82h
EMM già installato 
83h
Handle non valido 
84h
Funzione non supportata 
85h
Nessuno handle disponibile (allocazione fallita) 
86h
Errore nella gestione del mapping 
87h
Pagine EMS insufficienti (allocazione fallita) 
88h
Pagine EMS libere insufficienti (allocazione fallita) 
89h
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 (0­3) 
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:0010­FFFF: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 
    (*XMMdriver)();
è equivalente allo inline assembly 
    asm call dword ptr XMMdriver;
Circa i puntatori a funzione vedere il paragrafo dedicato
/********************

    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 
Input  AH  08h 
Output  AX  La dimensione in Kb del maggiore blocco libero. 
DX  La quantità totale, in Kb, di memoria XMS libera. 
Note  In caso di fallimento, AX è 0 e BL contiene il codice di errore XMS

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. 

SERVIZIO XMS 09H: ALLOCA UN BLOCCO DI MEMORIA XMS 
Input  AH  09h 
DX  La dimensione del blocco, in Kilobyte 
Output  AX  1 se l'allocazione è riuscita correttamente. 
DX  Lo handle associato al blocco. 
Note  In caso di fallimento, AX è 0 e BL contiene il codice di errore XMS
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 
Input  AH  0Bh 
DS:SI  Buffer descrittivo dell'operazione (vedere tabella seguente) 
Output  AX  1 se l'operazione di copia è riuscita correttamente. 
Note  In caso di fallimento, AX è 0 e BL contiene il codice di errore 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. 
Note  In caso di fallimento, AX è 0 e BL contiene il codice di errore XMS
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. 
Note  In caso di fallimento, AX è 0 e BL contiene il codice di errore XMS
Segue esempio: 
/********************

    BARNINGA_Z! - 1992

    EMBRESIZ.C - resizeEMB()

    int resizeEMB(void (far *XMMdriver)(void),unsigned EMBhandle,unsigned newKb);
    void (far *XMMdriver)(void);   puntatore all'entry point del driver XMM.
    unsigned EMBhandle;            handle XMS associato all'EMB da modificare.
    unsigned newKb;                nuova dimensione desiderata, in Kilobytes.
    Restituisce: 0 se l'operazione è stata eseguita con successo.
                 Se < 0 e' il codice di errore XMS.

    COMPILABILE CON TURBO C++ 2.0

        tcc -O -d -c -mx -k- embresiz.c

    dove -mx puo' essere -mt -ms -mc -mm -ml -mh

********************/
#pragma  inline

int cdecl resizeEMB(void (far *XMMdriver)(void),unsigned EMBhandle,unsigned newKb)
{
    _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 0­FFF0). 
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
SERVIZIO XMS 02H: DEALLOCA LA HMA 
Input  AH  02h 
Output  AX  1 se la HMA è stata liberata con successo. 
BL  In caso di fallimento, AX è 0 e BL contiene il codice di errore XMS
Note  Il programma che alloca la HMA deve disallocarla prima di restituire il controllo al sistema; in caso contrario la HMA rimane inutilizzabile fino al primo bootstrap. 
/********************

    BARNINGA_Z! - 1992

    ISHMAFRE.C - isHMAfree()

    int isHMAfree(void (far *XMMdriver)(void));
    void (far *XMMdriver)(void);   puntatore all'entry point del driver XMM.
    Restituisce: 1 se la HMA e' libera;
                 0 se gia' allocata;
                 Se < 0 e' il codice di errore XMS cambiato di segno.

    COMPILABILE CON TURBO C++ 2.0

        tcc -O -d -c -mx -k- ishmafre.c

    dove -mx puo' essere -mt -ms -mc -mm -ml -mh

********************/
#pragma  inline

int cdecl isHMAfree(void (far *XMMdriver)(void))
{
    _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). 
BL  In caso di fallimento, AX è 0 e BL contiene il codice di errore XMS
/********************

    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]

SERVIZIO XMS 03H: ABILITA LA A20 LINE 
Input  AH  03h 
Output  AX  1 se la A20 Line è stata attivata correttamente. 
Note  In caso di fallimento, AX è 0 e BL contiene il codice di errore XMS

Questo servizio deve essere utilizzato solo se il programma ha allocato con successo la HMA mediante il servizio 01h. 
SERVIZIO XMS 04H: DISABILITA LA A20 LINE 
Input  AH  04h 
Output  AX  1 se la A20 Line è stata disattivata correttamente. 
Note  In caso di fallimento, AX è 0 e BL contiene il codice di errore XMS

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 
Note  In caso di fallimento, AX è 0 e BL contiene il codice di errore XMS
Segue esempio: 
/********************

    BARNINGA_Z! - 1992

    UMBFREE.C - freeUMB()

    int freeUMB(void (far *XMMdriver)(void),unsigned UMBseg);
    void (far *XMMdriver)(void);   puntatore all'entry point del driver XMM.
    unsigned UMBseg;               indirizzo di segmento dell'UMB da liberare.
    Restituisce: 0 se l'operazione è stata eseguita con successo.
                 Se < 0 e' il codice di errore XMS.

    COMPILABILE CON TURBO C++ 2.0

        tcc -O -d -c -mx -k- umbfree.c

    dove -mx puo' essere -mt -ms -mc -mm -ml -mh

********************/
#pragma  inline

int cdecl freeUMB(void (far *XMMdriver)(void),unsigned UMBseg)
{
    _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): 
#include <dos.h>        // per MK_FP()
    ....
    unsigned umbSeg;
    char far *umbPtr;
    ....
    if(allocUMB(XMMdriver,10000,&umbSeg) < 0) {
        ....   // gestione errore XMS
    }
    else {
        umbPtr = (char far *)MK_FP(umbSeg,0);
        ....   // utilizzo UMB
        if(freeUMB(umbSeg) < 0) {
            ....   // gestione errore XMS
        }
    }
La tabella che segue riporta i codici di errore XMS. 

CODICI DI ERRORE XMS 
Codice
Descrizione
80h
Funzione non valida 
81h
E' installato VDISK 
82h
Errore nella A20 line 
8Eh
Errore interno al driver 
8Fh
Errore irrecuperabile del driver 
90h
La HMA non esiste 
91h
La HMA è già in uso 
92h
Richiesta allocazione in HMA di un numero di byte minore del parametro associato a /HMAMIN sulla riga di comando del driver XMM 
93h
La HMA è libera 
94h
Non è stato possibile disattivare la A20 line 
A0h
Tutta la memoria estesa è già allocata 
A1h
Tutti gli handles disponibili per la memoria estesa sono già utilizzati 
A2h
Handle non valido 
A3h
Handle sorgente non valido 
A4h
Offset sorgente non valido 
A5h
Handle destinazione non valido 
A6h
Offset destinazione non valido 
A7h
La lunghezza del blocco non è valida 
A8h
Le aree sorgente e destinazione si sovrappongono 
A9h
Errore di parità 
AAh
Il blocco non è locked 
ABh
Il blocco è locked 
ACh
Overflow nel conteggio dei lock del blocco 
ADh
Operazione di lock fallita 
B0h
E' disponibile un UMB di dimensione minore a quella richiesta 
B1h
Non sono disponibili UMB 
B2h
Segmento UMB non valido 

OK, andiamo avanti a leggere il libro... 

Non ci ho capito niente! Ricominciamo...