En este artículo aprenderemos como funciona un microprocesador en su fase de arranque mas temprana(modo real 16bits), también comprenderemos porque con un microprocesador que tiene registros de 16 bits no es capaz de direccionar mas de 64KB de RAM y como aumentar este límite a 1MB gracias a la segmentación de memoria.
Antes de comenzar es recomendable que leas estos artículos anteriores:
Todos los procesadores cuando arrancan, se comportan como un 8086 en modo real de 16 bits. Será el sistema operativo el que prepare el equipo para entrar en modo protegido y acceder así a todos los recursos, mecanismos de protección, etc, abandonando ya la compatibilidad con los procesadores anteriores al 386.
En modo real 16 bits los registros son de 16 bits, para acceder a una dirección de memoria hay que almacenar dicha dirección en un registro, como los registros son de 16 bits el valor máximo de memoria a la que podemos acceder es 2^16=64k posiciones.
Para solventar este problema los diseñadores de cpus añadieron unos registros especiales llamados registros de segmento.
- cs: CodeSegment
- ds: DataSegment
- ss: StackSegment
- es: ExtraSegment
El segmento actúa a modo de índice, la RAM se divide en segmentos de 64k, combinando la posición de segmento mas el offset obtenemos la dirección de memoria deseada.
Pongamos un ejemplo:
mov dx,[0]
En este caso copiamos lo que haya en la dirección de memoria 0 al registro dx, pero de que segmento?. En este caso de ds ya que es el segmento por defecto cuando utilizamos direccionamiento absoluto, esto se explica mas abajo en este mismo artículo.
Si necesitamos acceder a otro segmento debemos indicarlo mediante un prefijo:
mov dx,[es:0]
Este modo de direccionamiento se le llama Absoluto, hay varias maneras de indicar el offset, como por ejemplo en base al valor de un registro, dependiendo del registro que utilicemos para indicar el offset el segmento por defecto será uno u otro:
Nombre | Offset | Segmento por defecto |
---|---|---|
Absoluto | Valor inmediato | ds |
Indirecto con base | bx+x | ds |
bp+x | ss | |
Indirecto con índice | di+x | ds |
si+x | ds | |
Ind. con base e índice | bx+DI+x | ds |
bx+SI+x | ds | |
bp+DI+x | ss | |
bp+SI+x | ss |
En el siguiente ejemplo vamos a mover lo que haya en el segmento es con offset la dirección que haya en bp+100:
mov al,[es:bp+100h]
En este ejemplo hemos utilizado el modo de direccionamiento bp+x por lo tanto el segmento por defecto es ss, hemos tenido que indicar el prefijo para cambiarlo.
NOTA: En general no es buena idea usar prefijos de segmento, pues las instrucciones que los usan se codifican en más bytes y por tanto son más lentas.
La dirección de memoria absoluta se calcula multiplicando por 16 el valor del segmento y sumando el offset:
mov ds, [0x4d]
mov ax, [0x20]
ax tendría el valor:
(16 * 0x4d) + 0x20
Si calculamos la dirección mas alta posible utilizando este sistema nos dá 1MB de memoria direccionable:
(16 * 0xffff) + 0xffff = 1MB
NOTA: Cuando trabajemos con registros de segmento es importante recordar que no podemos mover literales directamente a estos hay que pasar por algún registros de propósito general intermedio.
En este artículo anterior ya habíamos utilizado la segmentación aunque no se hubiese entrado en detalle, el segmento donde la BIOS había copiado nuestro código era 0x7c00.
Veamos un ejemplo donde utilizamos direccionamiento absoluto(Tabla:Segmento por defecto DS) a distintas direcciones de memoria para imprimir por pantalla su contenido.
mov ah, 0x0e; tty mode
mov al, [the_secret]; we are calculating the address without beeing aware of our code segment
int 0x10
mov bx, 0x7c0; we copy segment value to a general purpose register
mov ds, bx; in that way we can copy segment value from general purpose register to segment register
mov al, [the_secret]
int 0x10
mov al, [es:the_secret]; we use prefix segment annotation, es has an unknown value so memory address can be any value
int 0x10
mov bx, 0x7c0; we do the same as second try but we use es instead of ds as segment register and use prefix annotation
mov es, bx
mov al, [es:the_secret]
int 0x10
jmp $
the_secret:
db "X"
times 510 - ($-$$) db 0
dw 0xaa55
Generamos la imagen
La cargamos en Qemu:
SeaBIOS (version rel-1.12.1-0-ga5cab58e9a3f-prebuilt.qemu.org)
iPXE (http://ipxe.org) 00:03.0 C980 PCI2.10 PnP PMM+07F91410+07EF1410 C980
Booting from Hard Disk...
çXçX
Como era de esperar solo han funcionado el segundo y cuarto intento de impresión del carácter.