Articulo de referencia

Segmentación de memoria x86

La segmentación de memoria x86 es un término que describe el tipo de segmentación de memoria característico de la arquitectura del conjunto de instrucciones de la computadora In...

La segmentación de memoria x86 es un término que describe el tipo de segmentación de memoria característico de la arquitectura del conjunto de instrucciones de la computadora Intel x86 . La arquitectura x86 ha admitido la segmentación de memoria desde el Intel 8086 original (1978), pero la segmentación de memoria x86 es simplemente un retrónimo descriptivo . La introducción de mecanismos de segmentación de memoria en esta arquitectura refleja la herencia de los procesadores 80xx anteriores, que inicialmente [ 1 ] solo podían direccionar 16 KB, o posteriormente [ 2 ] 64 KB de memoria (16 384 bytes o 65 536 bytes, respectivamente), y cuyas instrucciones y registros estaban optimizados para estos últimos. Por lo tanto, manejar direcciones más grandes y más memoria era comparativamente más lento, ya que esa capacidad se injertó en cierta medida en el Intel 8086. La segmentación de memoria podía mantener la compatibilidad de los programas, permitir su reubicación en la memoria y, al confinar partes significativas de la operación de un programa a segmentos de 64 KB, el programa aún podía ejecutarse más rápido.  

En 1982, el Intel 80286 añadió soporte para memoria virtual y protección de memoria ; el modo original pasó a llamarse modo real , y la nueva versión se denominó modo protegido . [ 3 ] La arquitectura x86-64 , introducida en 2003, ha abandonado en gran medida el soporte para la segmentación en modo de 64 bits.

Tanto en el modo real como en el modo protegido, el sistema utiliza registros de segmento de 16 bits para obtener la dirección de memoria real.En modo real, los registros CS, DS, SS y ES apuntan al segmento de código de programa en uso (CS), al segmento de datos actual (DS), al segmento de pila actual (SS) y a un segmento adicional determinado por el programador del sistema (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. [ 4 ]

La elección del segmento normalmente la realiza el procesador según la función que se esté ejecutando. Las instrucciones siempre se obtienen del segmento de código. Cualquier referencia de datos a la pila, incluyendo cualquier operación de inserción o extracción, utiliza el segmento de pila; las referencias de datos indirectas a través del registro BP suelen referirse a la pila, por lo que utilizan el segmento de pila por defecto. El segmento adicional es el destino obligatorio para las operaciones de cadena (por ejemplo, MOVS o CMPS); solo para este propósito, el registro de segmento seleccionado automáticamente no se puede sobrescribir. Todas las demás referencias a datos utilizan el segmento de datos por defecto. El segmento de datos es la fuente predeterminada para las operaciones de cadena, pero se puede sobrescribir. El formato de instrucción permite un byte de prefijo de segmento opcional que se puede utilizar para sobrescribir el segmento predeterminado para determinadas instrucciones si se desea. [ 5 ]

Modo real

Tres segmentos en la memoria en modo real (haga clic en la imagen para ampliarla). Existe una superposición entre el segmento 2 y el segmento 3; los bytes en el área turquesa pueden ser utilizados por ambos selectores de segmento. Sin embargo, si el código del programa que maneja el segmento 2 nunca utiliza desplazamientos lo suficientemente grandes como para alcanzar 0x77D0, entonces puede considerarse como un segmento más corto, sin superposición y de un máximo de 30 672 bytes.

En el modo real o modo V86 , el tamaño fundamental de un segmento es de 65.536 bytes , y los bytes individuales se direccionan 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, denominada dirección de segmento, cuyos 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:9234h (donde 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 le suma el desplazamiento, obteniendo la dirección lineal 06EF0h  +  9234h  =  10124h.

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

Debido a la forma en que se suman la dirección de segmento y el desplazamiento, una única dirección lineal puede asignarse a hasta 2¹² = 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 ser ventajoso, por ejemplo, al direccionar múltiples estructuras de datos anidadas.

Aunque técnicamente los segmentos en modo real siempre tienen una longitud de 64 KB , en la práctica esto significa que ningún segmento puede ser más largo que 64 KB, en lugar de que cada segmento utilizado en un programa deba considerarse de 64 KB. Es posible trabajar con segmentos más pequeños: los tamaños utilizables van desde 16 hasta 65 536 bytes, en incrementos de 16 bytes. Dado que en modo real no existe protección ni limitación de privilegios, el programa es quien debe coordinar y mantenerse dentro de los límites de los segmentos. Esto se aplica tanto cuando un segmento se trata programáticamente como menor o igual a los 64 KB, como cuando cualquier programa puede acceder a cualquier memoria simplemente cambiando de segmento, ya que puede configurar arbitrariamente selectores de segmento para cambiar las direcciones de los segmentos sin supervisión alguna. Por lo tanto, si bien se puede pensar que el modo real permite diferentes longitudes de segmento y que los segmentos se superpongan o no según se desee, la CPU no impone ninguna restricción al respecto.    

El espacio de direcciones efectivo de 20 bits de las CPU de la generación PC/XT limita la memoria direccionable a 2²⁰  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 presentaban en encapsulados DIP de 40 pines; incluso con solo 20 líneas de dirección, los buses de dirección y datos se multiplexaban para que todas las líneas de dirección y datos cupieran dentro del número limitado de pines).  

Cada segmento comienza a una distancia múltiplo de 16  bytes, denominada párrafo , desde el inicio del espacio de direcciones lineal (plano). Es decir, a  intervalos de 16 bytes. Dado que todos los segmentos tienen técnicamente 64  KB de longitud, esto explica cómo puede producirse la superposición entre segmentos y por qué se puede acceder a cualquier ubicación en el espacio de direcciones de memoria lineal con múltiples pares segmento:desplazamiento. La ubicación real del inicio de un segmento en el espacio de direcciones lineal se puede calcular multiplicando el tamaño del segmento por 16. Estas traducciones de direcciones las realiza la unidad de segmentación de la CPU.

Peculiaridad al final del espacio de direcciones

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 de la CPU 8086 o 8088. Otros 4,094 segmentos de 64K siguientes también cruzan ese umbral de 1MB, pero por cada vez menos. En las CPU 8086 y 8088, estos accesos a direcciones se envolvían hasta el principio del espacio de direcciones de tal manera que 65535:16 accedería a la dirección 0, y por ejemplo 65533:1000 accedería a la dirección  952 del espacio de direcciones lineal. El hecho de que algunos programas escritos para los procesadores 8088 y 8086 dependieran de esta peculiar función de envoltura como característica provocó problemas de compatibilidad con la puerta A20 en generaciones posteriores de CPU, como el Intel 286 y superiores, donde el espacio de direcciones lineal se amplió 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 una misma estructura de datos (con el fin de acceder a más memoria de la disponible en cualquier segmento de 64 KB) es bastante complejo, pero se consideraba un mal necesario para todas las herramientas, excepto las más pequeñas (que podían funcionar con menos memoria). La raíz del problema reside en la falta de instrucciones aritméticas de direcciones adecuadas para el direccionamiento plano de todo el rango de memoria. El direccionamiento plano es posible mediante la aplicación de múltiples instrucciones, lo que, sin embargo, ralentiza los programas.

El concepto de modelo de memoria se deriva de la configuración de los registros de segmento. Por ejemplo, en el modelo pequeño CS=DS=ES=SS, el código, los datos y la pila del programa se encuentran dentro de 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 de modo protegido (haga clic en la imagen para ampliarla), con la tabla de descriptores locales.

Modo protegido 80286

El modo protegido del 80286 extiende el espacio de direcciones del procesador a 2²⁴ bytes (16 megabytes), pero no ajustando el valor de desplazamiento utilizado para calcular una dirección de segmento a partir del valor en un registro de segmento. En cambio, cada registro de segmento de 16 bits ahora contiene un índice en una tabla de descriptores de segmento que contienen direcciones base de 24 bits a las que se suman desplazamientos. Para admitir software antiguo, el procesador arranca 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 del modo real (pero no los punteros del 8086) ahora pueden referirse a direcciones desde 100000¹⁶ hasta 10FFEF¹⁶ . Esta región de memoria de casi 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 del primer MB ), moviendo partes de DOS de la memoria convencional a la HMA. Con la adición de la HMA, el espacio de direcciones total es de aproximadamente 1,06 MB. Aunque el 80286 no trunca las direcciones de modo real a 20 bits, un sistema que contiene un 80286 puede hacerlo con hardware externo al procesador, desactivando la línea de dirección 21, la línea A20 . El IBM PC AT proporcionó el hardware para hacer esto (para compatibilidad con versiones anteriores del software de los modelos originales IBM PC y PC/XT ), y todos los clones de PC de " clase AT " posteriores también lo hicieron.     

El modo protegido 286 se usaba raramente, ya que habría excluido a la gran cantidad de usuarios con máquinas 8086/88. Además, seguía requiriendo dividir la memoria en segmentos de 64k, como se hacía en el modo real. Esta limitación se puede sortear en las CPU de 32 bits, que permiten el uso de punteros de memoria de más de 64k, pero como el campo Límite de Segmento solo tiene 24 bits, 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 las aplicaciones de Windows 3.x para producir un espacio de memoria plano, aunque como el propio sistema operativo seguía siendo de 16 bits, no se podían realizar llamadas a la API con instrucciones de 32 bits. Por lo tanto, seguía siendo necesario colocar todo el código que realizaba llamadas a la API en segmentos de 64k.

Una vez activado el modo protegido 286, normalmente no se podía salir de él salvo mediante un reinicio del hardware. Las máquinas que seguían el estándar IBM PC/AT podían simular un reinicio de la CPU mediante el controlador de teclado estandarizado, pero esto resultaba muy lento. Windows 3.x solucionó ambos problemas provocando intencionadamente un triple fallo en los mecanismos de gestión de interrupciones de la CPU, lo que hacía que el hardware compatible con IBM AT reiniciara la CPU casi instantáneamente, provocando así que volviera al modo real. [ 6 ]

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. Dicho 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 direcciones 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. A continuación, realiza la comprobación de privilegios:

max(CPL, RPL) ≤ DPL

donde CPL es el nivel de privilegio actual (ubicado 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 (ubicado en el descriptor). Todos los niveles de privilegio son números enteros en el rango 0-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. A continuación, el procesador toma el desplazamiento de 16 bits y lo compara con el límite de segmento especificado en el descriptor de segmento. Si es mayor, se genera un fallo GP. En caso contrario, el procesador suma la base de segmento de 24 bits, especificada en el descriptor, al desplazamiento, creando así una dirección física lineal.

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

Modo protegido 80386

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 del 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. La unidad de paginación puede estar habilitada o deshabilitada; si está deshabilitada, su 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 ocurría 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 obtiene al sumar ambas son 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 las traduce a direcciones físicas. Las direcciones físicas son de 32 bits en el 386 , pero pueden ser mayores en procesadores más recientes que admiten la extensión de dirección física (PDL) .

Como se mencionó anteriormente, 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 en el registro de control CR0; sin embargo, esta es una operación privilegiada para garantizar la seguridad y la robustez. En comparación, una 286 solo podía volver al modo real forzando un reinicio del procesador, por ejemplo, mediante una triple falla 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 es 2⁶⁴ . 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 también proporciona 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 apuntar al Thread Environment Block , una pequeña estructura de datos para cada hilo , que contiene información sobre el manejo de excepciones, variables locales del hilo y otros estados por hilo. 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 y en el protector de pila basado en canary de gcc .

Prácticas

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

movl$42,%fs:(%eax); Equivalent to M[fs:eax]<-42) inRTL

o en sintaxis Intel :

mov dword [ fs : eax ], 42

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

  • Todas las instrucciones de la CPU se obtienen implícitamente del segmento de código especificado por el selector de segmento almacenado en el  registro CS.
  • La mayoría de las referencias a memoria provienen del segmento de datos especificado por el selector de segmento almacenado en el  registro DS. También pueden provenir de cualquier segmento especificado por el selector de segmento almacenado en el registro de segmento correspondiente, si un prefijo de anulación de segmento precede a la instrucción que realiza la referencia a memoria. La mayoría de las instrucciones que utilizan DS por defecto, aunque no todas, aceptan cualquier otro prefijo de anulación.
  • Las referencias a la pila del procesador , ya sean implícitas (por ejemplo, las instrucciones push y pop ) o explícitas ( accesos a memoria mediante los registros (E)SP o (E)BP ), utilizan el segmento de pila especificado por el selector de segmento almacenado en el  registro SS. Para las referencias explícitas, el segmento puede ser sobrescrito.
  • Las instrucciones de cadena (por ejemplo, stos , movs , cmps ) utilizan el segmento adicional para la cadena de destino especificada por el selector de segmento almacenado 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 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 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 a las direcciones que el programa asigna 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 disponen de una unidad de paginación).

La versión actual de Linux también utiliza GS para apuntar al almacenamiento local de subprocesos .

Los segmentos pueden definirse como segmentos de código, datos o sistema. Existen bits de permisos adicionales para configurar los segmentos como 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 las instrucciones lret (retorno lejano) e iret (retorno de interrupción). En modo real, el código también puede modificar el  registro CS mediante un salto lejano (o utilizando una POP CSinstrucción no documentada en el 8086 o 8088). [ 7 ] Por supuesto, en 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. en el Intel 8008
  2. del Intel 8080
  3. "Familia de microprocesadores Intel 80286" . www.cpu-world.com . Consultado el 7 de noviembre de 2025 .
  4. 1 2 "Manual del desarrollador de software para 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 dice: " Cada registro de segmento tiene una parte "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 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. "
  5. Intel Corporation (2004). IA-32 Intel Architecture Software Developer's Manual Volume 1: Basic Architecture (PDF) .
  6. "Blogs de desarrolladores" .
  7. El operador ↑POP CS debe usarse con extrema precaución 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. Su existenciaPOP CSes probablemente accidental, ya que sigue un patrón de códigos de operación de instrucciones PUSH y POP para los cuatro registros de segmento en los microprocesadores 8086 y 8088.

Véase también

  • Página principal del manual del desarrollador de software de la arquitectura Intel IA-32
  • Esquema de direccionamiento Segmento:Desplazamiento