stringtranslate.com

Segmentación de memoria x86

La arquitectura del conjunto de instrucciones de la computadora Intel x86 ha admitido la segmentación de memoria desde el Intel 8086 original en 1978. Permite que los programas direccionen más de 64 KB (65,536  bytes ) de memoria, el límite en los procesadores 80xx anteriores. En 1982, el Intel 80286 agregó soporte para memoria virtual y protección de memoria ; el modo original fue renombrado como modo real , y la nueva versión fue nombrada como modo protegido . La arquitectura x86-64 , introducida en 2003, ha abandonado en gran medida el soporte para la segmentación en el modo de 64 bits.

Tanto en el modo real como en el protegido, el sistema utiliza registros de segmento de 16 bits para derivar la dirección de memoria real.En modo real, los registros CS, DS, SS y ES apuntan al segmento de código de programa (CS) utilizado actualmente, al segmento de datos actual (DS), al segmento de pila actual (SS) y a un segmento adicional determinado por el programador (ES). El Intel 80386 , introducido en 1985, añade dos registros de segmento adicionales, FS y GS, sin usos específicos definidos por el hardware. La forma en que se utilizan los registros de segmento difiere entre los dos modos. [1]

La elección del segmento normalmente la realiza el procesador de forma predeterminada según la función que se esté ejecutando. Las instrucciones siempre se obtienen del segmento de código. Cualquier inserción o extracción de la pila o cualquier referencia de datos que haga referencia a la pila utiliza el segmento de pila. Todas las demás referencias a datos utilizan el segmento de datos. El segmento adicional es el destino predeterminado para las operaciones de cadena (por ejemplo, MOVS o CMPS). FS y GS no tienen usos asignados por hardware. El formato de instrucción permite un byte de prefijo de segmento opcional que se puede utilizar para anular el segmento predeterminado para las instrucciones seleccionadas si se desea. [2]

Modo real

Tres segmentos en la memoria en modo real (haga clic en la imagen para ampliarla). Hay una superposición entre el segmento 2 y el segmento 3; los bytes en el área turquesa se pueden utilizar desde ambos selectores de segmento.

En modo real o modo V86 , el tamaño de un segmento puede variar desde 1 byte hasta 65.536 bytes (utilizando desplazamientos de 16 bits).

El selector de segmento de 16 bits en el registro de segmento se interpreta como los 16 bits más significativos de una dirección lineal de 20 bits, llamada dirección de segmento, de la cual los cuatro bits menos significativos restantes son todos ceros. La dirección de segmento siempre se suma a un desplazamiento de 16 bits en la instrucción para obtener una dirección lineal , que es la misma que la dirección física en este modo. Por ejemplo, la dirección segmentada 06EFh:1234h (aquí el sufijo "h" significa hexadecimal ) tiene un selector de segmento de 06EFh, que representa una dirección de segmento de 06EF0h, a la que se suma el desplazamiento, lo que produce la dirección lineal 06EF0h + 1234h = 08124h.

Debido a la forma en que se agregan la dirección del segmento y el desplazamiento, una única dirección lineal se puede asignar a hasta 2 pares de segmento:desplazamiento distintos de 12 = 4096. Por ejemplo, la dirección lineal 08124h puede tener las direcciones segmentadas 06EFh:1234h, 0812h:0004h, 0000h:8124h, etc.

Esto puede resultar confuso para los programadores acostumbrados a esquemas de direccionamiento únicos, pero también se puede utilizar de forma ventajosa, por ejemplo, al direccionar múltiples estructuras de datos anidadas. Si bien los segmentos de modo real siempre tienen una longitud de 64  KB , el efecto práctico es solo que ningún segmento puede tener más de 64 KB, en lugar de que todos los segmentos deben tener una longitud de 64 KB. Debido a que no hay protección ni limitación de privilegios en el modo real, incluso si un segmento pudiera definirse para que sea más pequeño que 64 KB, seguiría siendo totalmente responsabilidad de los programas coordinar y mantenerse dentro de los límites de sus segmentos, ya que cualquier programa siempre puede acceder a cualquier memoria (ya que puede configurar arbitrariamente selectores de segmento para cambiar las direcciones de segmento sin ninguna supervisión). Por lo tanto, el modo real también se puede imaginar como si tuviera una longitud variable para cada segmento, en el rango de 1 a 65.536 bytes, que simplemente no es impuesta por la CPU.

(Aquí se muestran los ceros iniciales de la dirección lineal, las direcciones segmentadas y los campos de segmento y desplazamiento para mayor claridad. Normalmente se omiten).

El espacio de dirección efectivo de 20 bits del modo real limita la memoria direccionable a 220 bytes  , o 1.048.576 bytes (1  MB ). Esto se derivó directamente del diseño de hardware del Intel 8086 (y, posteriormente, del estrechamente relacionado 8088), que tenía exactamente 20 pines de dirección . (Ambos se empaquetaban en paquetes DIP de 40 pines; incluso con solo 20 líneas de dirección, los buses de dirección y datos se multiplexaron para que cupieran todas las líneas de dirección y datos dentro del conteo limitado de pines).

Cada segmento comienza en un múltiplo de 16 bytes, llamado párrafo , desde el comienzo del espacio de direcciones lineal (plano). Es decir, en intervalos de 16 bytes. Dado que todos los segmentos tienen una longitud de 64 KB, esto explica cómo puede ocurrir la superposición entre segmentos y por qué se puede acceder a cualquier ubicación en el espacio de direcciones de memoria lineal con muchos pares segmento:desplazamiento. La ubicación real del comienzo de un segmento en el espacio de direcciones lineal se puede calcular con segmento×16. Un valor de segmento de 0Ch (12) daría una dirección lineal en C0h (192) en el espacio de direcciones lineal. El desplazamiento de dirección se puede sumar a este número. 0Ch:0Fh (12:15) sería C0h+0Fh=CFh (192+15=207), siendo CFh (207) la dirección lineal. Estas traducciones de direcciones las lleva a cabo la unidad de segmentación de la CPU. El último segmento, FFFFh (65535), comienza en la dirección lineal FFFF0h (1048560), 16 bytes antes del final del espacio de direcciones de 20 bits y, por lo tanto, puede acceder, con un desplazamiento de hasta 65.536 bytes, hasta 65.520 (65536−16) bytes más allá del final del espacio de direcciones de 20 bits del 8088. En el 8088, estos accesos a direcciones se envolvían hasta el principio del espacio de direcciones de modo que 65535:16 accedería a la dirección 0 y 65533:1000 accedería a la dirección 952 del espacio de direcciones lineal. El uso de esta característica por parte de los programadores condujo a los problemas de compatibilidad de Gate A20 en generaciones de CPU posteriores, donde el espacio de direcciones lineal se expandió más allá de los 20 bits.

En el modo real de 16 bits, permitir que las aplicaciones utilicen varios segmentos de memoria (para acceder a más memoria que la disponible en cualquier segmento de 64K) es bastante complejo, pero se consideraba un mal necesario para todas las herramientas, excepto las más pequeñas (que podrían funcionar con menos memoria). La raíz del problema es que no hay disponibles instrucciones de aritmética de direcciones adecuadas para el direccionamiento plano de todo el rango de memoria. [ cita requerida ] El direccionamiento plano es posible aplicando múltiples instrucciones, lo que, sin embargo, conduce a programas más lentos.

El concepto de modelo de memoria se deriva de la configuración de los registros de segmento. Por ejemplo, en el modelo diminuto CS=DS=SS, es decir, el código, los datos y la pila del programa están todos contenidos en un único segmento de 64 KB. En el modelo de memoria pequeña DS=SS, por lo que tanto los datos como la pila residen en el mismo segmento; CS apunta a un segmento de código diferente de hasta 64 KB.

Modo protegido

Tres segmentos en memoria en modo protegido (haga clic en la imagen para ampliar), con la tabla de descriptores locales .

80286 modo protegido

El modo protegido del 80286 extiende el espacio de direcciones del procesador a 224 bytes (16 megabytes), pero no ajustando el valor de desplazamiento. En su lugar, los registros de segmento de 16 bits ahora contienen un índice en una tabla de descriptores de segmento que contienen direcciones base de 24 bits a las que se agrega el desplazamiento. Para admitir software antiguo, el procesador se inicia en "modo real", un modo en el que utiliza el modelo de direccionamiento segmentado del 8086. Sin embargo, hay una pequeña diferencia: la dirección física resultante ya no se trunca a 20 bits, por lo que los punteros de modo real (pero no los punteros del 8086) ahora pueden referirse a direcciones entre 100000 16 y 10FFEF 16 . Esta región de memoria de aproximadamente 64 kilobytes se conocía como Área de memoria alta (HMA), y las versiones posteriores de DOS podían usarla para aumentar la memoria "convencional" disponible (es decir, dentro de los primeros MB ). Con la incorporación del HMA, el espacio total de direcciones es de aproximadamente 1,06 MB. Aunque el 80286 no trunca las direcciones en modo real a 20 bits, un sistema que contenga un 80286 puede hacerlo con hardware externo al procesador, desactivando la línea de dirección número 21, la línea A20 . El IBM PC AT proporcionó el hardware para hacer esto (para lograr compatibilidad total con el software de los modelos IBM PC y PC/XT originales ), y así lo hicieron todos los clones de PC "de clase AT " posteriores.

El modo protegido 286 rara vez se utilizaba, ya que habría excluido a la gran cantidad de usuarios con máquinas 8086/88. Además, todavía era necesario dividir la memoria en segmentos de 64k como se hacía en el modo real. Esta limitación se puede solucionar en CPU de 32 bits que permiten el uso de punteros de memoria de un tamaño mayor a 64k, sin embargo, como el campo Límite de segmento tiene solo 24 bits de longitud, el tamaño máximo de segmento que se puede crear es de 16 MB (aunque se puede usar paginación para asignar más memoria, ningún segmento individual puede superar los 16 MB). Este método se usaba comúnmente en aplicaciones de Windows 3.x para producir un espacio de memoria plano, aunque como el sistema operativo en sí mismo todavía era de 16 bits, no se podían realizar llamadas a la API con instrucciones de 32 bits. Por lo tanto, todavía era necesario colocar todo el código que realiza llamadas a la API en segmentos de 64k.

Una vez que se invocaba el modo protegido 286, no se podía salir de él excepto realizando un reinicio del hardware. Las máquinas que seguían el creciente estándar IBM PC/AT podían simular un reinicio de la CPU a través del controlador de teclado estandarizado, pero esto era significativamente lento. Windows 3.x solucionó ambos problemas al activar intencionalmente un triple fallo en los mecanismos de manejo de interrupciones de la CPU, lo que haría que la CPU volviera al modo real, casi instantáneamente. [3]

Flujo de trabajo detallado de la unidad de segmentación

Una dirección lógica consta de un selector de segmento de 16 bits (que proporciona 13+1 bits de dirección) y un desplazamiento de 16 bits. El selector de segmento debe estar ubicado en uno de los registros de segmento. Ese selector consta de un nivel de privilegio solicitado (RPL) de 2 bits, un indicador de tabla (TI) de 1 bit y un índice de 13 bits.

Al intentar traducir la dirección de una dirección lógica dada, el procesador lee la estructura de descriptor de segmento de 64 bits de la tabla de descriptores globales cuando TI=0 o de la tabla de descriptores locales cuando TI=1. Luego realiza la verificación de privilegios:

máx(CPL, RPL) ≤ DPL

donde CPL es el nivel de privilegio actual (que se encuentra en los 2 bits inferiores del registro CS), RPL es el nivel de privilegio solicitado al selector de segmento y DPL es el nivel de privilegio del descriptor del segmento (que se encuentra en el descriptor). Todos los niveles de privilegio son números enteros en el rango de 0 a 3, donde el número más bajo corresponde al privilegio más alto.

Si la desigualdad es falsa, el procesador genera una falla de protección general (GP) . De lo contrario, continúa la traducción de direcciones. Luego, el procesador toma el desplazamiento de 32 o 16 bits y lo compara con el límite de segmento especificado en el descriptor de segmento. Si es mayor, se genera una falla de GP. De lo contrario, el procesador agrega la base de segmento de 24 bits, especificada en el descriptor, al desplazamiento, creando una dirección física lineal.

La comprobación de privilegios se realiza únicamente cuando se carga el registro de segmento, porque los descriptores de segmento se almacenan en caché en partes ocultas de los registros de segmento. [ cita requerida ] [1]

80386 modo protegido

En el Intel 80386 y posteriores, el modo protegido conserva el mecanismo de segmentación del modo protegido del 80286, pero se ha añadido una unidad de paginación como segunda capa de traducción de direcciones entre la unidad de segmentación y el bus físico. Además, y esto es importante, los desplazamientos de dirección son de 32 bits (en lugar de 16 bits) y la base de segmento en cada descriptor de segmento también es de 32 bits (en lugar de 24 bits). El funcionamiento general de la unidad de segmentación no ha cambiado en lo demás. La unidad de paginación puede estar habilitada o deshabilitada; si está deshabilitada, el funcionamiento es el mismo que en el 80286. Si la unidad de paginación está habilitada, las direcciones de un segmento son ahora direcciones virtuales, en lugar de direcciones físicas como lo eran en el 80286. Es decir, la dirección inicial del segmento, el desplazamiento y la dirección final de 32 bits que la unidad de segmentación obtiene sumando las dos son todas direcciones virtuales (o lógicas) cuando la unidad de paginación está habilitada. Cuando la unidad de segmentación genera y valida estas direcciones virtuales de 32 bits, la unidad de paginación habilitada finalmente traduce estas direcciones virtuales en direcciones físicas. Las direcciones físicas son de 32 bits en el 386 , pero pueden ser más grandes en procesadores más nuevos que admiten la extensión de dirección física .

El 80386 también introdujo dos nuevos registros de segmento de datos de propósito general, FS y GS, al conjunto original de cuatro registros de segmento (CS, DS, ES y SS).

Una CPU 386 puede volver al modo real borrando un bit del registro de control CR0, pero se trata de una operación privilegiada para reforzar la seguridad y la robustez. A modo de comparación, una CPU 286 solo podría volver al modo real forzando un reinicio del procesador, por ejemplo, mediante un triple fallo o utilizando hardware externo.

Desarrollos posteriores

La arquitectura x86-64 no utiliza segmentación en modo largo (modo de 64 bits). Cuatro de los registros de segmento, CS, SS, DS y ES, están forzados a la dirección base 0 y el límite a 2 64 . Los registros de segmento FS y GS aún pueden tener una dirección base distinta de cero. Esto permite que los sistemas operativos utilicen estos segmentos para fines especiales. A diferencia del mecanismo de tabla de descriptores globales utilizado por los modos heredados, la dirección base de estos segmentos se almacena en un registro específico del modelo . La arquitectura x86-64 proporciona además la instrucción especial SWAPGS , que permite intercambiar las direcciones base del modo kernel y del modo usuario .

Por ejemplo, Microsoft Windows en x86-64 utiliza el segmento GS para señalar el bloque de entorno de subprocesos , una pequeña estructura de datos para cada subproceso , que contiene información sobre el manejo de excepciones, variables locales de subprocesos y otros estados por subproceso. De manera similar, el núcleo de Linux utiliza el segmento GS para almacenar datos por CPU.

GS/FS también se utilizan en el almacenamiento local de subprocesos de gcc y en el protector de pila basado en canario .

Prácticas

Las direcciones lógicas se pueden especificar explícitamente en lenguaje ensamblador x86 , por ejemplo (sintaxis AT&T):

movl $42, %fs:(%eax) ; Equivalente a M[fs:eax]<-42) en RTL

o en sintaxis Intel :

mov dword [ fs : eax ], 42   

Sin embargo, los registros de segmento normalmente se utilizan implícitamente.

La segmentación no se puede desactivar en los procesadores x86-32 (esto también es válido para el modo de 64 bits, pero queda fuera del alcance de esta discusión), por lo que muchos sistemas operativos de 32 bits simulan un modelo de memoria plana al establecer las bases de todos los segmentos en 0 para que la segmentación sea neutral para los programas. Por ejemplo, el núcleo de Linux configura solo 4 segmentos de propósito general:

Dado que la base se establece en 0 en todos los casos y el límite en 4 GiB, la unidad de segmentación no afecta las direcciones que el programa emite antes de que lleguen a la unidad de paginación . (Esto, por supuesto, se refiere a los procesadores 80386 y posteriores, ya que los procesadores x86 anteriores no tienen una unidad de paginación).

El sistema operativo Linux actual también utiliza GS para señalar el almacenamiento local del hilo .

Los segmentos se pueden definir como segmentos de código, datos o del sistema. Hay bits de permiso adicionales para que los segmentos sean de solo lectura, lectura/escritura, ejecución, etc.

En el modo protegido, el código siempre puede modificar todos los registros de segmento excepto CS (el selector de segmento de código ). Esto se debe a que el nivel de privilegio actual (CPL) del procesador se almacena en los 2 bits inferiores del registro CS. Las únicas formas de aumentar el nivel de privilegio del procesador (y recargar CS) son a través de las instrucciones lcall (llamada lejana) e int (interrupción) . De manera similar, las únicas formas de reducir el nivel de privilegio (y recargar CS) son a través de las instrucciones lret (retorno lejano) e iret (retorno de interrupción). En el modo real, el código también puede modificar el registro CS haciendo un salto lejano (o usando una POP CSinstrucción no documentada en el 8086 o 8088). [4] Por supuesto, en el modo real, no hay niveles de privilegio; todos los programas tienen acceso absoluto sin control a toda la memoria y a todas las instrucciones de la CPU.

Para obtener más información sobre la segmentación, consulte los manuales IA-32 disponibles gratuitamente en los sitios web de AMD o Intel .

Notas y referencias

  1. ^ ab "Intel 64 and IA-32 Architectures Software Developer's Manual", Volume 3, "System Programming Guide", publicado en 2011, página "Vol. 3A 3-11", el libro está escrito: " Cada registro de segmento tiene una parte "visible" y una parte "oculta". (La parte oculta a veces se denomina "caché de descriptor" o "registro de sombra"). Cuando se carga un selector de segmento en la parte visible de un registro de segmento, el procesador también carga la parte oculta del registro de segmento con la dirección base, el límite de segmento y la información de control de acceso del descriptor de segmento al que apunta el selector de segmento. La información almacenada en caché en el registro de segmento (visible y oculta) permite al procesador traducir direcciones sin tomar ciclos de bus adicionales para leer la dirección base y el límite del descriptor de segmento. "
  2. ^ Intel Corporation (2004). Manual del desarrollador de software de arquitectura Intel IA-32 Volumen 1: Arquitectura básica (PDF) .
  3. ^ "Blogs de desarrollo".
  4. ^ POP CS debe usarse con sumo cuidado y tiene una utilidad limitada, ya que cambia inmediatamente la dirección efectiva que se calculará a partir del puntero de instrucción para obtener la siguiente instrucción. Generalmente, un salto lejano es mucho más útil. La existencia de POP CSes probablemente un accidente, ya que sigue un patrón de códigos de operación de instrucción PUSH y POP para los cuatro registros de segmento en el 8086 y el 8088.

Véase también

Enlaces externos