[1] Che cosa vi aspettavate? Questa
non è una guida di riferimento tecnico per il sistema operativo,
né un supplemento alla manualistica dei compilatori C. Questa è...
beh... chissà.
[2] Il C Borland include ALLOC.H; il C Microsoft
MALLOC.H.
[3] Si noti che i servizi DOS gestiscono la memoria
in unità minime di 16 byte, dette paragrafi. Inoltre, ogni blocco
allocato dal DOS si trova sempre ad un indirizzo allineato a paragrafo
(divisibile, cioè, per 16), esprimibile con un'espressione del tipo
segmento:0000.
[4] Forse è opportuno ricordare che gli indirizzi
segmento:offset sono una rappresentazione (coerente con i registri a 16
bit della CPU) di un indirizzo a 20 bit; 9FFF:000F equivale a
9FFFF.
[5] Tutte le versioni di DOS, inclusa la 6.2, sembrano
caricare il primo file nascosto proprio all'indirizzo 0070:0000.
[6] Il DOS non distingue le due aree: dopo il bootstrap,
tutta la RAM al di sopra dell'environment di COMMAND.COM è
considerata un'unica area, libera, a disposizione dei programmi. La parte
transiente di COMMAND.COM, se sovrascritta, viene ricaricata da
disco all'occorrenza.
[7] In sostanza, il DOS gestisce la RAM per aree (che
possono essere allocate ad un programma oppure libere), in testa ad ognuna
delle quali crea un MCB. Un po' di pazienza, tra breve analizzeremo i MCB
in dettaglio.
[8] Questa è la regola generale. A partire dal
DOS 4.0, però, l'area di RAM allocata ai device driver ha un MCB
regolare, recante la lettera 'M' nel campo POS, ma è
a sua volta suddivisa in tante subaree quanti sono i driver installati,
ciascuna dotata, in testa, di un proprio MCB. In tali Memory Control Block
il campo POS indica il tipo di driver; il suo contenuto può
essere: 'D' blocco device driver (installato dal comando DEVICE
in CONFIG.SYS), 'F' blocco FILES, 'X'
blocco FCBS, 'B' blocco BUFFERS, 'C'
blocco buffer EMS, 'I' blocco IFS, 'L' blocco LASTDRIVE,
'S' blocco STACKS, 'E' blocco device driver
appendage.
[9] Il PSP è un record di 256 byte che il DOS
prepara in testa al codice del programma eseguito. Per ogni programma caricato
in memoria si ha dunque un MCB, immediatamente seguito dal PSP, a sua volta
seguito dal codice del programma stesso. L'indirizzo di segmento del PSP
di un programma è pertanto pari a quello del suo MCB, incrementato
di uno.
[10] In effetti, l'area dei device driver è
quella che immediatamente segue la RAM riservata al secondo file nascosto,
come evidenziato in figura 1.
[11] Tutte le aree di RAM gestite mediante servizi
DOS hanno dimensione (in byte) divisibile per 16 (multiple per paragrafi:
non è più una novità). Anche il loro indirizzo è
divisibile per 16 (cioè allineato a paragrafo) ed è esprimibile
mediante la sola parte segmento (seg:0000). Ne segue che anche
i MCB sono allineati a paragrafo, dal momento che occupano il paragrafo
immediatamente precedente l'area allocata. Vale infine la pena di sottolineare
che i servizi 48h, 49h e 4Ah dell'int 21h restituiscono e/o richiedono
in input l'indirizzo (sotto forma di word, la sola parte segmento) dell'area
e non quello del MCB (ricavabile decrementando di uno quello dell'area).
[12] Le versioni di DOS anteriori alla 4.0 non utilizzano
questo campo; con esse il solo metodo per conoscere il nome del programma
è andare a curiosare in coda all'environment di questo (vedere gli
esempi relativi al linguaggio Clipper per un metodo valido, comunque,
anche con DOS 4 e successivi). Qui il nome è memorizzato completo
di drive e pathname; tuttavia esso scompare se la RAM allocata all'environment
viene liberata.
[13] Alcune interessanti particolarità relative
all'InDOS Flag sono discusse in un paragrafo
dedicato.
[14] Il puntatore mcb non è dichiaratonear né far: il suo tipo dipende perciò
dal modello di memoria scelto per la compilazione;
esso, in particolare, è near nei modelli tiny, small e
medium. All'interno della funzione non è possibile sapere se la
struttura a cui mcb punta è stata allocata nello heap con
una chiamata a malloc() (con indirizzo relativo a DS),
nell'area dati statici e globali (indirizzo ancora relativo a DS)
o nello stack come variabile automatica (indirizzo relativo a SS).
In tutti gli esempi di funzione presentati nel testo, in casi come quello
analizzato, si assume che nei modelli small e medium DS e SS
coincidano (e si utilizza dunque DS per ricavare la parte segmento
dell'indirizzo), in quanto questo è il default di comportamento
del compilatore. Solo con particolari e pericolose opzioni della riga di
comando è infatti possibile richiedere che DS e SS,
in detti modelli di memoria, non siano necessariamente uguali.
[15] Per la precisione: un megabyte e 64 Kb meno 16
byte (FFFF:FFFF). I (circa) 64 Kb eccedenti il Mb sono denominati
HMA (High Memory Area). Le macchine a 32 bit
(80386, 80486, etc.) possono indirizzare linearmente grandi quantità
di RAM, ma il limite descritto permane in ambiente DOS.
[16] Quanto detto è vero per le macchine 80286.
Le macchine basate su processore 80386 o 80486 (comprese le versioni SX)
possono utilizzare anche memoria estesa, se al bootstrap è installato
un driver in grado di emulare la memoria espansa attraverso quella estesa.
[17] Forse vale la pena di ricordare che la parte segmento
di un indirizzo equivale alla word più significativa di un puntatore
C far o huge.
[18] Il primo MCB ha lo scopo di escludere dal remapping
il buffer video EGA/VGA (A000:0AFFF:000F). La dimensione
del MCB può variare a seconda delle opzioni presenti sulla riga
di comando del driver che gestisce la Upper Memory.
[19] La logica è analoga a quella descritta
circa il caricamento dei device driver in memoria convenzionale.
[20] Riprendendo l'esempio precedente: a 9FFF:0
vi è il primo MCB dell'Upper Memory; la formula indirizzo+DIM+1
fornisce B001h. Se sulla macchina è installato un video
a colori, l'intervallo B001:0B7FF:0 costituisce il primo
UMB (a B000:0 vi è il suo proprio MCB), che può
contenere uno o più MCB. A B7FF:0 si trova un MCB che ha
lo scopo di proteggere l'intervallo B800:0C7FF:000F (nell'ipotesi
di scheda VGA presente): la formula indirizzo+DIM+1 fornisce C801h.
A C801:0 vi è un altro UMB (il suo proprio MCB è
a C000:0), che può contenere diversi MCB, e così
via.
[21] La word a 0:0413 contiene i Kb di memoria
convenzionale installati.
[22] Esempio: se sulla macchina è installato
un video a colori, a B000:0 vi è il primo MCB dell'Upper
Memory (l'area UMB è a B001:0); la formula indirizzo+DIM+1
fornisce l'indirizzo del successivo MCB. A B7FF:0 si trova un
MCB che ha lo scopo di proteggere l'intervallo B800:0C7FF:000F
(nell'ipotesi di scheda VGA presente): la formula indirizzo+DIM+1
fornisce C800h. Qui vi è un altro MCB (l'area UMB è
a C001:0), per il quale la formula indirizzo+DIM+1 fornisce
l'indirizzo del successivo MCB, e così via.
[23] Gli esempi su allocazione e disallocazione degli
UMB presumono la conoscenza della modalità di chiamata dei servizi
XMS, descritta proprio nel capitolo dedicato alla memoria estesa.
[24] Una pagina equivale a 16 Kb.
[25] Lo standard industriale di specifiche per la gestione
della memoria espansa definito da Lotus, Intel e Microsoft.
[26] Parte delle funzioni è scritta in C puro,
parte, a scopo esemplificativo, si basa sull'inline assembly.
[27] Se il bit 7 di DX è 0,
allora esiste nella directory corrente un file avente nome EMMXXXX0:
la open() ha aperto detto file (e non il device EMM). Quando si
dice la sfortuna...
[28] Tra l'altro questo algoritmo è facilmente
implementabile anche all'interno di gestori di interrupt.
[29] Il valore restituito dall'int 67h è "risistemato"
in modo coerente con il servizio 30h dell'int 21h, che restituisce in AL
la versione e in AH la revisione del DOS.
[30] In grado, cioè, di utilizzare porzioni
dello spazio libero su disco come RAM aggiuntiva. E' il caso, ad esempio,
di Microsoft Windows 3.x su macchine 80386 o superiori, se attivo in modalità
"80386 Avanzata".
[31] Se la sua dimensione supera i 9 byte, sono comunque
utilizzati solamente i primi 9.
[32] Se, ad esempio, si richiede al driver di allocare
un gruppo di 3 pagine logiche, queste saranno numerate da 0 a 2. Ciascuna
di esse è identificabile univocamente mediante lo handle e il proprio
numero. Nonostante il paragone sia un po' azzardato, si può pensare
ad un blocco di pagine logiche come ad un array: lo handle identifica l'array
stesso, ed ogni pagina ne è un elemento, che può essere referenziato
tramite il proprio numero (l'indice).
[33] Mappare.... orrendo!
[34] Alcune notizie relative all'utilizzo della memoria
EMS nei TSR sono date in un paragrafo dedicato.
[35] I programmi DOS possono operare in modo protetto
solo su macchine dotate di processore 80286 o superiore. La modalità
standard di lavoro in ambiente DOS, possibile su tutte le macchine, è
la cosiddetta reale (real mode).
[36] XMS è acronimo di eXtended Memory Specification,
standard industriale per la gestione della memoria estesa. La XMS include
anche regole per la gestione degli UMB.
[37] Ne segue che la memoria estesa in senso stretto
si trova oltre i primi 1088 Kb (FFFF:FFFF).
[38] Forse è il caso di spendere due parole
di chiarimento circa la HMA. Questa è l'unica parte di memoria estesa
indirizzabile da un processore 80286 o superiore senza necessità
di lavorare in modalità protetta. Infatti l'indirizzo F000:FFFF
punta all'ultimo byte di memoria entro il primo Mb: detto indirizzo è
normalizzato in FFFF:000F. Incrementandolo di uno si ottiene FFFF:0010,
cioè l'indirizzo del primo byte di memoria estesa, equivalente all'indirizzo
lineare 100000h, esprimibile mediante 21 bit. Le macchine 8086
dispongono di sole 20 linee di indirizzamento della RAM e possono quindi
gestire indirizzi "contenuti" in 20 bit. Per questo motivo l'indirizzo
FFFF:0010 subisce su di esse il cosiddetto address wrapping
e diviene 0000:0000. Al contrario, le macchine 80286 dispongono
di 24 bit per l'indirizzamento della memoria; quelle basate sul chip 80386
ne hanno 32. La A20 line è la linea hardware (sono convenzionalmente
indicate con A0...A31) di indirizzamento della RAM corrispondente al ventunesimo
bit: essa consente, se attiva (i servizi XMS lo consentono anche in modalità
reale), di indirizzare i primi FFF0h byte (65520) al
di là del primo Mb, che rappresentano, appunto, la HMA. Per ulteriori
notizie circa l'indirizzamento della RAM vedere il paragrafo
dedicato.
[39] Il valore restituito dall'int 67h è "risistemato"
in modo coerente con il servizio 30h dell'int 21h, che restituisce in AL
la versione e in AH la revisione del DOS.
[40] Il valore restituito dall'int 67h è "risistemato"
in modo coerente con il servizio 30h dell'int 21h, che restituisce in AL
la versione e in AH la revisione del DOS.
[42] L'uso dell'int 15h rappresenta uno standard (precedente
al rilascio delle specifiche XMS) in base al quale il programma che intende
allocare memoria estesa chiama l'int 15h e memorizza la quantità
di memoria estesa esistente: tale valore rappresenta anche l'indirizzo
dell'ultimo byte di memoria fisicamente installata. Il programma stesso
deve poi installare un proprio gestore dell'int 15h, che restituisce al
successivo chiamante un valore inferiore: la differenza rappresenta proprio
la quantità di memoria estesa che il programma intende riservare
a sé. Un altro standard, anch'esso precedente a quello XMS, è
il cosiddetto sistema "VDISK", dal nome del driver Microsoft
per ramdisk che lo implementò per primo. L'algoritmo è
analogo a quello dell'int 15h, ma la memoria estesa è allocata al
programma a partire "dal basso", cioè dall'indirizzo 100000h
(con lo standard dell'int 15h la memoria è, evidentemente, allocata
a partire dall'alto). I tre sistemi (int 15h, VDISK e XMS) sono beatamente
incompatibili tra loro... c'era da dubitarne?
[43] Non è davvero il caso di ripetere sempre
la medesima litania.
[44] E, pertanto, pericoloso. Non sembrano inoltre
essere disponibili servizi per la disallocazione delle porzioni di HMA
allocate mediante la subfunzione 02h del servizio 4Ah.
[45] Quando la A20 line è attiva, una coppia
di registri a 16 bit può esprimere indirizzi lineari a 21 bit, fino
a FFFF:FFFF (non è più una novità). Ne segue
che è possibile referenziare direttamente indirizzi all'interno
della HMA tramite normali puntatorifar
o huge. Ad esempio l'istruzione
#include <dos.h> // per MK_FP()
....
register i;
unsigned char *cPtr;
for(i = 0x10; i < 0x20; i++)
*(unsigned char far *)MK_FP(0xFFFF,i) = *cPtr++;
copia 16 byte da un indirizzo in memoria convenzionale (referenziato da
un puntatore near) all'inizio della HMA. I servizi XMS 05h e 06h,
del tutto analoghi ai servizi 03h e 04h, consentono di attivare e, rispettivamente,
disattivare la A20 line per indirizzare direttamente, mediante puntatori
lineari a 32 bit, circa 1 Mb di memoria estesa (al di fuori della HMA).