Estoy estudiando los sistemas operativos y la arquitectura x86, y mientras leía acerca de la segmentación y la paginación, naturalmente tenía curiosidad sobre cómo los sistemas operativos modernos manejan la administración de la memoria. Por lo que encontré, Linux y la mayoría de los otros sistemas operativos esencialmente evitan la segmentación a favor de la paginación. Algunas de las razones de esto que encontré fueron la simplicidad y la portabilidad.
¿Qué usos prácticos existen para la segmentación (x86 o de otro tipo) y alguna vez veremos sistemas operativos robustos que lo usen o continuarán favoreciendo un sistema basado en paginación?
Ahora sé que esta es una pregunta cargada, pero tengo curiosidad por saber cómo se manejaría la segmentación con los sistemas operativos recientemente desarrollados. ¿Tiene tanto sentido favorecer la paginación que nadie considerará un enfoque más "segmentado"? Si es así, ¿por qué?
Y cuando digo la segmentación de 'evitar', estoy implicando que Linux solo lo usa en la medida de lo necesario. Solo 4 segmentos para el usuario y el código del núcleo / segmentos de datos. Mientras leía la documentación de Intel tuve la sensación de que la segmentación se diseñó con soluciones más sólidas en mente. Por otra parte, me dijeron en muchas ocasiones lo complicado que puede ser el x86.
Encontré esta anécdota interesante después de estar vinculado al 'anuncio' original de Linux Torvald para Linux. Dijo esto algunas publicaciones más tarde:
Simplemente, diría que portar es imposible. Está principalmente en C, pero la mayoría de las personas no llamarían lo que yo escribo C. Utiliza todas las características imaginables del 386 que pude encontrar, ya que también fue un proyecto para enseñarme sobre el 386. Como ya se mencionó, usa un MMU , tanto para la paginación (todavía no en el disco) como para la segmentación. Es la segmentación lo que lo hace REALMENTE dependiente 386 (cada tarea tiene un segmento de 64Mb para código y datos - máximo 64 tareas en 4Gb. Cualquiera que necesite más de 64Mb / tarea - cookies difíciles).
Creo que mi propia experimentación con x86 me llevó a hacer esta pregunta. Linus no tenía StackOverflow, por lo que solo lo implementó para probarlo.
fuente
Respuestas:
Con la segmentación sería, por ejemplo, posible colocar cada objeto asignado dinámicamente (malloc) en su propio segmento de memoria. El hardware verificaría automáticamente los límites de los segmentos y se eliminaría toda la clase de errores de seguridad (desbordamientos del búfer).
Además, dado que todos los desplazamientos de segmento comienzan en cero, todo el código compilado sería automáticamente independiente de la posición. Llamar a otra DLL se reduciría a una llamada lejana con desplazamiento constante (dependiendo de la función llamada). Esto simplificaría enormemente los enlazadores y cargadores.
Con 4 anillos de protección, es posible idear un control de acceso más detallado (con paginación solo tiene 2 niveles de protección: usuario y supervisor) y núcleos de sistema operativo más robustos. Por ejemplo, solo el anillo 0 tiene acceso completo al hardware. Al separar el núcleo del sistema operativo principal y los controladores de dispositivo en los anillos 0 y 1, podría hacer un sistema operativo de microkernel más robusto y muy rápido donde HW realizaría la mayoría de las verificaciones de acceso relevantes. (Los controladores de dispositivo podrían obtener acceso al hardware a través del mapa de bits de acceso de E / S en el TSS).
Sin embargo ... x86 es un poco limitado. Tiene solo 4 registros de segmento de datos "libres"; recargarlos es bastante costoso y es posible acceder simultáneamente solo a 8192 segmentos. (Suponiendo que desea maximizar el número de objetos accesibles, entonces el GDT solo contiene descriptores del sistema y descriptores LDT).
Ahora, con la segmentación en modo de 64 bits se describe como "heredado" y las comprobaciones de límite de hardware se realizan solo en circunstancias limitadas. En mi humilde opinión, un gran error. En realidad, no culpo a Intel, culpo principalmente a los desarrolladores, la mayoría de los cuales pensaban que la segmentación era "demasiado complicada" y anhelaba un espacio de direcciones planas. También culpo a los escritores del sistema operativo que carecían de la imaginación para dar un buen uso a la segmentación. (AFAIK, OS / 2 fue el único sistema operativo que hizo uso completo de las características de segmentación).
fuente
La respuesta corta es que la segmentación es un truco, que se usa para hacer que un procesador con una capacidad limitada para direccionar la memoria exceda esos límites.
En el caso del 8086, había 20 líneas de dirección en el chip, lo que significa que podía acceder físicamente a 1Mb de memoria. Sin embargo, la arquitectura interna se basó en el direccionamiento de 16 bits, probablemente debido al deseo de mantener la coherencia con el 8080. Por lo tanto, el conjunto de instrucciones incluía registros de segmentos que se combinarían con los índices de 16 bits para permitir el direccionamiento de 1Mb completo de memoria . El 80286 amplió este modelo con una verdadera MMU, para admitir la protección basada en segmentos y el direccionamiento de más memoria (iirc, 16Mb).
En el caso del PDP-11, los modelos posteriores del procesador proporcionaron una segmentación en espacios de Instrucción y Datos, nuevamente para admitir las limitaciones de un espacio de direcciones de 16 bits.
El problema con la segmentación es simple: su programa debe evitar explícitamente las limitaciones de la arquitectura. En el caso del 8086, esto significaba que el bloque contiguo de memoria más grande al que podía acceder era 64k. Si necesitara acceder a más que eso, tendría que cambiar sus registros de segmento. Lo que significaba, para un programador de C, que tenía que decirle al compilador de C qué tipo de punteros debería generar.
Fue mucho más fácil programar el MC68k, que tenía una arquitectura interna de 32 bits y un espacio de direcciones físicas de 24 bits.
fuente
Para 80x86 hay 4 opciones: "nada", solo segmentación, solo paginación y segmentación y paginación.
Para "nada" (sin segmentación o paginación) terminará sin una manera fácil de proteger un proceso de sí mismo, no hay una manera fácil de proteger los procesos entre sí, no hay forma de manejar cosas como la fragmentación del espacio de direcciones físicas, no hay forma de evitar la posición código independiente, etc. A pesar de todos estos problemas, podría (en teoría) ser útil en algunas situaciones (por ejemplo, dispositivo integrado que solo ejecuta una aplicación; o tal vez algo que use JIT y virtualice todo de todos modos).
Solo para segmentación; casi resuelve el problema de "proteger un proceso de sí mismo", pero se necesitan muchas soluciones alternativas para que sea utilizable cuando un proceso quiere usar más de 8192 segmentos (suponiendo un LDT por proceso), lo que hace que se rompa. Casi resuelven el problema de "proteger los procesos unos de otros"; pero diferentes piezas de software que se ejecutan en el mismo nivel de privilegio pueden cargar / usar los segmentos de cada uno (hay formas de evitarlo: modificando las entradas de GDT durante las transferencias de control y / o usando LDT). También resuelve principalmente el problema del "código de posición independiente" (puede causar un problema de "código dependiente del segmento", pero eso es mucho menos significativo). No hace nada por el problema de "fragmentación del espacio de direcciones físicas".
Solo para paginación; no resuelve mucho el problema de "proteger un proceso de sí mismo" (pero seamos honestos aquí, esto solo es realmente un problema para depurar / probar código escrito en lenguajes inseguros, y de todos modos hay herramientas mucho más poderosas como valgrind). Resuelve completamente el problema de "proteger los procesos unos de otros", resuelve completamente el problema del "código de posición independiente" y resuelve completamente el problema de "fragmentación del espacio de direcciones físicas". Como una ventaja adicional, abre algunas técnicas muy poderosas que no son tan prácticas sin paginación; incluyendo cosas como "copiar al escribir", archivos mapeados en memoria, manejo eficiente del espacio de intercambio, etc.
Ahora pensaría que usar tanto la segmentación como la paginación le daría los beneficios de ambos; y en teoría puede, excepto que el único beneficio que obtiene de la segmentación (que no se hace mejor mediante paginación) es una solución al problema de "proteger un proceso de sí mismo" que a nadie le importa realmente. En la práctica, lo que obtienes es la complejidad de ambos y la sobrecarga de ambos, para muy poco beneficio.
Esta es la razón por la cual casi todos los sistemas operativos diseñados para 80x86 no usan la segmentación para la administración de la memoria (lo usan para cosas como almacenamiento por CPU y por tarea, pero eso es solo por conveniencia para evitar consumir un registro de propósito general más útil para estos cosas).
Por supuesto, los fabricantes de CPU no son tontos: no van a gastar tiempo y dinero optimizando algo que saben que nadie usa (van a optimizar algo que casi todos usan). Por esta razón, los fabricantes de CPU no optimizan la segmentación, lo que hace que la segmentación sea más lenta de lo que podría ser, lo que hace que los desarrolladores de sistemas operativos quieran evitarla aún más. En su mayoría, solo mantuvieron la segmentación por compatibilidad con versiones anteriores (lo cual es importante).
Finalmente, AMD diseñó el modo largo. No había que preocuparse por el código antiguo / existente de 64 bits, por lo que (para el código de 64 bits) AMD se deshizo de tanta segmentación como pudieron. Esto les dio a los desarrolladores del sistema operativo otra razón (no es una forma fácil de portar código diseñado para la segmentación a 64 bits) para continuar evitando la segmentación.
fuente
Estoy bastante sorprendido de que, en todo momento desde que se publicó esta pregunta, nadie haya mencionado los orígenes de las arquitecturas de memoria segmentada y el verdadero poder que pueden permitirse.
El sistema original que inventó, o refinó en forma útil, todas las características que rodean el diseño y el uso de sistemas segmentados de memoria virtual paginada (junto con multiprocesamiento simétrico y sistemas de archivos jerárquicos) fue Multics (y vea también el sitio de Multicians ). La memoria segmentada permite a Multics ofrecer al usuario una vista de que todo está en la memoria (virtual), y permite el máximo nivel de intercambio de todoen forma directa (es decir, directamente direccionable en la memoria). El sistema de archivos se convierte simplemente en un mapa de todos los segmentos en la memoria. Cuando se usa correctamente de manera sistemática (como en Multics), la memoria segmentada libera al usuario de las muchas cargas de administrar el almacenamiento secundario y compartir datos y las comunicaciones entre procesos. Otras respuestas han hecho algunas afirmaciones de que la memoria segmentada es más difícil de usar, pero esto simplemente no es cierto, y Multics lo demostró con un éxito rotundo hace décadas.
Intel creó una versión cobarde de memoria segmentada, la 80286, que aunque es bastante poderosa, sus limitaciones impidieron que se usara para algo realmente útil. El 80386 mejoró estas limitaciones, pero las fuerzas del mercado en ese momento prácticamente impidieron el éxito de cualquier sistema que realmente pudiera aprovechar estas mejoras. En los años transcurridos desde que parece, demasiadas personas han aprendido a ignorar las lecciones del pasado.
Intel también intentó desde el principio construir un súper micro más capaz llamado iAPX 432 que hubiera superado con creces cualquier otra cosa en ese momento, y tenía una arquitectura de memoria segmentada y otras características fuertemente orientadas a la programación orientada a objetos. Sin embargo, la implementación original fue demasiado lenta, y no se hicieron más intentos para solucionarlo.
Una discusión más detallada de cómo Multics usó la segmentación y la paginación se puede encontrar en el documento de Paul Green, Multics Virtual Memory - Tutorial and Reflections
fuente
La segmentación fue un truco / solución alternativa para permitir que un procesador de 16 bits direccione hasta 1 MB de memoria; normalmente, solo 64 K de memoria habrían sido accesibles.
Cuando aparecieron los procesadores de 32 bits, podía direccionar hasta 4 GB de memoria con un modelo de memoria plana y ya no había necesidad de segmentación: los registros de segmento se volvieron a utilizar como selectores para el GDT / paginación en modo protegido (aunque puede tienen modo protegido de 16 bits).
Además, un modo de memoria plana es mucho más conveniente para los compiladores: puede escribir programas segmentados de 16 bits en C , pero es un poco engorroso. Un modelo de memoria plana simplifica todo.
fuente
Algunas arquitecturas (como ARM) no admiten segmentos de memoria en absoluto. Si Linux hubiera dependido de la fuente de los segmentos, no podría haberse portado a esas arquitecturas muy fácilmente.
Mirando la imagen más amplia, la falla de los segmentos de memoria tiene que ver con la continua popularidad de C y la aritmética de puntero. El desarrollo de C es más práctico en una arquitectura con memoria plana; y si quieres memoria plana, eliges la paginación de memoria.
Hubo un momento en el cambio de los años 80 cuando Intel, como organización, anticipaba la futura popularidad de Ada y otros lenguajes de programación de alto nivel. Esto es básicamente de donde provienen algunas de sus fallas más espectaculares, como la horrible segmentación de memoria APX432 y 286. Con el 386 capitularon ante programadores de memoria plana; paginación y se agregó un TLB y los segmentos se hicieron redimensionables a 4GB. Y luego, AMD básicamente eliminó segmentos con x86_64 al hacer que el registro base sea un dont-care / implied-0 (¿excepto fs? Para TLS, creo).
Dicho esto, las ventajas de los segmentos de memoria son obvias: cambiar los espacios de direcciones sin tener que repoblar un TLB. Tal vez algún día alguien haga una CPU de rendimiento competitivo que soporte la segmentación, podemos programar un sistema operativo orientado a la segmentación para ella, y los programadores pueden hacer que Ada / Pascal / D / Rust / another-langage-that-does-require-require-flat -programas de memoria para ello.
fuente
La segmentación es una gran carga para los desarrolladores de aplicaciones. De aquí vino el gran impulso para eliminar la segmentación.
Curiosamente, a menudo me pregunto qué mejor sería i86 si Intel eliminara todo el soporte heredado para estos modos antiguos. Aquí, mejor implicaría una menor potencia y quizás una operación más rápida.
Supongo que se podría argumentar que Intel agrió la leche con segmentos de 16 bits que condujeron a una especie de revuelta de desarrolladores. Pero seamos sinceros, un espacio de direcciones de 64k no es nada, especialmente cuando miras una aplicación moderna. Al final tuvieron que hacer algo porque la competencia podía y lo hizo comercializar efectivamente contra los problemas de espacio de direcciones de i86.
fuente
La segmentación conduce a traducciones e intercambios de páginas más lentos
Por esas razones, la segmentación se eliminó en gran medida en x86-64.
La principal diferencia entre ellos es que:
Si bien puede parecer más inteligente tener anchos de segmento configurables, a medida que aumenta el tamaño de la memoria para un proceso, la fragmentación es inevitable, por ejemplo:
eventualmente se convertirá a medida que crece el proceso 1:
hasta que una división sea inevitable:
En este punto:
Sin embargo, con páginas de tamaño fijo:
Los trozos de memoria de tamaño fijo son simplemente más manejables y han dominado el diseño actual del sistema operativo.
Ver también: https://stackoverflow.com/questions/18431261/how-does-x86-paging-work
fuente