La mejor respuesta a esta pregunta en Super User dio una explicación que me resultó satisfactoria al principio de por qué el vector de reinicio no está en la dirección 0 (después , me di cuenta de que no por qué no se podía poner el final de la RAM en 0xFFFFFFFF y luego crecer hacia abajo). Pero, 0xFFFFFFF0 es una dirección muy extraña. ¿Por qué el x86 empieza a ejecutar 16 bytes antes de la parte superior del espacio de direcciones de 32 bits? ¿Se usan esos 16 bytes para algo especial?
Respuestas
¿Demasiados anuncios?Las instrucciones x86 a menudo ocupan más de un byte, y una rutina de reinicio razonable casi seguro que apuntará a múltiples instrucciones.
Si el vector de reinicio se refiriera a 0xFFFF_FFFF, entonces sólo cabría una instrucción de un byte en esa asignación de memoria; casi cualquier funcionalidad de reinicio útil requeriría instrucciones que cruzaran el límite de dirección lineal 0xFFFF_FFFF/0xFFFF_0000 (ya que el segmento de código está configurado con base 0xFFFF_0000).
Colocando el código de reinicio en esta dirección, es posible encajar unas cuantas instrucciones (incluyendo un salto) sin requerir memoria válida en 0xFFFF_0000.
La razón por la que está 16 bytes por debajo de la parte superior de la memoria viene de cómo se cargaban los registros de la CPU 8086 original durante el reinicio. E incluso eso puede tener algo que ver con la compatibilidad para CPU 8085 más antiguas. Y esta compatibilidad se ha trasladado a chips posteriores, como el 80286, 80386, etc.
Claro que podrían haber seleccionado cualquier valor, pero como los vectores de interrupción están fijados en la parte inferior del área de memoria donde se pretende que haya RAM, la ROM del programa está pensada para ponerse en la parte superior del área de memoria.
Aunque se podría seleccionar cualquier dirección en la parte superior de la memoria, tiene sentido que sea lo más cercana posible a las últimas direcciones de memoria, para que no dicte lo grande que debe ser el área de la ROM, y puedas usar una ROM tan pequeña como puedas.
Y tampoco debe estar demasiado cerca de la última dirección de memoria, para dejar espacio suficiente a las instrucciones para que, al menos, salten a ejecutar código en otro lugar. Un opcode de salto corto ocupa dos bytes, el salto cercano ocupa tres bytes y el salto lejano ocupa 5 bytes, por lo que tiene sentido reservar al menos 5 bytes para el área.
Como el 8086 tiene un espacio de direcciones de 20 bits (o 1 megabyte), y los diseñadores lo eligieron utiliza el enfoque de memoria segmentada donde tienes un segmento de 16 bits y un offset de 16 bits para apuntar a una dirección lineal de 20 bits. Esto significa que cada registro de segmento puede seleccionar una dirección base para un desplazamiento de 16 bits con una granularidad de dirección base de 16 bytes. Básicamente, el direccionamiento del 8085 de 16 bits se amplió con la adición de los registros de segmento.
Así que durante el reinicio del hardware, el registro contador de programa (IP) se establece en 0x0000, al igual que en una CPU 8085. Y para llegar al final de la memoria, el registro de segmento de código (CS) se pone a 0xFFFF. Esto hace que la CPU comience a 16 bytes del final de la memoria.
Hay muchas combinaciones CS:IP válidas que suman la dirección lineal de 0xFFFF0, pero este era realmente el método más sencillo, ya que todos los bits del registro se cargan con el mismo valor, IP con cero bits, y CS con uno, por lo que cargar una combinación especial de bits en cualquiera de los registros no era necesario.
Sin embargo, las CPU posteriores ya rompieron un poco esta compatibilidad, pero de una forma que no importa mucho.
Por ejemplo, un 80286 carga el registro IP con 0xFFF0. Y como utiliza un bus de memoria de 24 bits para acceder a 16 Mbytes de memoria, y la ROM debe estar todavía al final de los 16 Mbytes de memoria direccionable, el valor del registro CS se reinicia a 0xF000, de modo que el modo real CS:IP apunta a 0xFFFF0, y la base del selector CS se establece en la dirección base de 0xFF0000 para establecer la base de memoria física, de modo que la dirección física es 0xFFFFF0. De esta manera, si acabas de rediseñar el viejo sistema 8086 para tener un 80286, se podría hacer compatible para arrancar la ROM original.
Cuando eso se expandió al 80386 de 32 bits, también carga el registro IP de 32 bits con 0x0000FFF0, el registro CS con 0xF000, y la base del selector CS con 0xFFFF0000, por lo que la dirección física es 0xFFFFFFF0. Así que si un sistema 80286 fuera rediseñado para llevar una CPU 80386SX, podría hacerse compatible para arrancar la ROM original.