La segmentación de la memoria x86 se refiere a la implementación de la segmentación de la 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 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 la memoria virtual y la protección de la memoria . En este punto, el modelo original pasó a llamarse modo real y la nueva versión se denominó modo protegido . El x86-64 La arquitectura, 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 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 un segmento adicional determinado por el programador. (ES). El Intel 80386 , introducido 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. [1]
La elección del segmento es normalmente 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 push o pop de pila o cualquier referencia de datos que se refiera a la pila usa 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 instrucciones seleccionadas si se desea. [2]
Modo real
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, denominada dirección de segmento, de los cuales los cuatro bits menos significativos restantes son todos ceros. La dirección del segmento siempre se agrega 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 agrega el desplazamiento, lo que da como resultado la dirección lineal 06EF0h + 1234h = 08124h.
0000 0110 1110 1111 0000 | segmento , | 16 bits, desplazados 4 bits a la izquierda (o multiplicados 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 única dirección lineal se puede mapear hasta 2 12 = 4096 segmentos distintos: pares de 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 direccionar 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 solo que ningún segmento puede tener una longitud superior a 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 el modo real, incluso si un segmento pudiera definirse para tener un tamaño menor a 64 KB, aún dependería completamente de los programas coordinar y mantenerse dentro de los límites de sus segmentos, ya que cualquier programa puede hacerlo. siempre acceda a cualquier memoria (ya que puede configurar arbitrariamente los selectores de segmento para cambiar las direcciones de segmento sin absolutamente ninguna supervisión). Por lo tanto, el modo real también puede imaginarse con 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. Por lo general, se omiten).
El espacio de direcciones efectivo de 20 bits del modo real limita la memoria direccionable a 20 bytes o 1.048.576 bytes (1 MB ). Esto derivó directamente del diseño de hardware del Intel 8086 (y, posteriormente, del 8088 estrechamente relacionado), 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 comienzo 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 se puede producir la superposición entre los segmentos y por qué se puede acceder a cualquier ubicación en el espacio de direcciones de memoria lineal con muchos segmentos: pares de 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. Dichas 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 por lo tanto, puede acceder, con un desplazamiento de hasta 65,536 bytes, hasta 65,520 (65536 −16) bytes después del final del espacio de direcciones 8088 de 20 bits. En el 8088, estos accesos de dirección se envolvieron al 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 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 hagan uso de 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 para las más pequeñas ( que le vendría bien con menos memoria). La raíz del problema es que no se dispone de instrucciones aritméticas de direcciones adecuadas para el direccionamiento plano de todo el rango de memoria. [ cita requerida ] El direccionamiento plano es posible aplicando múltiples instrucciones, 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 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 80.286 's modo protegido se extiende espacio de direcciones del procesador a 2 24 bytes (16 megabytes), pero no mediante el ajuste del 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 se trunca a 20 bits, por lo que es real. Los punteros de modo (pero no los punteros 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 High Memory Area (HMA), y las versiones posteriores de DOS podrí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 las direcciones en modo real a 20 bits, un sistema que contenga 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 hacia atrás con el software para los modelos originales de IBM PC y PC / XT ), por lo que todos los clones de PC "clase AT " posteriores también lo hicieron.
El modo protegido 286 rara vez se usó, ya que habría excluido al gran número de usuarios con máquinas 8086/88. Además, todavía era necesario dividir la memoria en 64k segmentos 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 un tamaño superior a 64k; sin embargo, como el campo Límite de segmento tiene una longitud de solo 24 bits, el tamaño máximo de segmento que se puede crear es de 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 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, aún era necesario colocar todo el código que realiza llamadas a la API en segmentos de 64k.
Una vez que se invoca el modo protegido 286, no se puede salir excepto realizando un restablecimiento de hardware. Las máquinas que siguen el creciente estándar IBM PC / AT podrían fingir un reinicio de la CPU a través del controlador de teclado estandarizado, pero esto fue significativamente lento. Windows 3.x solucionó 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 regresara 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 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. 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 privilegios 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 una falla de protección general (GP) . De lo contrario, la traducción de direcciones continúa. A continuación, el procesador toma el desplazamiento de 32 bits o 16 bits y lo compara con el límite de segmento especificado en el descriptor de segmento. Si es mayor, se genera un error 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 el registro de segmento está cargado, porque los descriptores de segmento se almacenan en caché en partes ocultas de los registros de segmento. [ cita requerida ] [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 una 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 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 búsqueda puede estar habilitada o deshabilitada; si está deshabilitado, el funcionamiento es el mismo 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 deriva sumando las dos son todas direcciones virtuales (o lógicas) cuando la unidad de búsqueda 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 uso general, FS y GS, en el conjunto original de registros de cuatro 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 robustez. A modo de comparación, un 286 solo podría volver al modo real forzando un reinicio del procesador, por ejemplo, por una falla triple o usando hardware externo.
Desarrollos posteriores
La arquitectura x86-64 no utiliza la segmentación en modo largo (modo de 64 bits). Cuatro de los registros de segmento, CS, SS, DS y ES, se fuerzan a 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 descriptor global 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 de kernel y del modo de usuario .
Por ejemplo, Microsoft Windows en x86-64 usa el segmento GS para señalar el Thread Environment Block , una pequeña estructura de datos para cada subproceso , que contiene información sobre el manejo de excepciones, las variables locales del subproceso y otro estado por subproceso. De manera similar, el kernel de Linux usa el segmento GS para almacenar datos por CPU.
En x64, la CPU se enciende en modo real y es indistinguible de un Pentium 4 de 32 bits. Las instrucciones de 64 bits no se pueden usar a menos que se establezca el modo largo. Cuando el modo largo está en funcionamiento, las instrucciones de 16 bits y el modo x86 virtual se desactivan y el modo protegido desaparece.
GS / FS también se utilizan en gcc 's de almacenamiento local de subprocesos y basado en canario pila protector.
Practicas
Las direcciones lógicas se pueden especificar explícitamente en lenguaje ensamblador x86 , por ejemplo (sintaxis de 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 se suelen utilizar implícitamente.
- Todas las instrucciones de la CPU se obtienen implícitamente del segmento de código especificado por el selector de segmento contenido en el registro CS.
- La mayoría de las referencias de memoria provienen del segmento de datos especificado por el selector de segmento contenido en el registro DS. Estos también pueden provenir del segmento adicional especificado por el selector de segmento contenido en el registro ES, si un prefijo de anulación de segmento precede a la instrucción que hace la referencia de memoria. La mayoría de las instrucciones que utilizan DS de forma predeterminada, aunque no todas, aceptarán un prefijo de anulación de ES.
- Las referencias a la pila del procesador , ya sea implícitamente (por ejemplo, instrucciones push y pop ) o explícitamente ( accesos a la memoria utilizando los registros (E) SP o (E) BP ) utilizan el segmento de pila especificado por el selector de segmento contenido en el registro SS.
- Instrucciones de cadena (por ejemplo OCP , MOVs ), además de segmento de datos, también utilizan el segmento adicional especificado por el selector segmento celebrada en el ES se registren.
La segmentación no se puede desactivar en 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 estableciendo todas las bases de 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:
Nombre | Descripción | Base | Límite | DPL |
---|---|---|---|---|
__KERNEL_CS | Segmento de código de kernel | 0 | 4 GiB | 0 |
__KERNEL_DS | Segmento de datos del kernel | 0 | 4 GiB | 0 |
__USER_CS | Segmento de código de usuario | 0 | 4 GiB | 3 |
__USER_DS | Segmento de datos de usuario | 0 | 4 GiB | 3 |
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 los problemas del 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).
El Linux actual también usa GS para apuntar al almacenamiento local de subprocesos .
Los segmentos se pueden definir como códigos, datos o segmentos del sistema. Hay bits de permiso adicionales 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 privilegios del procesador (y recargar CS) son a través de las instrucciones lcall (llamada remota) 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) e 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 POP CS
instrucción no documentada en el 8086 o 8088) [4] ). Por supuesto, en modo real, no hay niveles de privilegios; 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
- ^ a b "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 Parte "visible" y una parte "oculta". (La parte oculta a veces se denomina "caché de descriptores" o "registro de sombra"). 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, 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 " .
- ^ Corporación Intel (2004). IA-32 Manual del desarrollador de software de arquitectura Intel Volumen 1: Arquitectura básica (PDF) .
- ^ http://blogs.msdn.com/b/larryosterman/archive/2005/02/08/369243.aspx
- ^
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 dePOP CS
es probablemente un accidente, ya que sigue un patrón de códigos de operación de instrucción PUSH y POP para los registros de cuatro segmentos en el 8086 y 8088.
Ver también
- Modelo de memoria Intel
- EL sistema de multiprogramación
enlaces externos
- Inicio del Manual para desarrolladores de software de arquitectura Intel IA-32
- El segmento: esquema de direccionamiento compensado