Segmentación de memoria X86
La segmentación de memoria x86 hace referencia a la implementación de la segmentación de memoria en la arquitectura del conjunto de instrucciones de la computadora Intel x86. La segmentación se introdujo en Intel 8086 en 1978 como una forma de permitir que los programas aborden más de 64 KB (65 536 bytes) de memoria. 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, se cambió el nombre del modo original a modo real, y la nueva versión se denominó modo protegido. La arquitectura x86-64, introducida en 2003, ha dejado de admitir en gran medida 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, el segmento de datos actual (DS), el segmento de pila actual (SS) y uno extra segmento determinado por el programador (ES). El Intel 80386, presentado en 1985, agrega 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.
La elección del segmento normalmente está predeterminada por el procesador de acuerdo con la función que se está ejecutando. Las instrucciones siempre se obtienen del segmento de código. Cualquier inserción o extracción de pila o cualquier referencia de datos que se refiera 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 usar para anular el segmento predeterminado para las instrucciones seleccionadas si se desea.
Modo real
En modo real o modo V86, el tamaño de un segmento puede oscilar entre 1 byte y 65 536 bytes (con 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, denominada dirección de segmento, de los cuales los cuatro bits restantes menos significativos son todos ceros. La dirección del segmento siempre se agrega a un desplazamiento de 16 bits en la instrucción para generar 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 agrega el desplazamiento, dando como resultado la dirección lineal 06EF0h + 1234h = 08124h.
0000 0110 1110 1111 0000 | Segmento | 16 bits, desplazado 4 bits a la izquierda (o multiplicado por 0x10) |
---|---|---|
+ 0001 0010 0011 0100 | Offset | 16 bits |
| ||
0000 1000 0001 0010 0100 | Dirección | 20 bits |
Debido a la forma en que se agregan la dirección del segmento y el desplazamiento, una sola dirección lineal se puede asignar a hasta 212 = 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 se puede utilizar con ventaja, por ejemplo, al abordar varias estructuras de datos anidadas. Si bien los segmentos en 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 cada segmento debe tener una longitud de 64 KB. Debido a que no hay protección o limitación de privilegios en modo real, incluso si un segmento pudiera definirse para que sea más pequeño que 64 KB, aún dependería completamente de los programas coordinarse y mantenerse dentro de los límites de sus segmentos, ya que cualquier programa puede acceda siempre a cualquier memoria (ya que puede configurar arbitrariamente los selectores de segmento para cambiar las direcciones de segmento sin supervisión alguna). Por lo tanto, 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 impuesto 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. Por lo general, 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 se derivó directamente del diseño de hardware del Intel 8086 (y, posteriormente, el estrechamente relacionado 8088), que tenía exactamente 20 pines de dirección. (Ambos se empaquetaron 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 adaptarse a todas las líneas de dirección y datos dentro del número limitado de pines).
Cada segmento comienza en un múltiplo de 16 bytes, llamado párrafo, desde el principio del espacio de direcciones lineal (plano). Es decir, a 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 la 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 lineales. El desplazamiento de la dirección se puede agregar 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 son realizadas por 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 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 función por parte de los programadores provocó 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 varios segmentos de memoria (para acceder a más memoria que la disponible en cualquier segmento de 64 000) es bastante complejo, pero se consideraba un mal necesario para todos menos el herramientas más pequeñas (que podrían funcionar con menos memoria). La raíz del problema es que no hay disponibles instrucciones aritméticas de direcciones adecuadas para el direccionamiento plano de todo el rango de memoria. El direccionamiento plano es posible aplicando varias 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 diminuto CS=DS=SS, es decir, el código, los datos y la pila del programa están contenidos en un solo 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
80286 modo protegido
El modo protegido del 80286 amplía el espacio de direcciones del procesador a 224 bytes (16 megabytes), pero no ajustando 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 contiene 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, existe una pequeña diferencia: la dirección física resultante ya no se trunca en 20 bits, por lo que los punteros en modo real (pero no los punteros 8086) ahora pueden hacer referencia a direcciones entre 10000016 y 10FFEF16. Esta región de memoria de aproximadamente 64 kilobytes se conocía como High Memory Area (HMA), y las versiones posteriores de DOS podían usarla para aumentar la capacidad "convencional" memoria (es decir, dentro de los primeros MB). Con la adición de 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 contiene un 80286 puede hacerlo con hardware externo al procesador, bloqueando la línea de dirección 21, la línea A20. El IBM PC AT proporcionó el hardware para hacer esto (para una total compatibilidad con versiones anteriores del software para los modelos originales de IBM PC y PC/XT), por lo que todos los modelos posteriores de "AT-class" Los clones de PC también lo hicieron.
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, aún requería dividir la memoria en segmentos de 64k como se hizo en modo real. Esta limitación se puede solucionar en CPU de 32 bits que permiten el uso de punteros de memoria de más de 64k de tamaño; sin embargo, dado que el campo Límite de segmento tiene solo 24 bits, el tamaño máximo de segmento que se puede crear es de 16 MB (aunque la paginación puede usarse para asignar más memoria, ningún segmento individual puede exceder los 16 MB). Este método se usaba comúnmente en las aplicaciones de Windows 3.x para producir un espacio de memoria plano, aunque como el sistema operativo en sí todavía era de 16 bits, las llamadas a la 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 excepto realizando un reinicio de 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 resolvió estos dos 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.
Flujo de trabajo de la unidad de segmentación detallada
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 una dirección lógica dada, 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:
- max(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 desde el 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, 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 más grande, se genera una falla de protección general. 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 solo cuando se carga el registro de segmento, porque los descriptores de segmento se almacenan en caché en partes ocultas de los registros de segmento.
80386 modo protegido
En Intel 80386 y versiones 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, lo que es más importante, las compensaciones de dirección 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 cambia. La unidad de buscapersonas puede habilitarse o deshabilitarse; si está deshabilitada, la operación es la misma que en el 80286. Si la unidad de paginació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 de inicio del segmento, el desplazamiento, y la dirección final de 32 bits que la unidad de segmentación obtuvo al sumar 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 los 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 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 solo podría volver al modo real forzando un reinicio del procesador, p. por una falla triple o usando hardware externo.
Desarrollos posteriores
La arquitectura x86-64 no usa segmentación en modo largo (modo de 64 bits). Cuatro de los registros de segmento, CS, SS, DS y ES, se fuerzan a la dirección base 0 y el límite a 264. 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 propósitos 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 usa el segmento GS para apuntar al Bloque de entorno de subproceso, una pequeña estructura de datos para cada subproceso, que contiene información sobre el manejo de excepciones, las variables locales del subproceso y otros estados por subproceso.. De manera similar, el kernel de Linux usa 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, p. (sintaxis de AT&T):
movl $42, %fs:(%eax); Equivalente a M[fs:eax]
o en la sintaxis de Intel:
mov dword [f:eax] 42
Sin embargo, los registros de segmento generalmente se usan implícitamente.
- Todas las instrucciones de la CPU se recogen implícitamente de la segmento de código especificado por el selector de segmento que se mantiene en el registro CS.
- La mayoría de las referencias de memoria provienen de segmento de datos especificado por el selector de segmento que se mantiene en el registro DS. Estos también pueden provenir del segmento extra especificado por el selector de segmento que se mantiene en el registro de ES, si un prefijo de override de segmento precede a la instrucción que hace referencia a la memoria. La mayoría, pero no todas, las instrucciones que usan DS por defecto aceptarán un prefijo de anulación de ES.
- Referencias de la pila de procesadores, ya sea implícitamente (por ejemplo. empujar y pop instrucciones) o explícitamente (accesos de memoria utilizando los registros (E)SP o (E)BP) segmento de pila especificado por el selector de segmento mantenido en el registro SS.
- Instrucciones de montaje (por ejemplo. stos, movs), junto con el segmento de datos, también utilizar el segmento extra especificado por el selector de segmento que se mantiene en el registro ES.
La segmentación no se puede desactivar en los procesadores x86-32 (esto también es cierto para el modo de 64 bits, pero más allá del alcance de la discusión), por lo que muchos sistemas operativos de 32 bits simulan un modelo de memoria plana configurando todos los segmentos. 39; bases a 0 para que la segmentación sea neutral para los programas. Por ejemplo, el kernel de Linux configura solo 4 segmentos de propósito general:
Nombre | Descripción | Base | Límite | DPL |
---|---|---|---|---|
__KERNEL_CS | Serie de código de kernel | 0 | 4 GiB | 0 |
__KERNEL_DS | Serie de datos de kernel | 0 | 4 GiB | 0 |
__USER_CS | segmento de código de usuario | 0 | 4 GiB | 3 |
__USER_DS | Serie de datos de usuario | 0 | 4 GiB | 3 |
Dado que la base se establece en 0 en todos los casos y el límite es de 4 GiB, la unidad de segmentación no afecta las direcciones que emite el programa 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).
Linux actual también usa GS para apuntar al almacenamiento local de subprocesos.
Los segmentos pueden definirse como segmentos de código, datos o sistema. Los bits de permiso adicionales están 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 a través de las instrucciones lcall (llamada lejana) e int (interrupción). Del mismo modo, las únicas formas de reducir el nivel de privilegios (y recargar CS) son a través de las instrucciones lret (retorno lejano) y iret (retorno de interrupción). En modo real, el código también puede modificar el registro CS haciendo un salto lejano (o usando una instrucción POP CS
no documentada en el 8086 o el 8088). Por supuesto, en modo real, no hay niveles de privilegio; todos los programas tienen acceso absoluto sin control a toda la memoria y 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
- ^ a b "Intel 64 and IA-32 Architectures Software Developer's Manual", Volumen 3, "System Programming Guide", publicado en 2011, Página "Vol. 3A 3-11", el libro está escrito: "Cada registro de segmentos tiene una parte “visible” y una parte “oculta”. (La parte oculta es a veces conocida como un “caché descriptor” o un “registro de sombras”.) Cuando un selector de segmento se carga 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, límite de segmento y información de control de acceso del descriptor de segmento apuntado por el selector de segmento. La información grabada en el registro de segmentos (visible y oculto) permite al procesador traducir direcciones sin tomar ciclos de autobús adicionales para leer la dirección base y límite del descriptor de segmento."
- ^ Intel Corporation (2004). IA-32 Intel Architecture Software Developer's Manual Volumen 1: Basic Architecture (PDF).
- ^ "DevBlogs".
- ^
POP CS
debe ser utilizado con cuidado extremo y tiene poca utilidad, ya que inmediatamente cambia la dirección efectiva que se computará desde el puntero de instrucción para buscar la siguiente instrucción. Generalmente, un salto lejano es mucho más útil. La existencia dePOP CS
es probablemente un accidente, ya que sigue un patrón de los opcodes de instrucción PUSH y POP para los cuatro registros de segmentos en los 8086 y 8088.
Contenido relacionado
Línea picadilly
Corporación Lockheed
Obús M198