Assembly Avanzato con MASM

Capitolo 8: Lo standard EMS - Expanded Memory Specifications


Sin dai tempi dei vecchi PC basati sulle CPU 8086, ci si era già resi conto del fatto che l'unico MiB indirizzabile con l'address bus a 20 linee si sarebbe rivelato ben presto insufficiente a soddisfare i requisiti di memoria, sempre più esorbitanti, richiesti dai nuovi programmi; nell'attesa che uscissero nuove CPU dotate di un maggior numero di linee di indirizzo, venne adottata una soluzione largamente sfruttata nel mondo della modalità reale.

La Figura 8.1 illustra il concetto fondamentale su cui si basa tale soluzione. Bisogna partire quindi dal presupposto che la 8086 può accedere in modo diretto ad un solo MiB di RAM, compreso tra gli indirizzi fisici 00000h e FFFFFh; qualunque altra memoria supplementare (memoria esterna) che, in genere, si trova su una scheda separata dalla cosiddetta RAM centrale, risulta quindi inaccessibile, in modo diretto, alla CPU.
Per aggirare questo ostacolo, viene utilizzata la tecnica di Figura 8.1; in pratica, nel momento in cui si vuole accedere in I/O ad una determinata area della memoria esterna, si fa in modo che l'area stessa "appaia" in un preciso punto della RAM centrale. La tecnica appena descritta prende il nome di I/O Memory Mapped (input/output su un'area di memoria esterna mappata nella memoria centrale); l'area della RAM centrale nella quale viene mappata la porzione di memoria esterna prende il nome di frame buffer (buffer fotogramma).

Il compito di mappare la memoria esterna nel frame buffer spetta all'hardware della scheda su cui è installata la stessa memoria esterna; il programmatore quindi non deve fare altro che specificare (all'apposito driver) quale area della memoria esterna deve essere mappata nel frame buffer.

Il termine frame buffer fa chiaramente riferimento all'analogia con la pellicola cinematografica (la memoria esterna) la quale, come sappiamo, è composta da una lunga sequenza di fotogrammi; il proiettore (l'hardware della memoria esterna) fa scorrere la pellicola in modo che i vari fotogrammi compaiano uno alla volta sullo schermo (il frame buffer) risultando così visibili al pubblico (la CPU).

Come è stato già spiegato in precedenza, la tecnica descritta in Figura 8.1 viene largamente impiegata nel mondo della modalità reale proprio con lo scopo di permettere alla CPU l'accesso diretto alle memorie che si trovano al di fuori del suo spazio di indirizzamento; tra gli esempi più emblematici si possono citare: il BIOS del PC, il BIOS della scheda video e la VRAM (Video RAM) della stessa scheda video.
Nel caso particolare in cui la memoria esterna sia una scheda contenente RAM supplementare che va ad aggiungersi alla RAM centrale, si utilizza la definizione di espansione di memoria; la RAM supplementare presente in tale scheda viene definita memoria espansa. Ai tempi delle CPU 8086, se si aveva la necessità di ulteriore memoria RAM, l'unica strada da seguire consisteva nell'installazione di una espansione di memoria la cui caratteristica principale era il prezzo proibitivo; l'utente doveva anche installare un apposito driver il cui compito era quello di pilotare l'hardware della stessa espansione di memoria.
Ci si potrebbe chiedere allora che senso abbia oggi parlare di memoria espansa, vista la disponibilità delle potenti CPU 80386 e superiori, capaci di indirizzare direttamente diversi GiB di RAM (anche in modalità reale grazie ai servizi XMS descritti nel precedente capitolo).
La risposta a questa domanda è legata ad una geniale intuizione avuta dagli sviluppatori di 386MAX (il famoso XMM descritto nel precedente capitolo); quegli sviluppatori si accorsero ad un certo punto che avendo a disposizione una CPU 80386 o superiore ed un XMM, sarebbe stato possibile simulare la memoria espansa via software, facendola apparire nella memoria estesa!
L'aspetto interessante è che la gestione della memoria espansa (simulata) avviene in modo nettamente più veloce ed efficiente rispetto al caso della memoria estesa (grazie al fatto che non abbiamo a che fare con le lentissime operazioni di attivazione e disattivazione della linea A20, necessarie per poter svolgere trasferimenti di dati); ma un altro aspetto ben più interessante per i programmi che operano in modalità reale è dato dal fatto che, a differenza di quanto accade con la memoria estesa, utilizzabile solo per operazioni di I/O, la memoria espansa può essere utilizzata anche per implementare SO capaci di eseguire uno o più programmi (multitasking)!
Il driver della memoria espansa fornisce anche tutti i meccanismi di protezione necessari per evitare che due o più programmi in esecuzione possano interferire tra loro; la vecchia versione 2.0 di Windows venne completamente riscritta proprio per poter beneficiare di tutte quelle novità che, all'epoca, erano sicuramente rivoluzionarie.

8.1 Lo standard LIM-EMS 4.0

In analogia a quanto è successo per la memoria estesa, anche l'utilizzo della memoria espansa da parte dei programmi che operano in modalità reale è stato regolamentato da un apposito standard definito sul finire degli anni 80 da un gruppo di aziende composto dalla Lotus Development Corporation, dalla Intel Corporation e dalla Microsoft Corporation; le iniziali dei nomi di queste tre aziende hanno originato l'acronimo LIM, mentre per indicare lo standard viene usato l'acronimo EMS che sta per Expanded Memory Specifications.
Nel seguito del capitolo faremo riferimento allo standard LIM-EMS 4.0 definito nel 1987.

8.1.1 Definizioni convenzionali

Con la definizione di expanded memory (memoria espansa) si indica tutta la RAM che si trova disposta oltre i primi 640 KiB (cioè, subito dopo la memoria convenzionale); come sappiamo, una CPU che opera in modalità reale, oltre i primi 640 KiB può accedere in modo diretto solamente alla upper memory.
Lo standard LIM-EMS 4.0 prevede, appunto, che proprio nella upper memory venga creato il frame buffer, attraverso il quale è possibile accedere a tutta la memoria espansa disponibile; è chiaro quindi che la frase: "tutta la RAM che si trova disposta oltre i primi 640 KiB" deve essere interpretata come: "tutta la upper memory, compresa la expanded memory visibile attraverso il frame buffer"!

Si ricordi che, come è stato spiegato nei precedenti capitoli, in assenza di appositi accorgimenti tutta la RAM compresa tra gli indirizzi fisici A0000h e FFFFFh (cioè, la upper memory) risulta invisibile ai normali programmi DOS; uno di questi accorgimenti è rappresentato, appunto, dallo standard LIM-EMS.

Il termine EMM sta per Expanded Memory Manager e indica un qualsiasi driver capace di fornire tutti i servizi necessari per l'accesso alla memoria espansa; l'EMM più famoso è sicuramente EMM386 in quanto viene fornito insieme al DOS.
Compito fondamentale dell'EMM è anche quello di creare il frame buffer nella upper memory; a tale proposito, si veda la Figura 6.1 del Capitolo 6, sezione Assembly Avanzato.

L'EMM suddivide la memoria espansa in tanti blocchi, consecutivi e contigui, ciascuno dei quali ha una dimensione pari a 16 KiB; ogni blocco da 16 KiB rappresenta una cosiddetta logical page (pagina logica).
A sua volta, il frame buffer viene suddiviso in tanti blocchi, consecutivi e contigui, ciascuno dei quali ha una dimensione pari a 16 KiB; ogni blocco da 16 KiB rappresenta una cosiddetta physical page (pagina fisica).

In ciascuna pagina fisica del frame buffer può essere mappata una delle eventuali pagine logiche appositamente allocate da un programma nella memoria espansa; si viene a creare quindi una situazione come quella descritta in Figura 8.2. Nell'esempio di Figura 8.2 notiamo che l'EMM ha creato il frame buffer in upper memory a partire dall'indirizzo fisico D0000h a cui possiamo associare l'indirizzo logico normalizzato D000h:0000h; risultano disponibili quattro pagine fisiche, convenzionalmente numerate da 0 a 3, per un totale di 4*16=64 KiB.
Sempre nell'esempio di Figura 8.2, un programma in esecuzione ha allocato dodici pagine logiche, convenzionalmente numerate da 0 a 11; complessivamente, il programma stesso sta usando 12*16=192 KiB di memoria espansa.

Una volta creato lo schema di Figura 8.2, possiamo gestire la memoria espansa nel modo che riteniamo più opportuno, anche senza rispettare l'ordine stabilito dalla numerazione; in pratica (come si vede in Figura 8.2), una qualunque delle dodici pagine logiche può essere mappata in una qualunque delle quattro pagine fisiche.
Appare evidente però che per ottenere il massimo risultato in termini di efficienza, conviene gestire le pagine logiche a gruppi di quattro, in modo da operare su 64 KiB di memoria espansa per volta; per lo stesso motivo, è anche chiaro il vantaggio che si ottiene facendo in modo che ciascun gruppo contenga quattro pagine logiche consecutive e contigue (ad esempio, gruppo(0, 1, 2, 3), gruppo(4, 5, 6, 7), etc)!

8.1.2 Installazione dell'Expanded Memory Manager

Nel seguito del capitolo, ci occuperemo della simulazione della memoria espansa attraverso la memoria estesa; di conseguenza, viene data per scontata la presenza di una CPU 80386 o superiore e di una adeguata quantità di memoria estesa.

Se vogliamo simulare la memoria espansa attraverso la memoria estesa, prima di tutto dobbiamo procedere, ovviamente, all'installazione di un XMM; a tale proposito, valgono tutte le considerazioni esposte nel precedente capitolo.

A questo punto possiamo passare all'installazione dell'EMM; come al solito, il procedimento può variare a seconda del SO che si sta impiegando.

In un ambiente DOS puro (anche sotto VirtualBox), aprendo con un editor il file C:\CONFIG.SYS, si deve inserire una linea del tipo:
DEVICE=C:\DOS\EMM386.EXE
Tale linea deve essere successiva al comando di installazione dell'XMM e provvede, a sua volta, ad installare l'EMM predefinito (EMM386.EXE) in fase di avvio del SO; lo stesso driver accetta anche numerosi parametri da linea di comando, alcuni dei quali verranno illustrati nel seguito (per un elenco dettagliato di tali parametri e del loro significato, si consiglia di consultare un manuale del DOS).

Si presti attenzione al fatto che il precedente comando potrebbe anche assumere il seguente aspetto:
DEVICE=C:\DOS\EMM386.EXE NOEMS
Il parametro NOEMS indica che vogliamo la disponibilità della memoria superiore, ma non della memoria espansa; in un caso del genere, per rendere disponibile la memoria espansa dobbiamo eliminare il parametro NOEMS e riavviare il computer (o la macchina virtuale)!

Se si lavora con FreeDOS (anche sotto VirtualBox), si devono seguire le istruzioni illustrate nel seguito del capitolo.

Nel caso degli emulatori come DOSEmu, in genere, non è necessario apportare alcuna modifica in quanto nel file CONFIG.SYS è già presente il comando:
devicehigh=c:\dosemu\ems.sys

8.2 Interfaccia di programmazione EMS

Tutti i servizi forniti da un EMM risultano accessibili attraverso una chiamata della INT 67h (denominata LIM-EMS interrupt); ciascuno di tali servizi viene identificato attraverso un codice a 8 bit da caricare in AH.
Nel caso generale, quindi, l'accesso ad un servizio EMS, fornito da un EMM, avviene nel seguente modo: Un programma che intenda accedere ai servizi offerti dallo standard EMS deve innanzi tutto verificare che sia installato (e che sia operativo) un EMM in memoria; a tale proposito, come vedremo anche più avanti, è necessario porre AH=40h (Get EMM Status) e chiamare la INT 67h. L'aspetto paradossale, però, è che tale servizio può essere richiesto solo se un EMM è già installato in memoria!
Come se non bastasse, in assenza di un EMM in memoria, la chiamata alla INT 67h potrebbe anche provocare un crash del computer; ciò accade, ad esempio, quando il BIOS (durante il boot) non inizializza correttamente la ISR associata al vettore di interruzione n.67h (generalmente, le ISR associate ai vettori di interruzione inutilizzati, vengono inizializzate dal BIOS con il codice macchina CFh dell'istruzione IRET).

Per ovviare a questo problema, possiamo seguire un procedimento alternativo basato sul fatto che, se un EMM è installato in memoria, allora all'indirizzo logico XXXXh:000Ah, dove XXXXh è la componente Seg della ISR associata alla INT 67h, è presente la stringa "EMMXXXX0"; tale stringa rappresenta il nome in codice che identifica, appunto, un EMM.
Possiamo verificare questo aspetto anche attraverso il programma DEBUG (solo in un ambiente DOS puro o con FreeDOS); a tale proposito, tenendo presente che 67h*4=019Ch, la coppia Seg:Offset della relativa ISR sarà memorizzata nel vettore delle interruzioni all'indirizzo logico 0000h:019Ch. A questo punto, supponendo che a tale indirizzo sia presente la coppia C302h:0091h, non dobbiamo fare altro che richiedere il "dump" della memoria con il comando:
-d c302:000a
In base alle considerazioni appena esposte, per sapere se il driver EMM è installato in memoria possiamo procedere nel modo seguente: Queste istruzioni presuppongono che la coppia DS:SI stia puntando alla stringa "EMMXXXX0"; bisogna ricordare, infatti, che l'istruzione CMPSB compara il BYTE puntato da DS:SI (stringa sorgente) con il BYTE puntato da ES:DI (stringa destinazione).
Dopo ogni comparazione CMPSB pone ZF=1 se i due BYTE sono uguali e ZF=0 se i due BYTE sono diversi; l'istruzione CLD pone DF=0 determinando così l'incremento automatico dei puntatori SI e DI dopo l'esecuzione di CMPSB.
A sua volta, il prefisso REPZ ripete il loop per un massimo di CX=8 volte decrementando lo stesso registro CX ad ogni iterazione (senza alterare il registro FLAGS); il loop termina in modo regolare quando CX=0 o in modo prematuro quando ZF=0.
Se il loop termina in modo regolare si ha quindi CX=0 e ZF=1; in tal caso, siamo sicuri che le due stringhe comparate sono uguali.
Nel caso in cui la comparazione abbia dato esito positivo, possiamo procedere con la chiamata del servizio AH=40h della INT 67h per verificare lo stato operativo dell'EMM; tale servizio viene descritto in dettaglio più avanti.

Se la richiesta di un servizio EMS ha successo, la chiamata alla INT 67h restituisce AH=00h più eventuali informazioni addizionali in altri registri; se, invece, la richiesta fallisce, la chiamata alla INT 67h restituisce un codice di errore sempre in AH.
I codici di errore validi hanno sempre il bit più significativo che vale 1; la Figura 8.3 illustra l'elenco completo dei codici di errore. Il significato dei vari termini presenti in Figura 8.3 sarà chiarito nel seguito del capitolo.

8.3 Servizi EMS

Lo standard LIM-EMS 4.0 mette a disposizione una numerosa serie di servizi destinati ai normali programmi DOS; tali servizi hanno lo scopo di permettere ai programmi che girano in modalità reale di accedere in I/O alla memoria espansa.
Sono anche presenti diversi potenti servizi destinati alla implementazione di veri e propri SO capaci di eseguire uno o più programmi contemporaneamente; i SO implementati con lo standard LIM-EMS 4.0 hanno a disposizione sino a 24 pagine fisiche da 16 KiB in memoria convenzionale e sino a 12 pagine fisiche da 16 KiB in memoria superiore.
Nel seguito del capitolo faremo riferimento solo ai servizi standard destinati ai normali programmi DOS; ci occuperemo quindi di semplici operazioni di I/O nella memoria espansa attraverso un frame buffer in upper memory costituito da 4 pagine fisiche.

In generale, un determinato EMM potrebbe anche non implementare tutti i servizi previsti dallo standard LIM-EMS 4.0; proprio per questo motivo, è importante che il programmatore verifichi sempre l'eventuale codice di errore restituito in AH dalla INT 67h, con particolare riferimento ai codici 84h (servizio inesistente) e 8Fh (sottoservizio inesistente)!

Analizziamo ora l'elenco dei principali servizi EMS e le caratteristiche di ciascuno di essi; per maggiori dettagli si consiglia di scaricare il file ems40.zip presente nella sezione Documentazione tecnica di supporto al corso assembly dell’ Area Downloads di questo sito.

8.3.1 Servizio n. 40h: Get EMM Status

Questo servizio restituisce un codice per indicare se un EMM è installato in memoria e se il relativo hardware sta funzionando correttamente. Il valore 00h restituito in AH indica che il driver è installato in memoria e il relativo hardware di gestione della expanded memory funziona in modo corretto; ogni altro valore restituito in AH indica, invece, una condizione di errore!

8.3.2 Servizio n. 41h: Get Page Frame Address

Questo servizio restituisce l'indirizzo in upper memory del frame buffer. Bisogna ricordare che tutti i blocchi di memoria DOS (compresi gli UMB) sono sempre allineati al paragrafo per cui il loro indirizzo logico iniziale Seg:Offset ha la componente Offset che vale 0000h; proprio per questo motivo, il servizio n.41h restituisce solo la componente Seg dell'indirizzo logico iniziale del frame buffer.

8.3.3 Servizio n. 42h: Get Unallocated Page Count

Questo servizio restituisce il numero di pagine logiche libere e il numero totale di pagine logiche disponibili. In caso di successo, questo servizio restituisce in BX il numero di pagine logiche libere, allocabili dai programmi che ne hanno bisogno; in DX viene, invece, restituito il numero di pagine logiche complessive (libere o già allocate).
Lo standard LIM-EMS 4.0 permette di gestire un massimo di 2048 pagine logiche da 16 KiB ciascuna per un totale di 32 MiB di memoria espansa.

8.3.4 Servizio n. 43h: Allocate Pages

Questo servizio tenta di allocare il numero di pagine logiche richiesto dal programmatore. In caso di successo, questo servizio restituisce in DX un cosiddetto handle rappresentato da un numero intero il cui scopo è quello di identificare in modo univoco il blocco di pagine logiche allocato da un programma; gli handle per i programmi hanno sempre un valore compreso tra 1 e 254 in quanto il valore 0 è riservato ad un eventuale SO.
Si tenga presente che è proibito allocare 0 pagine logiche!

Il DOS non ha nulla a che vedere con la gestione della memoria espansa e quindi non è in grado di deallocarla automaticamente quando un programma termina; proprio per questo motivo, i programmi che allocano memoria espansa devono rigorosamente restituirla prima di terminare!

8.3.5 Servizio n. 44h: Map/Unmap Handle Pages

Questo servizio ha lo scopo di abilitare o disabilitare la mappatura delle pagine logiche nel frame buffer. Questo servizio permette di mappare pagine logiche (allocate da un programma) nelle pagine fisiche del frame buffer; il programmatore deve specificare in AL l'indice della pagina fisica in cui verrà mappata la pagina logica il cui indice è contenuto in BX.
Come si può notare, il servizio 44h permette di effettuare un solo accoppiamento (pagina logica - pagina fisica) per volta e ciò può chiaramente comportare problemi di efficienza nel caso di frequenti operazioni di mappatura; proprio per questo motivo, viene reso disponibile un altro servizio (descritto più avanti) attraverso il quale si possono effettuare mappature multiple.

Lo stesso servizio 44h permette anche di disabilitare la mappatura corrente di una pagina logica in una pagina fisica; a tale proposito, bisogna porre BX=FFFFh.
In una situazione del genere, la pagina logica che era stata precedentemente mappata nella pagina fisica specificata in AL diventa inaccessibile per le operazioni di I/O!

8.3.6 Servizio n. 45h: Deallocate Pages

Questo servizio tenta di deallocare le pagine logiche precedentemente allocate da un programma. Come è stato spiegato in precedenza, il DOS non ha alcun ruolo nella gestione della memoria espansa per cui non è in grado di sapere se un programma, prima di terminare, ha proceduto alla deallocazione di eventuali pagine logiche che stava usando; di conseguenza, il compito di effettuare tale deallocazione spetta rigorosamente al programma stesso.
Se non si tiene conto di questo aspetto, tutte le pagine logiche associate ai vari handle non restituiti al sistema risulteranno inutilizzabili da altri programmi; lo stesso problema riguarda quindi anche i programmi che terminano in modo anomalo (ad esempio, per un crash) dopo aver allocato memoria espansa!

8.3.7 Servizio n. 46h: Get Version

Questo servizio restituisce la versione dello standard LIM-EMS supportata dall'EMM in uso. Il valore restituito in AL è espresso in formato BCD; il nibble alto di AL contiene il major number, mentre il nibble basso di AL contiene il minor number.
Ricordando quanto è stato esposto nella sezione Assembly Base, se vogliamo scompattare i due nibble di AL nella coppia AH:AL dobbiamo utilizzare l'istruzione AAM con divisore 16; possiamo scrivere allora:
db 0D4h, 16

8.3.8 Servizio n. 47h: Save Page Map

Questo servizio ha lo scopo di salvare lo stato corrente del frame buffer. Supponiamo di avere un programma che, dopo aver allocato un certo numero di pagine logiche, mappa 4 di esse nelle 4 pagine fisiche del frame buffer; la situazione che si viene a creare nel frame buffer (cioè, l'associazione tra pagine fisiche e pagine logiche) rappresenta il cosiddetto mapping context corrente.
Nel caso in cui, in quel momento, arrivi una interruzione hardware, come sappiamo, la CPU interrompe il programma in esecuzione e chiama una apposita ISR; se questa ISR deve fare uso della memoria espansa, con conseguente modifica del mapping context corrente, si viene a creare una situazione piuttosto pericolosa.
Infatti, al termine della ISR, il programma precedentemente interrotto riprende l'esecuzione e si ritrova con un mapping context completamente alterato; come è facile immaginare, la conseguenza di tutto ciò è un sicuro crash!

Per ovviare a questo problema, un programma che ne interrompe un altro e fa poi uso della memoria espansa, deve rigorosamente preservare un eventuale mapping context già esistente; a tale proposito, può essere utilizzato il servizio 47h il quale salva tutte le necessarie informazioni in una apposita area dati riservata.

Un aspetto importantissimo riguarda il fatto che tale servizio deve essere utilizzato DOPO aver ottenuto un handle e PRIMA di procedere con la mappatura nel frame buffer!

Tutto ciò è necessario in quanto il servizio 47h richiede esplicitamente un handle; se il programmatore ha bisogno di salvare semplicemente il mapping context corrente senza specificare alcun handle, può utilizzare un altro servizio descritto più avanti.

8.3.9 Servizio n. 48h: Restore Page Map

Questo servizio ha lo scopo di ripristinare un mapping context salvato in precedenza con il servizio 47h. Un programma che ha precedentemente salvato un mapping context già esistente, prima di terminare deve rigorosamente ripristinarlo; a tale proposito, può essere utilizzato il servizio 48h.

Un aspetto importantissimo riguarda il fatto che tale servizio deve essere utilizzato PRIMA di restituire l'handle delle pagine logiche al gestore della memoria espansa!

Tutto ciò è necessario in quanto il servizio 48h richiede esplicitamente un handle; se il programmatore ha bisogno di ripristinare semplicemente un mapping context senza specificare alcun handle, può utilizzare un altro servizio descritto più avanti.

8.3.10 Servizio n. 4Bh: Get Handle Count

Questo servizio restituisce il numero totale di handle correntemente aperti. Il servizio 4Bh restituisce il numero totale di handle correntemente aperti dai vari programmi che stanno utilizzando la memoria espansa; il valore restituito da questo servizio comprende anche l'handle numero 0000h riservato al SO.

8.3.11 Servizio n. 4Ch: Get Handle Pages

Questo servizio restituisce il numero di pagine logiche associate ad un handle. Il servizio 4Ch restituisce il numero di pagine logiche associate all'handle specificato nel registro DX; il valore massimo possibile restituito da tale servizio è 2048 in quanto, come sappiamo, lo standard LIM-EMS 4.0 può gestire sino a 32 MiB di memoria espansa suddivisa in pagine da 16 KiB ciascuna.

8.3.12 Servizio n. 4Dh: Get All Handle Pages

Questo servizio restituisce l'elenco completo degli handle aperti e il numero delle pagine logiche associate a ciascuno di essi. Il servizio 4Dh riempie un vettore, puntato da ES:DI, con l'elenco completo delle coppie [handle, pagine]; ogni coppia contiene quindi uno degli handle aperti e il numero delle pagine logiche ad esso associate.
Nel registro BX viene restituito il numero totale di coppie contenute nel vettore e cioè, il numero totale di handle aperti (compreso il n.0000h riservato al SO).

Lo spazio per il vettore puntato da ES:DI deve essere allocato dal programma che richiede il servizio 4Dh; a tale proposito, si tenga presente che lo standard EMS-LIM 4.0 prevede un numero massimo di handle pari a 255 (compreso il n.0000h riservato al SO).
Ogni elemento del vettore è una struttura il cui aspetto è illustrato in Figura 8.4. In definitiva, il programmatore predispone un vettore capace di contenere un massimo di 255 strutture handle_page_struct; tale vettore, puntato da ES:DI, viene riempito dal servizio 4Dh con tutte le coppie [handle, pagine] che, in quel momento, sono in uso ai vari programmi che accedono alla memoria espansa.

8.3.13 Servizio n. 4E00h: Get Page Map

Questo servizio ha lo scopo di salvare tutti i mapping context attivi senza la necessità di specificare un handle. Il servizio 4Eh, sottoservizio 00h, salva tutti i mapping context correnti senza che il programmatore debba specificare un handle; ovviamente, nel caso di un semplice programma DOS che accede in I/O alla memoria espansa, è presente un solo eventuale mapping context relativo all'unico frame buffer allocato nella upper memory.
Questo servizio deve essere utilizzato al posto del n.47h quando non vogliamo (o non possiamo) fare riferimento ad uno specifico handle.

Tutte le necessarie informazioni, la cui struttura viene stabilita dall'EMM, vengono salvate in un vettore puntato da ES:DI e predisposto dal programmatore; le dimensioni di tale vettore possono essere stabilite attraverso il servizio 4E03h descritto più avanti.

8.3.14 Servizio n. 4E01h: Set Page Map

Questo servizio ha lo scopo di ripristinare tutti i mapping context salvati in precedenza attraverso il servizio 4E00h. Il servizio 4Eh, sottoservizio 01h, ripristina tutti i mapping context salvati in precedenza con il servizio 4Eh, sottoservizio 00h; tale servizio deve essere quindi utilizzato al posto del n.48h quando non vogliamo (o non possiamo) fare riferimento ad uno specifico handle.

Tutte le necessarie informazioni, la cui struttura viene stabilita dall'EMM, vengono lette da un vettore puntato da DS:SI e predisposto dal programmatore; le dimensioni di tale vettore possono essere stabilite attraverso il servizio 4E03h descritto più avanti.

8.3.15 Servizio n. 4E02h: Get & Set Page Map

Questo servizio ha lo scopo di salvare tutti i mapping context attivi e di ripristinare tutti i mapping context salvati in precedenza senza la necessità di specificare un handle. Il servizio 4Eh, sottoservizio 02h, salva tutti i mapping context correnti e ripristina tutti quelli precedentemente salvati senza che il programmatore debba specificare un handle; tale servizio deve essere quindi utilizzato al posto dei n.47h e 48h quando non vogliamo (o non possiamo) fare riferimento ad uno specifico handle.

Prima di tutto, il servizio 4E02h salva tutti i mapping context correnti in un vettore puntato da ES:DI e successivamente ripristina tutti i mapping context salvati in precedenza leggendo le informazioni da un vettore puntato da DS:SI; entrambi i vettori devono essere predisposti dal programmatore.

8.3.16 Servizio n. 4E03h: Get Size of Page Map Save Array

Questo servizio restituisce le dimensioni dei vettori richiesti dai precedenti tre sottoservizi. Prima di predisporre i vettori richiesti dai precedenti tre servizi, il programmatore può determinarne le dimensioni attraverso il servizio 4E03h; in questo modo è possibile allocare la quantità opportuna di memoria necessaria per i vettori stessi.

8.3.17 Servizio n. 5000h: Map/Unmap Multiple Handle Pages - Logical/Physical Method

Questo servizio permette di attivare o disattivare la mappatura di pagine multiple nel frame buffer. Il servizio 50h, sottoservizio 00h, permette di mappare simultaneamente una serie di pagine logiche in una serie di pagine fisiche; si tratta quindi di un servizio da utilizzare, al posto del n.44h, quando un programma deve gestire una grande quantità di operazioni di mappatura.
Nei casi che stiamo esaminando (programmi DOS che accedono alla memoria espansa per semplici operazioni di I/O), abbiamo a disposizione 4 pagine fisiche rappresentate dagli indici 0, 1, 2, 3; possiamo mappare quindi simultaneamente sino a 4 pagine logiche nelle 4 pagine fisiche disponibili.

Il metodo logical/physical prevede che, sia le pagine logiche, sia le pagine fisiche, vengano identificate mediante i rispettivi indici numerici; tali indici devono essere inseriti in un vettore i cui elementi sono rappresentati dalla struttura illustrata in Figura 8.5. Il vettore, predisposto dal programmatore, deve essere puntato dalla coppia DS:SI; il numero di strutture presenti nel vettore deve essere indicato nel registro CX.

Questo stesso servizio può essere utilizzato per impedire l'accesso in I/O ad una serie di pagine logiche associate ad una serie di pagine fisiche; a tale proposito, nel membro logical_index relativo al membro physical_index che ci interessa, dobbiamo inserire il valore FFFFh.
Il risultato che si ottiene è che tutte le pagine fisiche associate a pagine logiche identificate dal valore FFFFh, inibiscono qualunque tentativo di accesso in I/O; si tratta quindi di una funzionalità molto utile quando, ad esempio, un programma lancia un secondo programma al quale vuole impedire l'accesso a determinate pagine logiche.

8.3.18 Servizio n. 5001h: Map/Unmap Multiple Handle Pages - Logical/Segment Method

Questo servizio permette di attivare o disattivare la mappatura di pagine multiple nel frame buffer. Il servizio 50h, sottoservizio 01h, è del tutto simile al caso trattato in precedenza; l'unica differenza è che le pagine fisiche vengono individuate dal loro indirizzo Seg:Offset anziché da un indice.
Per sapere a quali indirizzi Seg:Offset si trovano le 4 pagine fisiche disponibili dobbiamo ricordare innanzi tutto che qualunque blocco di memoria DOS (e quindi anche il frame buffer) è sempre allineato al paragrafo; supponiamo allora che il servizio 41h (Get Page Frame Address) ci abbia restituito il valore D000h.
Possiamo dire quindi che il nostro frame buffer si trova in upper memory a partire dall'indirizzo logico D000h:0000h; osservando ora che ogni pagina fisica occupa 16 KiB (cioè, 4000h byte), possiamo affermare che: Normalizzando questi 4 indirizzi logici otteniamo: Le 4 componenti Seg appena ottenute sono proprio quelle che dobbiamo utilizzare per identificare le corrispondenti 4 pagine fisiche; ovviamente, le relative componenti Offset valgono, implicitamente, 0000h.

Tutte le informazioni richieste dal metodo logical/segment devono essere inserite in un vettore i cui elementi sono rappresentati dalla struttura illustrata in Figura 8.6. Il vettore, predisposto dal programmatore, deve essere puntato dalla coppia DS:SI; il numero di strutture presenti nel vettore deve essere indicato nel registro CX.

Questo stesso servizio può essere utilizzato per impedire l'accesso in I/O ad una serie di pagine logiche associate ad una serie di pagine fisiche; a tale proposito, valgono le identiche considerazioni svolte per il servizio 5000h.

8.3.19 Servizio n. 51h: Reallocate Pages

Questo servizio permette di modificare il numero di pagine associate ad un handle. Con questo servizio è possibile modificare il numero di pagine associate ad un handle già assegnato ad un programma; si possono presentare i seguenti 4 casi: Se la richiesta del servizio 51h fallisce, il registro BX restituisce lo stesso numero di pagine logiche precedentemente in uso all'handle; si presti però attenzione al fatto che il caso 2) non rappresenta una condizione di errore e produce quindi sempre la restituzione di AH=0.

8.4 Libreria EMSLIB

In analogia al caso della memoria estesa, trattato nel precedente capitolo, anche per la gestione dello standard LIM-EMS 4.0 conviene scriversi una apposita libreria linkabile a tutti i programmi che ne hanno bisogno; nella sezione Librerie di supporto per il corso assembly dell’ Area Downloads di questo sito, è presente una libreria, denominata EMSLIB, che può essere linkata ai programmi destinati alla generazione di eseguibili in formato EXE.

All'interno del pacchetto emslibexe.zip è presente la documentazione, la libreria vera e propria EMSLIB.ASM, l'include file EMSLIBN.INC per il NASM, l'include file EMSLIBM.INC per il MASM e l'header file EMSLIB.H per eventuali programmi scritti in linguaggio C (purché destinati sempre alla modalità reale).

Per la creazione dell'object file di EMSLIB.ASM è richiesta la presenza della libreria di macro LIBPROC.MAC (Capitolo 29 della sezione Assembly Base con NASM); l'assemblaggio consiste nel semplice comando:
nasm -f obj emslib.asm
L'object file così ottenuto è perfettamente linkabile anche ai programmi scritti con MASM; a tale proposito, è necessario ricordarsi di chiamare le varie procedure della libreria secondo le convenzioni C (passaggio dei parametri da destra verso sinistra e pulizia dello stack a carico del caller).

La possibilità di linkare la libreria ai programmi scritti in C è legata al fatto che le varie procedure definite in EMSLIB seguono le convenzioni C per il passaggio degli argomenti e per il valore di ritorno; da notare, inoltre, la presenza degli underscore all'inizio di ogni identificatore globale definito nella libreria stessa.

Diverse procedure della libreria richiedono argomenti passati per indirizzo; tale indirizzo deve essere sempre di tipo FAR!
Ricordando le convenzioni C per il passaggio degli argomenti (da destra verso sinistra), dobbiamo stare attenti quindi a mettere nella lista di chiamata, prima la componente Offset e poi la componente Seg della variabile da passare per indirizzo; in questo modo, la componente Offset si troverà a precedere la componente Seg in memoria, nel rispetto della convenzione Intel (con il Pascal avremmo dovuto disporre le due componenti in ordine inverso).
All'interno delle procedure, le variabili passate per indirizzo vengono gestite con le coppie DS:SI e ES:DI inizializzate con le istruzioni LDS e LES; come sappiamo, tali istruzioni lavorano in modo corretto solo quando la coppia Seg:Offset, da caricare in DS:SI o ES:DI, si trova disposta in memoria (in questo caso, nello stack) secondo la convenzione Intel!

8.5 Esempi pratici

Prima di procedere con alcuni esempi pratici, è necessario predisporre l'ambiente operativo in modo che la memoria espansa venga attivata e resa utilizzabile; a tale proposito, si deve procedere diversamente a seconda del SO che si sta usando.

Nel caso del DOS vero e proprio (anche sotto VirtualBox), si deve editare il file C:\CONFIG.SYS; al suo interno dobbiamo inserire le linee necessarie per il supporto della memoria estesa e della memoria espansa.
Se necessario, è anche possibile specificare l'indirizzo esadecimale (solo componente Seg) da cui parte il frame buffer; a tale proposito, si deve usare il parametro FRAME. Nella Figura 6.1 del Capitolo 6, possiamo notare che l'area di memoria riservata a vari buffer, tra i quali anche quello per la memoria espansa, è compresa tra gli indirizzi logici D000h:0000h e F000h:0000h; si può impostare quindi il parametro FRAME=D000. Nel file CONFIG.SYS si dovrebbe presentare allora una situazione del genere (in ordine rigoroso): Se queste linee non sono presenti, devono essere aggiunte dall'utente (bisogna anche ricordarsi di togliere l'eventuale parametro NOEMS a EMM386.EXE); in tal caso, è anche necessario riavviare il computer (o la macchina virtuale).
Come sappiamo, EMM386.EXE è il driver più diffuso per il supporto dell'EMS; bisogna ricordare comunque che esistono anche altri EMM altrettanto validi come, ad esempio, 386MAX che è capace di svolgere contemporaneamente il ruolo di XMM e di EMM.

Nel caso di FreeDOS (anche eseguito sotto VirtualBox), in fase di avvio bisogna selezionare il Menu 2:
Load FreeDOS with EMM386 (Expanded Memory) and SHARE loaded
Inoltre, nel file C:\FDCONFIG.SYS bisogna modificare la riga:
2?DEVICE=C:\FDOS\BIN\JEMM386.EXE X=TEST I=TEST I=B000-B7FF NOVME NOINVLPG
aggiungendo alla fine l'indirizzo del frame buffer; ad esempio:
FRAME=D000
Nel caso di DOSEmu, i file di avvio della finestra DOS sono i soliti AUTOEXEC.BAT e CONFIG.SYS (che si trovano nella directory scelta come radice C:\ dell'emulatore); per quanto riguarda le impostazioni della memoria, basta editare il file $HOME/.dosemurc, apportare le modifiche desiderate nella sezione Memory settings e riavviare l'emulatore.

8.5.1 Trasferimento di stringhe in memoria espansa

Analizziamo ora un esempio molto simile a quelli presentati nel precedente capitolo; la Figura 8.7 mostra un programma che memorizza 16 stringhe in 16 pagine logiche differenti e poi le visualizza sullo schermo. Innanzi tutto il programma effettua una serie di test diagnostici mostrando dei messaggi relativi alla presenza e all'operatività di un EMM, al numero di versione del driver, al numero di pagine logiche totali e libere, etc; è molto importante che i programmi che accedono alla memoria espansa facciano tutte le verifiche necessarie per evitare sorprese.
Successivamente, il programma tenta di allocare 16 pagine logiche; se l'allocazione riesce, viene effettuato un trasferimento dati che consiste nel sistemare in ciascuna delle 16 pagine logiche una stringa formata da 80x24=1920 elementi di tipo BYTE più un '$' finale (necessario per il servizio Display String della INT 21h).
Ogni stringa è formata da una sequenza di lettere maiuscole tutte uguali; la prima stringa contiene una sequenza di lettere 'A' (codice ASCII 41h), la seconda stringa contiene una sequenza di lettere 'B' (codice ASCII 42h) e così via.

Il trasferimento delle 16 stringhe nelle 16 pagine logiche avviene, per semplicità, tramite la sola pagina fisica 0 del frame buffer; a tale proposito, viene eseguito un loop durante il quale le stringhe stesse vengono create in modo automatico.
Si osservi, infatti, che se EAX = 41414141h = "AAAA", allora:
EAX + 01010101h = "AAAA" + 01010101h = 41414141h + 01010101h = 42424242h = "BBBB"
In seguito, viene attivato un secondo loop il cui scopo è quello di visualizzare le 16 stringhe in 16 schermate successive; a tale proposito, ciascuna stringa viene mappata nella pagina fisica 0 del frame buffer e poi visualizzata attraverso il servizio Display String della INT 21h.

Da notare ancora una volta l'importanza della parametrizzazione dei programmi; se si ha bisogno di allocare un numero di pagine logiche diverso da 16, nel listato di Figura 8.7 basta semplicemente modificare il valore associato alla costante simbolica MAX_PAGES.

Bibliografia

Expanded Memory Specifications (EMS), ver. 4.0
(disponibile nella sezione Downloads - Documentazione - ems40.tar.bz2)