Assembly Avanzato con NASM
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:
- Indirizzo logico Pagina Fisica 0 = D000h:0000h
- Indirizzo logico Pagina Fisica 1 = D000h:4000h
- Indirizzo logico Pagina Fisica 2 = D000h:8000h
- Indirizzo logico Pagina Fisica 3 = D000h:C000h
Normalizzando questi 4 indirizzi logici otteniamo:
- Indirizzo logico normalizzato Pagina Fisica 0 = D000h:0000h
- Indirizzo logico normalizzato Pagina Fisica 1 = D400h:0000h
- Indirizzo logico normalizzato Pagina Fisica 2 = D800h:0000h
- Indirizzo logico normalizzato Pagina Fisica 3 = DC00h:0000h
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:
- BX = 0
Se BX specifica il valore 0, l'handle rimane assegnato al
programma ma tutte le pagine logiche associate vengono restituite al sistema; questa
tecnica può essere utilizzata per impedire che un determinato handle, pur non
essendo associato ad alcuna pagina logica, venga utilizzato da altri programmi.
- BX specifica lo stesso numero di pagine già in uso all'handle
In questo caso la situazione rimane immutata; all'handle restano associate
le stesse pagine in uso prima della chiamata del servizio 51h.
- BX specifica un numero di pagine maggiore di quello in uso all'handle
L'EMM tenta di aggiungere ulteriori pagine logiche all'handle, sino a
raggiungere il valore specificato in BX; gli indici delle nuove pagine logiche
sono consecutivi a quelli già in uso all'handle.
- BX specifica un numero di pagine minore di quello in uso all'handle
L'EMM tenta di sottrarre pagine logiche all'handle, sino a raggiungere
il valore specificato in BX; gli indici delle pagine sottratte sono quelli
posti alla fine della sequenza precedentemente in uso all'handle.
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)