Poseemos la mejor información que hemos encontrado en todo internet. Nuestro deseo es que te sea de mucha ayuda y si quieres compartir alguna mejora hazlo libremente.
Solución:
Sí, Linux usa paginación, por lo que todas las direcciones son siempre virtuales. (Para acceder a la memoria en una dirección física conocida, Linux mantiene toda la memoria física 1: 1 asignada a un rango de espacio de direcciones virtuales del kernel, por lo que simplemente puede indexar en ese “array”utilizando la dirección física como compensación. Complicaciones de módulo para núcleos de 32 bits en sistemas con más RAM física que espacio de direcciones del núcleo).
Este espacio de direcciones lineal constituido por páginas, se divide en cuatro segmentos
No, Linux usa un modelo de memoria plana. La base y el límite para los 4 descriptores de segmento son 0 y -1 (ilimitado). es decir todos se superponen completamente, cubriendo todo el espacio de direcciones lineales virtuales de 32 bits.
Entonces la parte roja consta de dos segmentos
__KERNEL_CS
y__KERNEL_DS
No, aquí es donde te equivocaste. Los registros de segmento x86 son no utilizado para la segmentación; son un equipaje heredado de x86 que solo se usa para el modo de CPU y la selección de nivel de privilegio en x86-64. En lugar de agregar nuevos mecanismos para eso y eliminar segmentos por completo para el modo largo, AMD simplemente neutralizó la segmentación en el modo largo (la base se fijó en 0 como todos los que usaban en el modo de 32 bits de todos modos) y siguió usando segmentos solo para propósitos de configuración de máquina que no son particularmente interesante a menos que esté escribiendo código que cambie al modo de 32 bits o lo que sea.
(Excepto que puede establecer una base distinta de cero para FS y / o GS, y Linux lo hace para el almacenamiento local de subprocesos. Pero esto no tiene nada que ver con cómo copy_from_user()
está implementado, ni nada. Solo tiene que verificar ese valor de puntero, no con referencia a ningún segmento o al CPL / RPL de un descriptor de segmento).
En el modo heredado de 32 bits, es posible escribir un kernel que use un modelo de memoria segmentada, pero ninguno de los sistemas operativos convencionales lo hizo. Sin embargo, algunas personas desearían que eso se hubiera convertido en una cosa, por ejemplo, ven esta respuesta lamentando x86-64 que hace imposible un sistema operativo estilo Multics. Pero esto es no cómo funciona Linux.
Linux es un https://wiki.osdev.org/Higher_Half_Kernel, donde los punteros del kernel tienen un rango de valores (la parte roja) y las direcciones del espacio de usuario están en la parte verde. El kernel puede desreferenciar direcciones de espacio de usuario simple si se mapean las tablas de página de espacio de usuario correctas, no necesita traducirlas ni hacer nada con segmentos; esto es lo que significa tener un modelo de memoria plana. (El kernel puede usar entradas de tabla de páginas de “usuario”, pero no viceversa). Para x86-64 específicamente, consulte https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt para ver el mapa de memoria real.
La única razón por la que esas 4 entradas GDT deben estar todas separadas es por razones de nivel de privilegio, y porque los descriptores de segmentos de código y de datos tienen formatos diferentes. (Una entrada GDT contiene más que solo la base / límite; esas son las partes que deben ser diferentes. Ver https://wiki.osdev.org/Global_Descriptor_Table)
Y especialmente https://wiki.osdev.org/Segmentation#Notes_Regarding_C, que describe cómo y por qué un sistema operativo “normal” suele utilizar GDT para crear un modelo de memoria plana, con un par de códigos y descriptores de datos para cada nivel de privilegio. .
Para un kernel de Linux de 32 bits, solo gs
obtiene una base distinta de cero para el almacenamiento local de subprocesos (por lo que los modos de direccionamiento como [gs: 0x10]
accederá a una dirección lineal que depende del hilo que la ejecute). O en un kernel de 64 bits (y espacio de usuario de 64 bits), Linux usa fs
. (Debido a que x86-64 hizo que GS fuera especial con el swapgs
instrucción, destinada a su uso con syscall
para que el kernel encuentre la pila del kernel).
Pero de todos modos, la base distinta de cero para FS o GS no son de una entrada de GDT, están configuradas con el wrgsbase
instrucción. (O en CPU que no admiten eso, con una escritura en un MSR).
pero ¿qué son esas banderas?
0xc09b
,0xa09b
y así ? Tiendo a creer que son los selectores de segmentos
No, los selectores de segmento son índices del GDT. El kernel está definiendo el GDT como C array, usando la sintaxis de inicializador designado como [GDT_ENTRY_KERNEL32_CS] = initializer_for_that_selector
.
(En realidad, los 2 bits bajos de un selector, es decir, el valor del registro de segmento, son el nivel de privilegio actual. GDT_ENTRY_DEFAULT_USER_CS
debe ser `__USER_CS >> 2.)
mov ds, eax
Activa el hardware para indexar el GDT, ¡no buscarlo de forma lineal para buscar datos coincidentes en la memoria!
Formato de datos GDT:
Está viendo el código fuente de Linux x86-64, por lo que el kernel estará en modo largo, no en modo protegido. Podemos decirlo porque hay entradas separadas para USER_CS
y USER32_CS
. El descriptor de segmento de código de 32 bits tendrá su L
poco borrado. La descripción actual del segmento CS es lo que coloca una CPU x86-64 en modo de compatibilidad de 32 bits frente al modo largo de 64 bits. Para ingresar al espacio de usuario de 32 bits, iret
o sysret
establecerá CS: RIP en un selector de segmento de 32 bits en modo de usuario.
I pensar también puede tener la CPU en modo de compatibilidad de 16 bits (como el modo de compatibilidad, no el modo real, pero el tamaño del operando y el tamaño de la dirección predeterminados son 16). Sin embargo, Linux no hace esto.
De todos modos, como se explica en https://wiki.osdev.org/Global_Descriptor_Table and Segmentation,
Cada descriptor de segmento contiene la siguiente información:
- El dirección base del segmento
- El tamaño de operación predeterminado en el segmento (16 bits / 32 bits)
- El nivel de privilegio del descriptor (Ring 0 -> Ring 3)
- La granularidad (el límite de segmento está en unidades de bytes / 4kb)
- El límite del segmento (el desplazamiento legal máximo dentro del segmento)
- La presencia del segmento (está presente o no)
- El tipo de descriptor (0 = sistema; 1 = código / datos)
- El tipo de segmento (Código / Datos / Lectura / Escritura / Accedido / Conforme / No conforme / Expandir hacia arriba / Expandir hacia abajo)
Estos son los bits adicionales. No estoy particularmente interesado en qué bits son cuáles porque (creo que) entiendo la imagen de alto nivel de para qué sirven las diferentes entradas de GDT y para qué sirven, sin entrar en los detalles de cómo está realmente codificado.
Pero si consulta los manuales de x86 o la wiki de osdev, y las definiciones de esas macros init, debería encontrar que dan como resultado una entrada GDT con la L
bit establecido para segmentos de código de 64 bits, borrado para segmentos de código de 32 bits. Y, obviamente, el tipo (código frente a datos) y el nivel de privilegio difieren.
Puntuaciones y comentarios
Puedes añadir valor a nuestro contenido asistiendo con tu experiencia en las críticas.