memory management - tipos - ¿Cómo funciona generalmente un sistema operativo para administrar la memoria del kernel y el manejo de la página?
sistemas operativos (2)
La respuesta a esta pregunta depende en gran medida de la arquitectura. Voy a asumir que estás hablando de x86. Con x86, un kernel generalmente proporciona un conjunto de llamadas al sistema , que son puntos de entrada predeterminados en el kernel. El código de usuario solo puede ingresar al kernel en esos puntos específicos, por lo que el kernel 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 interrupciones, el kernel configura una tabla de descriptores de interrupción (IDT), que define los posibles puntos de entrada en el kernel. El código de usuario puede usar la instrucción int
para generar una interrupción suave para llamar al kernel. Las interrupciones también pueden ser generadas por el hardware (llamadas interrupciones duras); esas interrupciones generalmente deberían ser distintas de las interrupciones suaves, pero no tienen que ser así.
Las instrucciones sysenter y sysexit son una forma más rápida de realizar llamadas al sistema, ya que el manejo de interrupciones es lento; No estoy tan familiarizado con su uso, así que no puedo comentar si son o no una mejor opción para su situación.
Cualquiera que sea el que use, tendrá que definir la interfaz de llamada del sistema. Probablemente quiera pasar argumentos de llamadas al sistema en registros y no en la pila, ya que generar una interrupción hará que cambie pilas a la pila del núcleo. Esto significa que seguramente tendrá que escribir algunos stubs del lenguaje ensamblador en el extremo del modo de usuario para hacer la llamada al sistema, y nuevamente en el extremo del kernel para reunir los argumentos de llamada del sistema y guardar los registros.
Una vez que tenga todo eso en su lugar, puede empezar a pensar en manejar fallas de página. Las fallas de página son solo otro tipo de interrupción: cuando el código de usuario intenta acceder a una dirección virtual para la que no hay una 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 en la página que falta del 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 la clase de sistemas operativos MIT . Mira la sección de referencias, tiene muchas cosas buenas.
Estoy trabajando en el diseño del kernel, y tengo algunas preguntas sobre la búsqueda.
La idea básica que tengo hasta ahora es esta: cada programa obtiene su propio (o eso cree) 4G de memoria, menos una sección en algún lugar que reservo para las funciones del kernel que el programa puede llamar. Entonces, el sistema operativo necesita encontrar alguna forma de cargar las páginas en memoria que el programa necesita usar durante su operación.
Ahora, suponiendo que tuviéramos cantidades infinitas de memoria y tiempo de procesador, podría cargar / asignar cualquier página en la que el programa escribiera o leyera como sucedió usando fallas de página para páginas que no existían (o que fueron reemplazadas) para que el sistema operativo podría rápidamente asignarlos o intercambiarlos. Sin embargo, en el mundo real, necesito optimizar este proceso, de modo que no tengamos un programa que consuma constantemente toda la memoria que haya tocado.
Así que supongo que mi pregunta es, ¿cómo funciona un sistema operativo en general? Mi idea inicial es crear una función que el programa llama para establecer / liberar páginas, que luego puede administrar la memoria por sí mismo, pero, ¿lo hace generalmente un programa, o el compilador supone que tiene dominio libre? Además, ¿cómo maneja el compilador las situaciones en las que necesita asignar un segmento bastante grande de memoria? ¿Debo 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 se encuentre en ese o 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 debería ser más fácil de responder también: ¿cómo se manejan generalmente las funciones del kernel que un programa necesita llamar? ¿Está bien solo tener un área de memoria determinada (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 idea desde allí sería hacer que las funciones del núcleo hicieran algo muy elegante y cambiar las páginas (para que los programas no pudieran ver funciones del kernel sensibles en su propio espacio) cuando los programas necesitaban 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 que el núcleo sea completamente compatible con GCC (de alguna manera) y necesito asegurarme de que proporciona todo lo que un programa normal podría necesitar.
Gracias por cualquier consejo.
Un buen punto de partida para todas estas preguntas es ver cómo lo hace Unix. Como dice una cita famosa: "Aquellos que no entienden a UNIX están condenados a reinventarlo, pobremente".
Primero, sobre llamar funciones de kernel. No es suficiente simplemente tener las funciones en algún lugar que un programa pueda llamar, 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 anillo 0 en IA-32) para hacer sus operaciones privilegiadas. De alguna manera, debe 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 los que están disponibles dependen de si la CPU es de AMD o Intel, y del modelo de CPU específico. Para acomodar esta variación, los kernels recientes de Linux usan una página de código mapeada por el kernel 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 gran tema; podrías escribir un libro grande sobre él y no exaust 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 de hardware y, a menudo, para los periféricos externos) y la vista lógica (el orden de las páginas) visto por los programas que se ejecutan en la CPU). Es bastante fácil confundir a los dos. Asignará páginas físicas y las asignará a direcciones lógicas en el espacio de direcciones del programa o kernel. 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 kernel (reservada para el kernel) generalmente se mapea en la parte superior del espacio de direcciones de cada proceso. Sin embargo, está configurado por lo que solo puede ser procesado en modo kernel. No hay necesidad de trucos sofisticados para ocultar esa parte de la memoria; el hardware hace todo el trabajo de bloquear el acceso (en IA-32, se realiza a través de indicadores de página o límites de segmento).
Los programas asignan memoria en el resto del espacio de direcciones de varias maneras:
- Parte de la memoria es asignada por el cargador de programas del kernel. Esto incluye el código del programa (o "texto"), los datos inicializados del programa ("datos"), los datos del programa no inicializados ("bss", rellenos cero), la pila y varias probabilidades y extremos. Cuánto asignar, dónde, cuáles deberían ser los contenidos iniciales, qué banderas de protección usar, y muchas otras cosas, se leen desde los encabezados en el archivo ejecutable que se va a cargar.
- Tradicionalmente, en Unix, hay un área de memoria que puede crecer y reducirse (su límite superior puede modificarse mediante la llamada al sistema
brk()
). Esto es usado tradicionalmente por el montón (el asignador de memoria en la biblioteca C, de la cualmalloc()
es una de las interfaces, es responsable del montón). - A menudo puede pedirle al kernel que asigne un archivo a un área de espacio de direcciones. Las lecturas y escrituras en esa área son (a través de magia de paginación) dirigidas al archivo de respaldo. Esto generalmente se llama
mmap()
. Con unmmap
anónimo, puede asignar nuevas áreas del espacio de direcciones que no están respaldadas por ningún archivo, pero actúan de la misma manera. El cargador de programas del kernel a menudo usarámmap
para asignar partes del código del programa (por ejemplo, el código del programa puede ser respaldado por el ejecutable mismo).
Las áreas de acceso del espacio de direcciones que no están asignadas de ninguna manera (o están reservadas para el kernel) se consideran un error, y en Unix provocarán que se envíe una señal al programa.
El compilador asigna memoria de forma estática (especificándola en los encabezados del archivo ejecutable, el cargador de programa del kernel 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 una función en el Biblioteca estándar de 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. Irá con mucho más detalle de lo que podría en una respuesta en .