memory - optimizacion - ¿Cómo distribuye el código el compilador en la memoria?
generacion de codigo objeto compiladores (3)
Ok, tengo un poco de pregunta de un estudiante novato.
Así que estoy familiarizado con el hecho de que las pilas contienen llamadas de subrutinas, y las pilas contienen estructuras de datos de longitud variable, y las variables estáticas globales se asignan a las ubicaciones de memoria permanente.
Pero, ¿cómo funciona todo en un nivel menos teórico?
¿El compilador simplemente supone que tiene una región entera de memoria desde la dirección 0 para abordar el infinito? ¿Y luego solo comienzas a asignar cosas?
¿Y dónde distribuye las instrucciones, la pila y el montón? En la parte superior de la región de la memoria, al final de la región de la memoria?
¿Y cómo funciona esto con la memoria virtual? La memoria virtual es transparente para el programa?
Perdón por las preguntas de un bajilion pero estoy tomando estructuras de lenguaje de programación y sigue refiriéndose a estas regiones y quiero entenderlas en un nivel más práctico.
¡GRACIAS mucho antes!
Depende.
Si está compilando un gestor de arranque, que tiene que empezar desde cero, puede asumir que tiene toda la memoria para usted.
Por otro lado, si está compilando una aplicación, puede asumir que tiene toda la memoria para usted.
La menor diferencia es que en el primer caso, usted tiene toda la memoria física para usted. Como cargador de arranque, no hay nada más en la RAM todavía. En el segundo caso, hay un sistema operativo en la memoria, pero (normalmente) configurará la memoria virtual para que parezca que tiene todo el espacio de direcciones para usted. Usuaully todavía tienes que pedirle al sistema operativo la memoria real, sin embargo.
Esto último significa que el sistema operativo impone algunas reglas. Por ejemplo, al sistema operativo le gustaría saber dónde está la primera instrucción de su programa. Una regla simple podría ser que su programa siempre comienza en la dirección 0, por lo que el compilador de C podría poner int main()
ahí. Por lo general, al sistema operativo le gustaría saber dónde está la pila, pero esta ya es una regla más flexible. En lo que se refiere a "el montón", el sistema operativo realmente no podría importarle.
Esta es una pregunta abierta con muchos temas.
Asumiendo el compilador típico -> ensamblador -> cadena de herramientas del enlazador. El compilador no sabe mucho, simplemente codifica cosas relativas a la pila, no importa cuánto o dónde está la pila, ese es el propósito / la belleza de una pila, no importa. El compilador genera el ensamblador que ensambla el ensamblador en un objeto, luego el enlazador toma la secuencia de comandos del enlazador de información de algunos argumentos de sabor o línea de comando que le dicen los detalles del espacio de la memoria, cuando usted
gcc hello.c -o hello
su instalación de binutils tiene una secuencia de comandos del enlazador predeterminada que se adapta a su objetivo (Windows, Mac, Linux, lo que sea que esté ejecutando). Y ese script contiene la información sobre dónde se inicia el espacio del programa, y desde allí sabe dónde comenzar el montón (después del texto, datos y bss). Es probable que el puntero de la pila esté configurado por ese script del enlazador y / o el os lo gestiona de otra manera. Y eso define tu stack.
Para un sistema operativo con un mmu, que es lo que tienen las computadoras portátiles y de escritorio Windows y Linux y Mac y BSD, entonces sí, cada programa se compila asumiendo que tiene su propio espacio de direcciones comenzando en 0x0000 que no significa que el programa está vinculado a comienza a ejecutarse en 0x0000, depende del sistema operativo en cuanto a lo que son las reglas de los sistemas operativos, algunos comienzan en 0x8000, por ejemplo.
Para una aplicación de escritorio donde de alguna manera es un espacio de dirección lineal único desde la perspectiva de su programa, es probable que tenga .text primero, luego .data o .bss y luego, después de todo, el montón se alineará en algún momento después de eso. Sin embargo, la pila que está configurada normalmente está alta y baja pero puede ser específica del procesador y del sistema operativo. esa pila generalmente está dentro de la vista de programas del mundo en la parte superior de su memoria.
la memoria virtual es invisible para todo esto, la aplicación normalmente no conoce o no le importa la memoria virtual. si y cuando la aplicación recupera una instrucción o realiza una transferencia de datos, pasa a través del hardware configurado por el sistema operativo y que convierte entre virtual y físico. Si el mmu indica una falla, lo que significa que el espacio no se ha asignado a una dirección física, a veces puede ser intencional y luego se aplica otro uso del término "memoria virtual". En esta segunda definición, el sistema operativo puede, por ejemplo, tomar otro trozo de memoria, el tuyo o el de otra persona, moverlo al disco duro, por ejemplo, marcar ese otro trozo como no presente y luego marcar que tu trozo tiene algún ariete y luego dejarlo ejecutas sin saber que fuiste interrumpido con un ariete que no sabías que tenías que quitarle a alguien más. Su aplicación, por diseño, no quiere saber nada de esto, solo desea ejecutar, el sistema operativo se encarga de administrar la memoria física y el mmu que le brinda un espacio de direcciones virtual (basado en cero) ...
Si fuera a hacer un poco de programación bare metal, sin material mmu al principio y luego con microcontrolador, qemu, raspberry pi, beaglebone, etc., puede ensuciarse las manos con el compilador, el script del enlazador y la configuración de un mmu. Utilizaría un brazo o mips para esto no x86, solo para hacer su vida más fácil, el panorama general se traduce directamente en todos los objetivos.
Una explicación completa probablemente esté más allá del alcance de este foro. Textos enteros están dedicados al tema. Sin embargo, en un nivel simplista puedes mirarlo de esta manera.
El compilador no muestra el código en la memoria. Asume que tiene toda la región de memoria en sí misma. El compilador genera archivos de objetos donde los símbolos en los archivos de objeto normalmente comienzan en el desplazamiento 0.
El vinculador es responsable de juntar los archivos del objeto, vincular los símbolos a su nueva ubicación de desplazamiento dentro del objeto vinculado y generar el formato de archivo ejecutable.
El enlazador tampoco establece el código en la memoria. Se empaqueta el código y los datos en secciones típicamente etiquetadas como .text
para las instrucciones del código ejecutable y .data
para cosas como variables globales y constantes de cadena. (y hay otras secciones también para diferentes propósitos) El enlazador puede proporcionar una pista para el cargador del sistema operativo donde reubicar los símbolos, pero el cargador no tiene que obligar.
Es el cargador del sistema operativo que analiza el archivo ejecutable y decide dónde se distribuyen el código y los datos en la memoria. La ubicación de la cual depende completamente del sistema operativo. Normalmente, la pila se encuentra en una región de memoria más alta que las instrucciones y datos del programa y crece hacia abajo.
Cada programa se compila / vincula con la suposición de que tiene todo el espacio de direcciones para sí mismo. Aquí es donde entra la memoria virtual. Es completamente transparente para el programa y es administrada completamente por el sistema operativo.
La memoria virtual normalmente varía desde la dirección 0 y hasta la dirección máxima admitida por la plataforma (no infinito). Este espacio de direcciones virtuales está dividido por el sistema operativo en el espacio direccionable del kernel y el espacio direccionable por el usuario. Digamos que en un sistema operativo hipotético de 32 bits, las direcciones superiores a 0x80000000
están reservadas para el sistema operativo y las direcciones a continuación son para uso del programa. Si el programa intenta acceder a la memoria por encima de esta partición, se cancelará.
El sistema operativo puede decidir que la pila comience en la memoria de usuario direccionable más alta y que crezca con el código del programa ubicado en una dirección mucho más baja.
La ubicación de la pila suele administrarla la biblioteca en tiempo de ejecución en la que ha creado su programa. Podría vivir a partir de la próxima dirección disponible después de su código de programa y datos.