L'indirizzamento della memoria in ambienti a 16 bit avviene tramite due parametri: l'indicatore di segmento e l' offset.
In generale possiamo immaginere la memoria come un vettore di celle, ognuna di 8 bit(1 byte), e per poter accedere in lettura e scrittura al contenuto di una cella dovremo riferirci ad essa attraverso delle coordinate.
Il metodo più semplice è quello di assegnare un indice ad ogni cella ed è proprio questo l'indirizzo lineare a cui faremo spesso riferimento.
Il problema, che però si presentava ai tempi dei primi calcolatori, era che i registri erano a 16 bit, per cui il numero più grande che poteva essere usato come indice era FFFFh=65535 (64KB). La memoria massima indirizzabile sarebbe dovuta essere di 65535 byte e questo era un problema, quindi quello che si è fatto è stato di usare un secondo registro per aumentare lo spazio di indirizzamento.
A questo punto l'indirizzo lineare è dato da :
Ora moltiplicare un numero esadecimale per 16 significa traslarlo a sinistra di una cifra:
70A3h x 10h = 70A30h
Quindi data la copia segmento:offset potremo convertirlo in indirizzo lineare con una traslazione e una semplice addizione:
3000:70C3 => 3000 + 0 => 30000+70C3=370C3h
Il massimo numero ottenibile in questo caso non sarà FFFFFh, come si potrebbe pensare guardando l'indirizzo lineare, ma è sempre data dai valori più grandi del segmento e dell' offset => FFFF:FFFF = FFFF0+FFFF = 10FFEFh = 1114095 cioè circa un mega byte , questo rispetto a prima è stato un passo avanti, anche se la dimensione risulta abbastanza ridicola rispetto alle memoria attuali.
La scelta fatta manifestò subito un po di problemi, il primo fu il minimo incremento di spazio di indirizzamento, il secondo fu la relazione non biunivoca tra i due spazi, infatti per ogni indirizzo lineare ci sono molte copie segmento-offset equivalenti ad esso e questo poteva creare confusione. Vediamo qualche esempio:
0000:70C3 = 0700:00C3 = 070C:0003 = 70C3:0000 = 70C3h
Per questo motivo venne introdotta una notazione normalizzata per questo tipo di indirizzamento:
2000:45D3 => 245D3h => 245D:3 o meglio 245D:0003
Ad ogni modo l'uso di questo tipo di coordinate seg:offset creò molti problemi soprattutto quando si trattava di istruzioni di salto (JMP) le quali davano per scontato che il segmento fosse lo stesso e quindi con risultati alquanto spiacevoli. Quindi, tra blocchi di codice creati da persone diverse, divenne buona pratica di programmazione assicurarsi che il codice di segmento fosse quello giusto.
Riassumendo, vediamo come viene suddivisa la memoria in base all'indirizzo lineare.
Mappa della memoria
- Conventional Memory: I primi 640 KB della memoria di sistema rappresentano la memoria convenzionale che è usata dai programmi DOS standard, driver, programmi residenti in memoria e inizia all'indirizzo lineare 00000h e finisce in 9FFFFh.
- Upper Memory Area (UMA): Questa parte di memoria rappresenta gli ultimi 384 KB del primo megabyte della memoria di sistema e si trova immediatamente sopra la memoria convenzionale quindi inizia da A0000h e finisce in FFFFFh. solitamente è usata dai dispositivi di sistema e dai drivers.
- High Memory Area (HMA): La parte alta dello spazio di indirizzamento è chiamata "high memory area" (abbreviato HMA) ed la sua dimensione è di 65520 byte (64 KB meno 16 byte) e parte da 100000h fino a 10FFEFh. Era considerata memoria estesa dato che inizialmente(negli 80x86) non era accessibile, infatti le linee d'indirizzamento erano solo 20 e gli indirizzi venivano troncati dopo il FFFFFh.
- Extended Memory: viene chiamata memoria estesa tutta la memoria che parte dalla fine della HMA fino ad arrivare alla fine della memoria fisica. Essa è usata dai programmi dati quando il sistema lavora in "modalità protetta"(protected mode). La memoria estesa inizia dall'indirizzo 10FFF0h e la fine dipende dalle dimensioni della memoria fisica installata.
I registri a 16 bit usati nell'indirizzamento sono:
- CS Code Segment: questo registro è usato insieme al registro IP (instruction pointer) per indicare l'istruzione successiva durante l'esecuzione del codice. Per esempio se nel registro IP ho il valore A2h e nel registro CS il valore 30h allora significa che l'indirizzo reale sarà 3A2h (0030:00A2).
Questo registro non può essere modificato direttamente e viene aggiornato dopo le operazioni di "far jump","far call" e "far return", questi salti o chiamate "lontani" infatti prevedono il cambio di segmento, mentre le relative istruzioni "normali" agiscoo all'intero dello stesso segmento di partenza. - DS Data Segment: Di default, il processore lo usa come indice di segmento per i registri generici AX, BX, CX, DX and i registri "indice" SI e DI(se non si tratta di gestione stringhe). Questo significa che se in AX inseriamo l'indirizzo di una cella di memoria, il vero indirizzo per il processore è dato da DS:[AX].
Il registro DS può essere modificato direttamente usando le istruzioni POP e LDS. - SS Stack Segment: Questo registro è usato insieme ai registri SP (stack pointer) e BP (base pointer) per per indicare il segmento relativo all'area di stack in uso. Da notare che quando BX è usato insieme a BP per calcolare un indirizzo di offset allora il segmento usato è indicato dal registro SS.
- ES Extra Segment: Questo registro è usato per puntare a segmenti di memoria extra ovvero definiti dal programmatore per le proprie esigenze. Se si tratta di istruzioni per la gestione stringhe esso indicherà il segmento e il registro DI l'offset dell'indirizzo di destinazione. E' possibile modificare questo registro con molte istruzioni.
In molte istruzioni è possibile modificare il segmento di default dichiarandolo esplicitamente. Per esempio nella usando BX come offset si sottintende che l'indirizzo è DS:[BX], ma se volessi usare CS come segmento basterebbe dichiararlo così CS:[BX].





