stringtranslate.com

segmentación de memoria x86

La segmentación de memoria x86 se refiere a la implementación de la segmentación de memoria en la arquitectura del conjunto de instrucciones de computadora Intel x86 . La segmentación se introdujo en el Intel 8086 en 1978 como una forma de permitir que los programas direccionen más de 64 KB (65 536  bytes ) de memoria. El Intel 80286 introdujo una segunda versión de segmentación en 1982 que agregó soporte para memoria virtual y protección de memoria . En este punto, el modo original pasó a llamarse modo real y la nueva versión se denominó modo protegido . La arquitectura x86-64 , introducida en 2003, ha dejado de ser compatible con la segmentación en 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) actualmente utilizado, 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 , presentado 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 ejecuta. Las instrucciones siempre se obtienen del segmento de código. Cualquier inserción o extracción de 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 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 usar para anular el segmento predeterminado para instrucciones seleccionadas si se desea. [2]

modo real

Tres segmentos en memoria en modo real (click en la imagen para ampliar). Existe una superposición entre el segmento 2 y el segmento 3; los bytes en el área turquesa se pueden usar 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 (usando compensaciones 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 del segmento siempre se suma a un desplazamiento de 16 bits en la instrucción para producir 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 12 = 4096 pares distintos de segmento:desplazamiento. Por ejemplo, la dirección lineal 08124h puede tener las direcciones segmentadas 06EFh:1234h, 0812h:0004h, 0000h:8124h, etc.

Esto podría resultar confuso para los programadores acostumbrados a esquemas de direccionamiento únicos, pero también puede utilizarse con ventaja, por ejemplo, al abordar múltiples estructuras de datos anidadas. Si bien los segmentos en modo real siempre tienen una longitud de 64  KB , el efecto práctico es que ningún segmento puede tener más de 64 KB, en lugar de que cada segmento deba tener 64 KB de longitud. Debido a que no hay limitación de protección o privilegios en el modo real, incluso si se pudiera definir un segmento para que tenga menos de 64 KB, aún sería totalmente responsabilidad de los programas coordinar y mantener dentro de los límites de sus segmentos, como cualquier programa puede hacerlo. acceda siempre a cualquier memoria (ya que puede configurar arbitrariamente selectores de segmentos para cambiar las direcciones de los segmentos sin ninguna supervisión). Por lo tanto, también se puede imaginar que el modo real tiene una longitud variable para cada segmento, en el rango de 1 a 65.536 bytes, que simplemente no es impuesta por la CPU.

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

El espacio de direcciones efectivo de 20 bits del modo real limita la memoria direccionable a 220  bytes, o 1.048.576 bytes (1  MB ). Esto 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 estaban empaquetados en paquetes DIP de 40 pines; incluso con solo 20 líneas de dirección, los buses de direcciones y datos se multiplexaron para ajustarse a todas las líneas de direcciones y datos dentro del número 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, a intervalos de 16 bytes. Dado que todos los segmentos tienen 64 KB de largo, 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. Luego se puede agregar el desplazamiento de dirección 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 realiza 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 así, 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 8088 de 20 bits. En el 8088, estos accesos a direcciones se ajustaron al comienzo 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 generó problemas de compatibilidad con Gate A20 en generaciones posteriores de CPU, donde el espacio de direcciones lineales se expandió más allá de los 20 bits.

En el modo real de 16 bits, permitir que las aplicaciones utilicen múltiples segmentos de memoria (para acceder a más memoria de 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 ( lo cual podría funcionar con menos memoria). La raíz del problema es que no se dispone de instrucciones de aritmética de direcciones apropiadas para el direccionamiento plano de todo el rango de memoria. [ cita necesaria ] 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 deriva de la configuración de los registros de segmento. Por ejemplo, en el modelo pequeño CS=DS=SS, es decir, el código, los datos y la pila del programa están contenidos en un único segmento de 64 KB. En el modelo de memoria pequeña DS=SS, 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 (click 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 24 bytes (16 megabytes), pero sin ajustar el valor de desplazamiento. En cambio, 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 está truncada a 20 bits, por lo que es real. Los punteros de modo (pero no los punteros 8086) ahora pueden hacer referencia 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 versiones posteriores de DOS podían usarla para aumentar la memoria "convencional" disponible (es decir, dentro del primer MB ). Con la adición de HMA, el espacio total de direcciones es de aproximadamente 1,06 MB. Aunque el 80286 no trunca direcciones en modo real a 20 bits, un sistema que contiene un 80286 puede hacerlo con hardware externo al procesador, cerrando la línea de dirección 21, la línea A20 . IBM PC AT proporcionó el hardware para hacer esto (para una total compatibilidad con el software de los modelos IBM PC y PC/XT originales ), y así lo hicieron todos los clones de PC " clase AT " posteriores.

El modo protegido 286 rara vez se usaba 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 modo real. Esta limitación se puede solucionar en CPU de 32 bits que permiten el uso de punteros de memoria de tamaño superior a 64 k; sin embargo, como el campo Límite de segmento tiene solo 24 bits de largo, el tamaño máximo de segmento que se puede crear es 16 MB (aunque la paginación se puede utilizar para asignar más memoria, ningún segmento individual puede exceder 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 todavía era de 16 bits, las llamadas API no se podían realizar con instrucciones de 32 bits. Por lo tanto, todavía era necesario colocar todo el código que realiza llamadas API en segmentos de 64k.

Una vez que se invoca el modo protegido 286, no se puede 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 activando intencionalmente una falla triple 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 la traducción de una dirección lógica determinada, el procesador lee la estructura del 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 del 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 un fallo de protección general (GP) . De lo contrario, la traducción de direcciones continúa. 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 del segmento de 24 bits, especificada en el descriptor, al desplazamiento, creando una dirección física lineal.

La verificación de privilegios se realiza sólo cuando el registro de segmento está cargado, porque los descriptores de segmento se almacenan en caché en partes ocultas de los registros de segmento. [ cita necesaria ] [1]

80386 modo protegido

En Intel 80386 y posteriores, el modo protegido conserva el mecanismo de segmentación del modo protegido 80286, pero se ha agregado 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, es importante destacar que los desplazamientos de direcciones son de 32 bits (en lugar de 16 bits) y la base del segmento en cada descriptor de segmento también es de 32 bits (en lugar de 24 bits). Por lo demás, el funcionamiento general de la unidad de segmentación no se modifica. La unidad de localización puede estar habilitada o deshabilitada; si está deshabilitado, la operación es la misma que en el 80286. Si la unidad de localización está habilitada, las direcciones en un segmento ahora son 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 obtuvo sumando las dos son todas direcciones virtuales (o lógicas) cuando la unidad de localizació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 mayores en los procesadores más nuevos que admiten la extensión de dirección física .

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

Una CPU 386 se puede volver a poner en modo real borrando un bit en el registro de control CR0; sin embargo, esta es una operación privilegiada para reforzar la seguridad y la solidez. A modo de comparación, un 286 sólo podría volver al modo real forzando un reinicio del procesador, por ejemplo mediante un fallo triple 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 obligados a tener 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 SWAPGS especial , que permite intercambiar las direcciones base del modo kernel y del modo usuario .

Por ejemplo, Microsoft Windows en x86-64 usa el segmento GS para apuntar al 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 kernel 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 canary .

Prácticas

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

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

o en la sintaxis de Intel :

mov dword [ fs : eax ], 42   

Sin embargo, los registros de segmento se suelen utilizar de forma implícita.

La segmentación no se puede desactivar en procesadores x86-32 (esto también se aplica al modo de 64 bits, pero está fuera del alcance de esta discusión), por lo que muchos sistemas operativos de 32 bits simulan un modelo de memoria plana estableciendo las bases de todos los segmentos en 0. para que la segmentación sea neutral para los programas. Por ejemplo, el kernel de Linux configura sólo 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 emite el programa antes de que lleguen a la unidad de localizació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).

Linux actual también usa GS para apuntar al almacenamiento local de subprocesos .

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

En 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 elevar el nivel de privilegio del procesador (y recargar CS) son mediante 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 mediante instrucciones lret (retorno lejano) e iret (retorno interrumpido). En modo real, el código también puede modificar el registro CS realizando un salto lejano (o usando una POP CSinstrucción no documentada en el 8086 o el 8088). [4] Por supuesto, en modo real, no hay niveles de privilegios; Todos los programas tienen acceso absoluto y 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 de IA-32 disponibles gratuitamente en los sitios web de AMD o Intel .

notas y referencias

  1. ^ ab "Manual del desarrollador de software de arquitecturas Intel 64 e IA-32", Volumen 3, "Guía de programación del sistema", publicado en 2011, página "Vol. 3A 3-11", el libro está escrito: "Cada registro de segmento tiene un" visible" y una parte "oculta". (La parte oculta a veces se denomina "caché de descriptores" 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 del segmento y la información de control de acceso del descriptor de segmento señalado por 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. ^ Corporación Intel (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 extremo cuidado y tiene una utilidad limitada, porque cambia inmediatamente la dirección efectiva que se calculará a partir del puntero de instrucción para buscar 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 instrucciones PUSH y POP para los cuatro registros de segmento en 8086 y 8088.

Ver también

enlaces externos