¿Linux no usa segmentación sino solo paginación?

24

La interfaz de programación de Linux muestra el diseño de un espacio de direcciones virtual de un proceso. ¿Cada región en el diagrama es un segmento?

ingrese la descripción de la imagen aquí

De comprender el kernel de Linux ,

¿es correcto que lo siguiente significa que la unidad de segmentación en MMU mapea los segmentos y las compensaciones dentro de los segmentos en la dirección de memoria virtual, y la unidad de paginación luego mapea la dirección de memoria virtual en la dirección de memoria física?

La Unidad de Administración de Memoria (MMU) transforma una dirección lógica en una dirección lineal por medio de un circuito de hardware llamado unidad de segmentación; posteriormente, un segundo circuito de hardware llamado unidad de paginación transforma la dirección lineal en una dirección física (consulte la Figura 2-1).

ingrese la descripción de la imagen aquí

Entonces, ¿por qué dice que Linux no usa segmentación sino solo paginación?

La segmentación se ha incluido en microprocesadores de 80x86 para alentar a los programadores a dividir sus aplicaciones en entidades lógicamente relacionadas, como subrutinas o áreas de datos globales y locales. Sin embargo, Linux usa la segmentación de una manera muy limitada. De hecho, la segmentación y la paginación son algo redundantes, porque ambos se pueden usar para separar los espacios de direcciones físicas de los procesos: la segmentación puede asignar un espacio de direcciones lineal diferente a cada proceso, mientras que la paginación puede mapear el mismo espacio de direcciones lineales en diferentes espacios de direcciones físicas . Linux prefiere la paginación a la segmentación por las siguientes razones:

• La gestión de la memoria es más simple cuando todos los procesos usan los mismos valores de registro de segmento, es decir, cuando comparten el mismo conjunto de direcciones lineales.

• Uno de los objetivos de diseño de Linux es la portabilidad a una amplia gama de arquitecturas; Las arquitecturas RISC, en particular, tienen soporte limitado para la segmentación.

La versión 2.6 de Linux usa segmentación solo cuando lo requiere la arquitectura 80x86.

Tim
fuente
¿Pueden especificar las ediciones por favor? También podría ser útil especificar nombres de autor. Sé que al menos el primero es de una figura prominente. Sin embargo, los dos nombres de los títulos son un poco genéricos, al principio no estaba claro para mí que estuvieras hablando de libros :-).
sourcejedi
2
Re "La segmentación se ha incluido en microprocesadores 80x86 ...": Eso simplemente no es cierto. Es un legado de los procesadores 808x, que tenían punteros de datos de 16 bits y segmentos de memoria de 64 Kb. Los punteros de segmento le permitieron cambiar segmentos para abordar más memoria. Esa arquitectura se trasladó a 80x86 (con el tamaño del puntero aumentado a 33 bits). Hoy en día en el modelo x86_64, tiene punteros de 64 bits que pueden (en teoría, creo que solo se usan 48 bits) direccionar 16 exabytes, por lo que los segmentos no son necesarios.
jamesqf
2
@jamesqf, bueno, el modo protegido de 32 bits en el 386 admite segmentos que son bastante diferentes de los punteros escalados de 16 bytes que están en el 8086, por lo que no es simplemente un legado simple. Eso no quiere decir nada sobre su utilidad, por supuesto.
ilkkachu
@jamesqf 80186 tenía el mismo modelo de memoria que 8086, no "33 bits"
Jasen
No es digno de respuesta, por lo tanto, solo un comentario: los segmentos y las páginas solo son comparables en el contexto del intercambio (por ejemplo, el intercambio de páginas frente al intercambio de segmentos) y en ese contexto el intercambio de páginas solo elimina el intercambio de segmentos fuera del agua. Si intercambia segmentos dentro / fuera, necesitaría intercambiar todo el segmento, que podría ser de 2-4GB. Esto nunca ha sido algo real para ser utilizado en x86. Con páginas siempre puedes trabajar en unidades de 4KB. Cuando se trata de acceder a la memoria, los segmentos y las páginas se relacionan a través de tablas de páginas y la comparación sería de manzanas a naranjas.
vhu

Respuestas:

20

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 0, y el límite a 2 ^ 64.

https://en.wikipedia.org/wiki/X86_memory_segmentation#Later_developments

Ya no es posible que el sistema operativo limite qué rangos de las "direcciones lineales" están disponibles. Por lo tanto, no puede usar la segmentación para la protección de la memoria; debe depender completamente de la paginación.

No se preocupe por los detalles de las CPU x86 que solo se aplicarían cuando se ejecute en los modos heredados de 32 bits. Linux para los modos de 32 bits no se usa tanto. Incluso puede considerarse "en un estado de negligencia benigna durante varios años". Ver soporte x86 de 32 bits en Fedora [LWN.net, 2017].

(Sucede que Linux de 32 bits tampoco usa la segmentación. Pero no necesita confiar en mí en eso, simplemente puede ignorarlo :-).

sourcejedi
fuente
Eso es un poco exagerado. base / limit se fijan en 0 / -1 en modo largo para los segmentos heredados originales-8086 (CS / DS / ES / SS), pero FS y GS todavía tienen una base de segmento arbitraria. Y el descriptor de segmento cargado en CS determina si la CPU se ejecuta en modo de 32 o 64 bits. Y el espacio de usuario en x86-64 Linux usa FS para almacenamiento local de subprocesos ( mov eax, [fs:rdi + 16]). El kernel usa GS (after swapgs) para encontrar la pila de kernel del proceso actual en el syscallpunto de entrada. Pero sí, la segmentación no se usa como parte del mecanismo principal de gestión de memoria / protección de memoria del sistema operativo.
Peter Cordes
Esto es básicamente lo que la cita en la pregunta quería decir con "La versión 2.6 de Linux usa la segmentación solo cuando lo requiere la arquitectura 80x86". Pero tu segundo párrafo es básicamente incorrecto. Linux usa la segmentación básicamente idéntica en los modos de 32 y 64 bits. es decir, base = 0 / límite = 2 ^ 32 o 2 ^ 64 para los segmentos clásicos (CS / DS / ES / SS) que se usan implícitamente en las instrucciones normales. No hay nada "extra" de qué preocuparse en el código Linux de 32 bits; la funcionalidad HW está ahí pero no se usa.
Peter Cordes
@PeterCordes básicamente estás interpretando mal la respuesta :-). Así que lo he editado para intentar que mi argumento sea menos ambiguo.
sourcejedi
Buena mejora, ahora no es engañoso. Estoy totalmente de acuerdo con su punto real, que es que puede y debe ignorar totalmente la segmentación x86, porque solo se usa para cosas de administración del sistema osdev y para TLS. Si finalmente desea aprender sobre esto, es mucho más fácil de entender después de que ya comprende x86 asm con un modelo de memoria plana.
Peter Cordes
8

Como el x86 tiene segmentos, no es posible no usarlos. Pero tanto cs(segmento de código) comods direcciones base (segmento de datos) se establecen en cero, por lo que la segmentación no se usa realmente. Una excepción son los datos locales de subprocesos, uno de los segmentos de segmento normalmente no utilizados registra los puntos para enlazar datos locales. Pero eso es principalmente para evitar reservar uno de los registros de propósito general para esta tarea.

No dice que Linux no use la segmentación en el x86, ya que eso no sería posible. Ya destacó una parte, Linux usa la segmentación de una manera muy limitada . La segunda parte es que Linux usa la segmentación solo cuando lo requiere la arquitectura 80x86

Ya mencionó las razones, la paginación es más fácil y más portátil.

RalfFriedl
fuente
7

¿Cada región en el diagrama es un segmento?

No.

Mientras que el sistema de segmentación (en modo protegido de 32 bits en un x86) está diseñado para admitir segmentos separados de código, datos y pila, en la práctica todos los segmentos están configurados en la misma área de memoria. Es decir, comienzan en 0 y terminan en el final de la memoria (*) . Eso hace que las direcciones lógicas y las direcciones lineales sean iguales.

Esto se llama un modelo de memoria "plana", y es algo más simple que el modelo donde tiene segmentos distintos y luego punteros dentro de ellos. En particular, un modelo segmentado requiere punteros más largos, ya que se debe incluir el selector de segmento además del puntero de desplazamiento. (Selector de segmento de 16 bits + desplazamiento de 32 bits para un puntero total de 48 bits; frente a un puntero plano de 32 bits).

El modo largo de 64 bits realmente ni siquiera admite segmentación que no sea el modelo de memoria plana.

Si programara en modo protegido de 16 bits en el 286, tendría más necesidad de segmentos, ya que el espacio de direcciones es de 24 bits pero los punteros son solo de 16 bits.

(* Tenga en cuenta que no recuerdo cómo Linux de 32 bits maneja la separación kernel / espacio de usuario. La segmentación lo permitiría mediante la configuración de los límites de segmentos de espacio de usuario para que no incluyan el espacio del kernel. La paginación lo permite ya que proporciona un nivel de protección por página).

Entonces, ¿por qué dice que Linux no usa segmentación sino solo paginación?

El x86 todavía tiene los segmentos y no puede deshabilitarlos. Solo se usan lo menos posible. En el modo protegido de 32 bits, los segmentos deben configurarse para el modelo plano, e incluso en el modo de 64 bits todavía existen.

ilkkachu
fuente
Huh, supongo que un kernel de 32 bits podría mitigar Meltdown de manera más económica que cambiar las tablas de páginas al establecer límites de segmento en CS / DS / ES / SS que eviten que el espacio de usuario acceda a más de 2G o 3G. (El Meltdown vuln es una solución alternativa para el bit kernel / user en las entradas de la tabla de páginas, lo que permite que el espacio de usuario lea desde páginas que se asignan solo al kernel). Sin embargo, las páginas VDSO pueden estar mapeadas en la parte superior de 4G: / wrfsbasees ilegal en modo protegido / compat, solo modo largo, por lo que en un espacio de usuario de kernel de 32 bits no se pudo establecer la base FS alta.
Peter Cordes
En un kernel de 64 bits, el espacio de usuario de 32 bits podría saltar a un segmento de código de 64 bits, por lo que no podría depender de los límites del segmento para la protección Meltdown, solo tal vez en un núcleo puro de 32 bits. (Lo que tiene grandes desventajas en máquinas con mucha RAM física, por ejemplo, quedarse sin memoria para las pilas de subprocesos). De todos modos, sí, Linux protege la memoria del kernel con paginación, dejando base / limit = 0 / -1 en el espacio de usuario para lo normal segmentos (no FS / GS que se utilizan para el almacenamiento local de subprocesos).
Peter Cordes
Antes de que el bit NX fuera compatible con las tablas de páginas de hardware (PAE), algunos parches de seguridad anteriores usaban la segmentación para hacer pilas no ejecutables para el código de espacio de usuario. por ejemplo, linux.com/news/exec-shield-new-linux-security-feature (la publicación de Ingo Molnar menciona el "parche de pila" no ejecutivo "excelente de Solar Designer").
Peter Cordes
3

Linux x86 / 32 no utiliza la segmentación en el sentido de que inicializa todos los segmentos a la misma dirección lineal y límite. La arquitectura x86 requiere que el programa tenga segmentos: el código solo puede ejecutarse desde el segmento de código, la pila solo puede ubicarse en el segmento de pila, los datos solo pueden manipularse en uno de los segmentos de datos. Linux omite este mecanismo al configurar todos los segmentos de la misma manera (con excepciones que su libro no menciona de todos modos), de modo que la misma dirección lógica sea válida en cualquier segmento. De hecho, esto es equivalente a no tener segmentos en absoluto.

Dmitry Grigoryev
fuente
2

¿Cada región en el diagrama es un segmento?

Estos son 2 usos casi totalmente diferentes de la palabra "segmento"

  • Segmentación x86 / registros de segmento: los sistemas operativos x86 modernos utilizan un modelo de memoria plana donde todos los segmentos tienen la misma base = 0 y límite = máximo en el modo de 32 bits, lo mismo que el hardware impone que en el modo de 64 bits , lo que hace que la segmentación sea un vestigio . (Excepto FS o GS, utilizado para almacenamiento local de subprocesos incluso en modo de 64 bits).
  • Vinculador / cargador de programas secciones / segmentos. ( ¿Cuál es la diferencia de sección y segmento en formato de archivo ELF )

Los usos tienen un origen común: si estaba utilizando un modelo de memoria segmentada (especialmente sin memoria virtual paginada), es posible que los datos y las direcciones BSS sean relativos a la base del segmento DS, la pila relativa a la base SS y el código relativo a la Dirección base de CS.

Por lo tanto, se pueden cargar múltiples programas diferentes en diferentes direcciones lineales, o incluso moverlos después de comenzar, sin cambiar los desplazamientos de 16 o 32 bits en relación con las bases de segmento.

Pero luego debe saber a qué segmento se refiere un puntero, por lo que tiene "punteros lejanos" y así sucesivamente. (Los programas x86 de 16 bits reales a menudo no necesitaban acceder a su código como datos, por lo que podrían usar un segmento de código de 64k en algún lugar, y tal vez otro bloque de 64k con DS = SS, con la pila creciendo desde grandes compensaciones y datos en la parte inferior, o un pequeño modelo de código con todas las bases de segmentos iguales).


Cómo interactúa la segmentación x86 con la paginación

La asignación de direcciones en modo de 32/64 bits es:

  1. segmento: desplazamiento (base de segmento implicada por el registro que contiene el desplazamiento, o anulado con un prefijo de instrucción)
  2. Dirección virtual lineal de 32 o 64 bits = base + desplazamiento. (En un modelo de memoria plana como el que usa Linux, punteros / desplazamientos = direcciones lineales también. Excepto cuando se accede a TLS en relación con FS o GS).
  3. Las tablas de páginas (almacenadas en caché por TLB) se asignan linealmente a 32 (modo heredado), 36 (PAE heredado) o dirección física de 52 bits (x86-64). ( /programming/46509152/why-in-64bit-the-virtual-address-are-4-bits-short-48bit-long-compared-with-the ).

    Este paso es opcional: la paginación debe habilitarse durante el arranque configurando un bit en un registro de control. Sin paginación, las direcciones lineales son direcciones físicas.

Tenga en cuenta que la segmentación no le permite usar más de 32 o 64 bits de espacio de direcciones virtuales en un solo proceso (o subproceso) , porque el espacio de direcciones plano (lineal) en el que se asigna todo solo tiene el mismo número de bits que los propios desplazamientos. (Este no fue el caso para x86 de 16 bits, donde la segmentación fue realmente útil para usar más de 64k de memoria con registros y compensaciones en su mayoría de 16 bits).


La CPU almacena en caché los descriptores de segmento cargados desde el GDT (o LDT), incluida la base del segmento. Cuando desreferencia un puntero, según el registro en el que se encuentre, el valor predeterminado es DS o SS como segmento. El valor del registro (puntero) se trata como un desplazamiento desde la base del segmento.

Como la base del segmento es normalmente cero, las CPU hacen esto en casos especiales. O desde otro punto de vista, si hacer una base de segmento distinto de cero, las cargas tienen una latencia adicional porque el caso (normal) "especial" de pasar por la adición de la dirección de base no se aplica.


Cómo Linux configura los registros de segmento x86:

La base y el límite de CS / DS / ES / SS son todos 0 / -1 en modo de 32 y 64 bits. Esto se denomina modelo de memoria plana porque todos los punteros apuntan al mismo espacio de direcciones.

(Los arquitectos de CPU de AMD neutralizaron la segmentación al aplicar un modelo de memoria plana para el modo de 64 bits porque los sistemas operativos principales no lo usaban de todos modos, excepto por la protección no ejecutiva que se proporcionó de una manera mucho mejor al paginar con el PAE o x86- Formato de tabla de 64 páginas).

  • TLS (Thread Local Storage): FS y GS no están fijos en base = 0 en modo largo. (Eran nuevos con 386, y no fueron utilizados implícitamente por ninguna instrucción, ni siquiera las repinstrucciones de cadena que usan ES). x86-64 Linux establece la dirección base FS para cada subproceso en la dirección del bloque TLS.

    Por ejemplo, mov eax, [fs: 16]carga un valor de 32 bits de 16 bytes en el bloque TLS para este hilo.

  • el descriptor de segmento CS elige en qué modo se encuentra la CPU (modo protegido de 16/32/64 bits / modo largo). Linux usa una sola entrada GDT para todos los procesos de espacio de usuario de 64 bits y otra entrada GDT para todos los procesos de espacio de usuario de 32 bits. (Para que la CPU funcione correctamente, DS / ES también debe establecerse en entradas válidas, y también lo hace SS). También elige el nivel de privilegio (kernel (anillo 0) vs. usuario (anillo 3)), por lo que incluso al regresar al espacio de usuario de 64 bits, el núcleo todavía tiene que organizar el cambio de CS, usando ireto en sysretlugar de un normal instrucción de salto o ret.

  • En x86-64, el syscallpunto de entrada usa swapgspara voltear GS del GS del espacio de usuario al kernel, que usa para encontrar la pila de kernel para este hilo. (Un caso especializado de almacenamiento local de subprocesos). La syscallinstrucción no cambia el puntero de la pila para apuntar a la pila del núcleo; todavía apunta a la pila de usuarios cuando el núcleo alcanza el punto de entrada 1 .

  • DS / ES / SS también debe establecerse en descriptores de segmento válidos para que la CPU funcione en modo protegido / modo largo, aunque la base / límite de esos descriptores se ignore en modo largo.

Básicamente, la segmentación x86 se usa para TLS y para el material osdev x86 obligatorio que el hardware requiere que haga.


Nota al pie 1: Historia divertida: hay archivos de listas de correo de mensajes entre desarrolladores de kernel y arquitectos de AMD de un par de años antes del lanzamiento del silicio AMD64, lo que da como resultado ajustes en el diseño de syscallmodo que sea utilizable. Vea los enlaces en esta respuesta para más detalles.

Peter Cordes
fuente