assembly x86 operating-system kernel x86-16

assembly - ¿Qué son los segmentos y cómo pueden abordarse en el modo 8086?



x86 operating-system (3)

En esta respuesta, solo estoy dando una explicación para el modo real. En el modo protegido, la secuenciación es un poco más complicada y, como probablemente nunca vayas a escribir un programa de modo protegido segmentado, no voy a explicar esto.

Los segmentos son muy simples en realidad. La CPU 8086 tiene cuatro registros de segmento llamados cs , ds , es y ss . cuando accede a la memoria, la CPU calcula la dirección física de esta manera:

physical_address = segment * 16 + effective_address

donde dirección_ejecución es la dirección indicada por el operando de memoria y segment es el contenido del registro de segmento para este acceso a la memoria. Por defecto, cs se usa cuando la CPU busca código, ss se usa para apilar y salirse de la pila, así como operandos de memoria con bp ya que el registro base se usa para ciertas instrucciones especiales y ds se usa en cualquier otro lado. El registro de segmento puede anularse usando un prefijo de segmento.

¿Qué significa eso en la práctica? El 8086 tiene registros de 16 bits, por lo que usar un registro para almacenar una dirección nos permite abordar hasta 65536 bytes de RAM. La idea detrás del uso de registros de segmentos es que podemos almacenar bits adicionales de la dirección en un segmento , lo que permite al programador abordar un poco más de 2 20 = 1048576 bytes = 1 MiB de RAM. Esta RAM se divide en 65536 segmentos superpuestos de 65536 bytes cada uno, donde cada segmento es un valor que puede cargar en un registro de segmento.

Cada uno de estos segmentos comienza en una dirección que es un múltiplo de 16, como puede ver en la lógica de cálculo de direcciones anterior. Puede asignarle un total de 1 espacio de direcciones físicas MiB con 16 segmentos que no se superponen (como explicó en su pregunta) 0x0000 , 0x1000 , ..., 0xf000 , pero también puede usar cualquier selector de segmento que desee.

Desde que comencé con la programación 8086 Assembly Language, me he estado preguntando acerca de estos segmentos y registros de segmentos. El problema al que me enfrento es que no puedo tener una imagen visual de los segmentos que tengo en mente y, por lo tanto, los conceptos no son claros para mí.

  • ¿Alguien puede ayudarme a entender el concepto relacionándolo con un escenario del mundo real ? También tengo las siguientes preguntas:

Pregunta 1:

Por lo que he entendido, en el modo real de 16 bits con 20 direcciones habilitadas, podríamos dividir la memoria física en 16 segmentos con 64 Kb cada uno. El primer segmento comienza en 0x00000 . Cuál será la dirección de inicio del próximo segmento. ¿Será mediante la adición de 0x10000 (65536 = 64KiB)?

Pregunta 2:

Esta pregunta es un poco rara para preguntar aquí, pero aún ASÍ es mi única opción. Supongamos que si 0x6000 una dirección de 0x6000 de 0x6000 , ¿cómo puedo encontrar el segmento al que pertenece para abordarlo?

Gracias


... podríamos dividir la memoria física en 16 segmentos con 64 Kb cada uno.

Es cierto, pero más exacto sería decir esto como "16 segmentos no superpuestos ", ya que también existe la posibilidad de dividir la memoria en 65536 segmentos superpuestos .

Cuando la línea A20 está habilitada, tenemos más de 1MB para jugar. (1048576 + 65536-16) Al establecer el registro de segmento relevante en 0xFFFF, podemos obtener acceso a la memoria entre 0x0FFFF0 y 0x10FFEF.

Las principales características de ambos tipos de segmentos son:

  1. Segmentos que no se solapan
    • Contiene 65536 bytes.
    • Están separados 65536 bytes en memoria.
    • Esta es la forma en que las personas a menudo vemos convenientemente la memoria. Nos permite decir que hemos puesto
      • la ventana de gráficos en el segmento A (0xA0000-0xAFFFF)
      • la ventana de video de texto en el segmento B (0xB0000-0xBFFFF)
      • el BIOS en el segmento F (0xF0000-0xFFFFF)
  2. Segmentos superpuestos

    • Contiene 65536 bytes.
    • Están a 16 bytes de distancia en la memoria.

      A veces verá que las personas se refieren a un segmento de 16 bytes de memoria como un segmento, pero obviamente esto es incorrecto. Sin embargo, hay un nombre ampliamente utilizado para tal cantidad de memoria: " párrafo ".

    • Esta es la forma en que la CPU (en el modo de dirección real) ve la memoria.
      El procesador calcula la dirección lineal usando los siguientes pasos:
      • Primero se calcula la dirección de desplazamiento desde los operandos de la instrucción. El resultado se trunca para caber en 16 bits (64KB envolvente).
      • A continuación se agrega el producto de SegmentRegister * 16
        Si la línea A20 está inactiva, el resultado se trunca para que quepa en 20 bits (1MB envolvente).
        Si la línea A20 está activa, el resultado se utiliza tal cual y, por lo tanto, no se produce un envolvente de 1 MB.

Supongamos que si recibo una dirección de desplazamiento de 0x6000, ¿cómo puedo encontrar el segmento al que pertenece para abordarlo?

Aquí nuevamente el problema radica en el fraseo.

Si por "una dirección de desplazamiento de 0x6000" quiere decir una compensación como la que normalmente usamos en la programación del modo de dirección real, entonces la pregunta no puede ser respondida ya que existe una compensación de 0x6000 en cada segmento que existe .

Si, por otro lado, la frase "una dirección de desplazamiento de 0x6000" en realidad se refiere a la dirección lineal 0x6000, entonces hay muchas soluciones para el registro de segmento:

segment:offset -------------- 0000:6000 0001:5FF0 0002:5FE0 0003:5FD0 ... 05FD:0030 05FE:0020 05FF:0010 0600:0000

Como puede ver, hay 0x0601 posibles configuraciones de registro de segmento para llegar a la dirección lineal 0x6000.
Lo anterior se aplica a cuando la línea A20 está habilitada. Si A20 estaba inactivo, se puede llegar a la dirección lineal 0x6000 (como cualquier otra dirección lineal de 0 a 1 MB-1) de forma precisa de 0x1000 (4096):

segment:offset -------------- F601:FFF0 F602:FFE0 F603:FFD0 ... FFFD:6030 FFFE:6020 FFFF:6010 0000:6000 0001:5FF0 0002:5FE0 0003:5FD0 ... 05FD:0030 05FE:0020 05FF:0010 0600:0000


En general, los segmentos son intervalos de memoria usando un sistema de indexación interno.

Si piensa en la memoria como una larga serie de bytes mem[0x100000] , puede especificar un segmento continuo seg=mem[a:a+b] , con len(seg)=b donde

  • seg[0] se almacena en mem[a]
  • seg[1] se almacena en mem[a+1]
  • ...
  • seg[b-1] se almacena en mem[a+b-1] .

La ventaja de utilizar segmentos es que las direcciones (índice de seg) dentro de un segmento pueden ser más cortas, por ejemplo, en el caso del 8086, la memoria direccionable sube a la dirección física (índice de mem) 2 20 -1, (en el los sucesores incluso puede ir un poco más allá, con direcciones de 16 bits en segmentos). También es bastante simple colocar un programa en cualquier lugar de la memoria porque solo necesita asignar uno o algunos segmentos libres y la mayoría de las direcciones operan dentro del segmento dedicado sin necesidad de ajustarlas.

En el 8086, todos los segmentos tienen una longitud de 2 16 bytes, por lo que las direcciones intrasegmentos se ajustan a 16 bits, lo que facilita su manejo. Para la dirección de inicio, puede seleccionar cualquier dirección en la memoria física que pueda dividirse entre 16 y sea igual o inferior a 0xFFFF0 . Esto significa que cualquier dirección física se encuentra en múltiples segmentos. Los segmentos están descritos por un número de 16 bits que es la dirección de inicio dividida por 16.

Entonces, el segmento 0xBADA corresponde al segmento que comienza en 0xBADA0 .