¿Cómo funciona generalmente un sistema operativo para administrar la memoria del núcleo y el manejo de páginas?

StackOverflow https://stackoverflow.com/questions/271115

Pregunta

Estoy trabajando en el diseño del núcleo y tengo algunas preguntas sobre la paginación.

La idea básica que tengo hasta ahora es esta: cada programa tiene su propio (o eso cree) 4G de memoria, menos una sección en algún lugar que reservo para las funciones del núcleo que el programa puede llamar. Entonces, el sistema operativo necesita encontrar alguna forma de cargar las páginas en la memoria que el programa necesita usar durante su operación.

Ahora, suponiendo que tengamos una cantidad infinita de memoria y tiempo de procesador, podría cargar / asignar cualquier página en la que el programa escribió o leyó como sucedió usando fallas de página para páginas que no existían (o se cambiaron) para que el sistema operativo pueda asignarlos rápidamente o intercambiarlos. Sin embargo, en el mundo real, necesito optimizar este proceso, para que no tengamos un programa que consuma constantemente toda la memoria que alguna vez tocó.

Entonces, supongo que mi pregunta es, ¿cómo hace un SO en general para solucionar esto? Mi pensamiento inicial es crear una función que el programa llame para establecer / liberar páginas, que luego puede administrar la memoria por sí mismo, pero ¿un programa generalmente hace esto, o el compilador supone que tiene un reinado libre? Además, ¿cómo maneja el compilador las situaciones en las que necesita asignar un segmento bastante grande de memoria? ¿Necesito proporcionar una función que intente darle X páginas en orden?

Obviamente, esta no es una pregunta específica del lenguaje, pero soy parcial al estándar C y bueno con C ++, por lo que me gustaría que cualquier ejemplo de código esté en eso o en el ensamblado. (El ensamblaje no debería ser necesario, tengo la intención de hacerlo funcionar con la mayor cantidad de código C posible y optimizarlo como último paso).

Otra cosa que también debería ser más fácil de responder: ¿cómo se manejan generalmente las funciones del núcleo que un programa necesita llamar? ¿Está bien solo tener un área de memoria establecida (estaba pensando hacia el final del espacio virtual) que contiene la mayoría de las funciones básicas / memoria específica del proceso que el programa puede llamar? Mi pensamiento desde allí sería que las funciones del kernel hicieran algo muy elegante e intercambiaran las páginas (para que los programas no pudieran ver las funciones sensibles del kernel en su propio espacio) cuando los programas necesitan hacer algo importante, pero no estoy realmente centrándose en la seguridad en este punto.

Así que supongo que estoy más preocupado por las ideas generales de diseño que por los detalles. Me gustaría hacer que el núcleo sea completamente compatible con GCC (de alguna manera) y necesito asegurarme de que proporcione todo lo que un programa normal necesitaría.

Gracias por cualquier consejo.

¿Fue útil?

Solución

Un buen punto de partida para todas estas preguntas es observar cómo lo hace Unix. Como dice una famosa cita, "Quienes no entienden UNIX están condenados a reinventarlo mal".

Primero, acerca de llamar a las funciones del núcleo. No es suficiente tener las funciones en algún lugar al que pueda llamar un programa, ya que el programa probablemente se esté ejecutando en "modo de usuario". (anillo 3 en IA-32) y el kernel debe ejecutarse en "modo kernel" (generalmente suena 0 en IA-32) para realizar sus operaciones privilegiadas. Tienes que hacer la transición entre ambos modos, y esto es muy específico de la arquitectura.

En IA-32, la forma tradicional es usar una puerta en el IDT junto con una interrupción de software (Linux usa int 0x80). Los procesadores más nuevos tienen otras formas (más rápidas) de hacerlo, y las que están disponibles dependen de si la CPU es de AMD o Intel, y del modelo de CPU específico. Para adaptarse a esta variación, los núcleos de Linux recientes utilizan una página de código asignada por el núcleo en la parte superior del espacio de direcciones para cada proceso. Entonces, en Linux reciente, para hacer una llamada al sistema, llama a una función en esta página, que a su vez hará lo que sea necesario para cambiar al modo kernel (el kernel tiene más de una copia de esa página y elige qué copia usar) en el arranque dependiendo de las características de su procesador).

Ahora, la gestión de la memoria. Este es un tema enorme ; podrías escribir un libro grande sobre él y no exponer el tema.

Asegúrese de tener en cuenta que hay al menos dos vistas de la memoria: la vista física (el orden real de las páginas, visible para el subsistema de memoria del hardware y, a menudo, para los periféricos externos) y la vista lógica (el orden de las páginas vistas por los programas que se ejecutan en la CPU). Es bastante fácil confundir a ambos. Asignará páginas físicas y las asignará a direcciones lógicas en el programa o en el espacio de direcciones del núcleo. Una sola página física puede tener varias direcciones lógicas y puede asignarse a diferentes direcciones lógicas en diferentes procesos.

La memoria del núcleo (reservada para el núcleo) generalmente se asigna en la parte superior del espacio de direcciones de cada proceso. Sin embargo, está configurado para que solo se pueda acceder en modo kernel. No hay necesidad de trucos elegantes para ocultar esa porción de memoria; el hardware hace todo el trabajo de bloquear el acceso (en IA-32, se realiza mediante indicadores de página o límites de segmento).

Los programas asignan memoria al resto del espacio de direcciones de varias maneras:

  • El cargador de programas del núcleo asigna parte de la memoria. Esto incluye el código del programa (o "texto"), los datos inicializados del programa ("datos"), los datos no inicializados del programa ("bss", rellenos con cero), la pila y varias probabilidades y finales. Cuánto asignar, dónde, cuáles deberían ser los contenidos iniciales, qué indicadores de protección usar y varias otras cosas, se leen desde los encabezados del archivo ejecutable que se cargará.
  • Tradicionalmente en Unix, hay un área de memoria que puede crecer y reducirse (su límite superior se puede cambiar mediante la llamada al sistema brk () ). El montón lo usa tradicionalmente (el asignador de memoria en la biblioteca C, del cual malloc () es una de las interfaces, es responsable del montón).
  • A menudo puede pedirle al núcleo que asigne un archivo a un área de espacio de direcciones. Las lecturas y escrituras en esa área se dirigen (a través de la magia de paginación) al archivo de respaldo. Esto generalmente se llama mmap () . Con un mmap anónimo, puede asignar nuevas áreas del espacio de direcciones que no están respaldadas por ningún archivo, pero que de otra manera actúan de la misma manera. El cargador de programas del núcleo a menudo usará mmap para asignar partes del código del programa (por ejemplo, el código ejecutable puede respaldarlo).

Las áreas de acceso del espacio de direcciones que no están asignadas de ninguna manera (o están reservadas para el núcleo) se consideran un error, y en Unix hará que se envíe una señal al programa.

El compilador asigna memoria estáticamente (especificándola en los encabezados de los archivos ejecutables; el cargador de programas del núcleo asignará la memoria al cargar el programa) o dinámicamente (llamando a una función en la biblioteca estándar del idioma, que generalmente llama a un funcionar en la biblioteca estándar del lenguaje C, que luego llama al núcleo para asignar memoria y la subdivide si es necesario).

La mejor manera de aprender los conceptos básicos de todo esto es leer uno de los varios libros sobre sistemas operativos, en particular los que usan una variante de Unix como ejemplo. Tendré más detalles de lo que podría en una respuesta en StackOverflow.

Otros consejos

La respuesta a esta pregunta depende mucho de la arquitectura. Asumiré que estás hablando de x86. Con x86, un núcleo generalmente proporciona un conjunto de llamadas al sistema , que son puntos de entrada predeterminados en el núcleo. El código de usuario solo puede ingresar al núcleo en esos puntos específicos, por lo que el núcleo tiene un control cuidadoso sobre cómo interactúa con el código de usuario.

En x86, hay dos formas de implementar llamadas al sistema: con interrupciones y con las instrucciones sysenter / sysexit. Con las interrupciones, el núcleo configura una tabla de descriptores de interrupción (IDT), que define los posibles puntos de entrada al núcleo. El código de usuario puede usar la instrucción int para generar una interrupción suave para llamar al núcleo. Las interrupciones también pueden ser generadas por hardware (las llamadas interrupciones duras); esas interrupciones generalmente deben ser distintas de las interrupciones suaves, pero no tienen que serlo.

Las instrucciones sysenter y sysexit son una forma más rápida de realizar llamadas al sistema, ya que manejar las interrupciones es lento; No estoy tan familiarizado con su uso, por lo que no puedo comentar si son o no una mejor opción para su situación.

Independientemente de lo que use, deberá definir la interfaz de llamada del sistema. Probablemente querrá pasar argumentos de llamadas al sistema en registros y no en la pila, ya que generar una interrupción hará que cambie las pilas a la pila del núcleo. Esto significa que seguramente tendrá que escribir algunos apéndices de lenguaje ensamblador tanto en el extremo del modo de usuario para realizar la llamada del sistema, como nuevamente en el extremo del núcleo para reunir los argumentos de la llamada del sistema y guardar los registros.

Una vez que tenga todo eso en su lugar, puede comenzar a pensar en manejar las fallas de la página. Las fallas de página son efectivamente solo otro tipo de interrupción: cuando el código de usuario intenta acceder a una dirección virtual para la cual no hay entrada en la tabla de páginas, generará la interrupción 14, y también obtendrá la dirección de falla como un código de error. El núcleo puede tomar esta información y luego decidir leer la página que falta en el disco, agregar el mapeo de la tabla de páginas y volver al código del usuario.

Le recomiendo que eche un vistazo a algunos de los materiales de los Sistemas operativos MIT clase. Echa un vistazo a la sección de referencias, tiene un montón de cosas buenas.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top