Propósito de los archivos del vinculador al colocar el código en un flash interno [cerrado]

0

¿Cómo se debe programar el archivo del enlazador para colocar un código en la memoria interna? ¿Cómo los archivos del archivador del vinculador son diferentes para los diferentes compiladores?

    
pregunta Saikiran Reddy

3 respuestas

4

Esto se reduce a lo que debería ser la representación binaria de un programa. Por ejemplo, podría pensar que su programa es una serie de bytes, y que todo lo que tiene que hacer es transferir esos bytes al flash interno. Sin embargo, hay un problema con eso, a saber, el direccionamiento de datos.

Si, en su programa, confía en un dato, por ejemplo:

static int my_variable = 1;

Entonces, cualquier código que quiera usar esta variable debe conocer la dirección real de la variable. Si coloca el binario ejecutable en la ubicación 0x1000_0000, y el desplazamiento dentro del binario es 0xFF0, entonces el sistema debe saber que terminará en 0x1000_0FF0.

El programa que tiene la tarea de agrupar todos los fragmentos de código y datos en un archivo binario se denomina vinculador . El propósito del script del enlazador es ayudar al enlazador a descubrir dónde van a terminar estas direcciones en el momento de la ejecución. Pero, por supuesto, hay cosas adicionales que el script de vinculador hace por nosotros:

  • Puede dividir el binario en diferentes secciones y manejarlas de forma independiente. Esto es útil cuando desea que ciertos códigos o datos residan en algún lugar especial.
  • Comprende secciones como "bss". Esta es una región de memoria especial donde el vinculador asumirá que son todos ceros en la inicialización. El código a continuación [1], probablemente terminará en la sección .bss.
  • También puede comprender las secciones copiadas (duplicadas), donde se asumirá que se copiará en otro lugar. Por ejemplo, los datos que no son cero generalmente se copian de la memoria flash a la RAM en el momento de la inicialización. El código [2] a continuación muestra algunos datos que pueden copiarse a la RAM, por lo que se necesita una dirección de memoria diferente en el momento de la compilación.
  • El script del vinculador también puede definir variables en ubicaciones específicas. Esto es útil para determinar los tamaños de las diferentes secciones.

[1] .bss datos:

static int my_table[4] = { 0, 0, 0, 0 };

[2] .data:

static float pi = 3.14159287;
    
respondido por el Pål-Kristian Engstad
3

El archivo enlazador es responsable de especificar el diseño de la memoria del procesador para el compilador, por lo que puede asignar tanto los datos como el código del programa en los lugares correctos. Voy a utilizar un ejemplo bastante complicado, ya que muestra todas las capacidades de este esquema.

El microcontrolador MC9S08AZ1128 de 8 bits de Freescale utiliza una arquitectura de Von Neumann , es decir, las variables y el código de programa son asignado dentro del mismo espacio de direcciones. El procesador tiene un espacio de direcciones de 16 bits que se dirige a 64 K bytes (0x0000-0xFFFF).

Este procesador tiene dos características de direccionamiento especiales: los procesadores Freescale de 8 bits (que se remontan a los 6800 originales de hace 40 años) tienen una sección para variables de "página directa o cero" y registros de E / S, que pueden ser se accede con solo una dirección de 8 bits usando un modo de direccionamiento especial para este propósito. Esto permite una gama completa de instrucciones de dos bytes además de las de tres bytes que tendrían una dirección de 16 bits.

En segundo lugar, incluso con el espacio de direcciones de 16 bits, el procesador puede tener programas que tengan una longitud de hasta 128K bytes. Maneja esto usando un registro de paginación, que asigna 8 páginas de 16K a un bloque de direcciones de 16K en la memoria.

Aquí está el mapa de memoria para este microcontrolador:

Porlotanto,elcompiladornecesitasaberdóndeasignarlasvariablesyelcódigoalasdistintasseccionesdelamemoria.Lohaceconelarchivoenlazador,quedividelosfragmentosdememoriaensegmentosconnombre:

SEGMENTSZ_RAM=READ_WRITE0x0080TO0x00FF;RAM=READ_WRITE0x0100TO0x17FF;RAM1=READ_WRITE0x1900TO0x217F;/*unbankedFLASHROM*/ROM=READ_ONLY0x4000TO0x7FFF;ROM1=READ_ONLY0x2180TO0x3BFF;ROM2=READ_ONLY0xC000TO0xFF7F;EEPROM=READ_ONLY0x3C00TO0x3FFF;INTVECTS=READ_ONLY0xFF80TO0xFFFF;/*bankedFLASHROM*/PPAGE_0=READ_ONLY0x008000TO0x00A17F;/*PAGEpartiallycontainedinROMsegment*/PPAGE_0_1=READ_ONLY0x00BC00TO0x00BFFF;PPAGE_2=READ_ONLY0x028000TO0x02BFFF;PPAGE_3=READ_ONLY0x038000TO0x03BFFF;PPAGE_4=READ_ONLY0x048000TO0x04BFFF;PPAGE_5=READ_ONLY0x058000TO0x05BFFF;PPAGE_6=READ_ONLY0x068000TO0x06BFFF;PPAGE_7=READ_ONLY0x078000TO0x07BFFF;/*PPAGE_1=READ_ONLY0x018000TO0x01BFFF;PAGEalreadycontainedinsegmentat0x4000-0x7FFF*//*PPAGE_3=READ_ONLY0x038000TO0x03BFFF;PAGEalreadycontainedinsegmentat0xC000-0xFFFF*/END

Elprogramadortodavíatienequedecirlealcompiladorquévariablesseasignaránalapáginaceroyquérutinassevanadepositar.Lasrutinasdeinterrupción,porejemplo,nosepuedenguardarenbancos,yaquepuedenocurrirencualquiermomento.Lomismoparamain().Lamemorianoalmacenadatambiénesútilparafuncionesquepuedenllamarsedesdemásdeunsegmento.

Hayunainstrucciónespecial(CALL)queseutilizaenlugardelainstrucciónJSRnormalparallamardesdelapágina0aunbancoodesdeunbancoalsiguiente.

Elprogramadorutiliza"pragmas" para especificar si una rutina debe ubicarse en una memoria no almacenada o no almacenada:

#pragma CODE_SEG NON_BANKED
void __interrupt VectorNumber_nnnn isr_name(void)
{
    /* code for interrupt routine which must be in non-bank memory 
} 
#pragma CODE_SEG DEFAULT

/* resume default code which may be banked or not */ 

También puede especificar si las variables van a la página cero o al segmento predeterminado:

#pragma DATA_SEG __SHORT_SEG unsigned int myDirectPageVar1, myVar2;
#pragma DATA_SEG DEFAULT
/* resume placing data in segments requiring a 16-bit address */

También hay formas de especificar si los datos deben inicializarse o no (esta última es la sección .BSS , históricamente llamado Bloque iniciado por símbolo en un programa de ensamblador en la década de 1950 '). Si se inicializa, entonces si está contenido como parte del código del programa (variable) o si se copia en la RAM (variable estática) depende del compilador. Por ejemplo, el compilador GCC coloca todas las variables const en el segmento de código.

Code Segment (or Text segment) - stores only code and maybe const variables
   (see below), read-only
Data segment - stores uninitialized global and static variables, both read-write 
   (BSS (or Block Started by Symbols), and static read-only (see below).
   Uninitialized static variables are actually initialized to 0.

Las variables estáticas y constantes de solo lectura se pueden asignar en el segmento de datos o en el segmento de código, dependiendo del compilador (el estándar C deja esto para el implementador). Si se asigna al segmento de RAM, sus valores de inicialización se almacenan en el código o segmento de texto y se copian al inicio.

El compilador GCC, por ejemplo, coloca todas las variables const en el segmento de código. Otros compiladores solo colocan variables const en el segmento de código si están precedidos por la palabra clave "código" no estándar. La razón de esto, es que requiere más instrucciones para leer datos del segmento de código en comparación con el segmento de datos para muchos microcontroladores. Pero poner una matriz de solo lectura, por ejemplo, en el segmento de datos desperdicia un espacio de RAM valioso, que tal vez sea muy limitado en un microcontrolador. Por ejemplo, el mapa de memoria que se muestra arriba muestra solo un poco más de 8K de RAM regular más 128 bytes de cero o RAM de página directa.

    
respondido por el tcrosley
1

Comprenda cuándo compila un programa hola mundo en su computadora portátil con una llamada de gcc, hay varios pasos, compile en lenguaje ensamblador, luego el ensamblador hace un objeto con eso y luego se llama al vinculador.

El paso de compilación no sabe nada sobre el diseño de la memoria / dirección del destino, lo más probable es que conozca la arquitectura de destino (x86, ARM, MIPS, pdp-11, etc.) pero en este punto no sabe qué dirección El código que está construyendo vivirá. El enlazador es responsable de coser los objetos y construir el binario final. Resuelve las ramas externas y otras conexiones externas a un objeto (variables globales, etc.).

Para una cadena de herramientas (MSVC, Borland, GNU, Keil, etc.) donde ha habido una o muchas versiones a lo largo del tiempo. La forma en que se comunica con ese enlazador cambia lentamente con el tiempo pero hasta cierto punto es la misma. Sin embargo, entre las cadenas de herramientas no hay ninguna razón para esperar ninguna portabilidad, la forma en que le dice a un compilador dónde están sus flashes y dónde están sus espacios sram es diferente de una cadena de herramientas a la siguiente.

Tienes que decirle al toolchain / linker dónde está tu flash si esperas poder construir un binario que realmente funcione. Del mismo modo, si tiene variables que cambian y no pueden caber solo en los registros, entonces necesita un ram y tiene que decirle al vinculador dónde está ese ram. Lo más probable es que su propio código haya especificado directa o indirectamente las direcciones exactas para los periféricos, por lo que normalmente no usa el script de enlace para eso.

A menudo es posible compilar código independiente de la posición, de manera que el programa en sí pueda moverse y ejecutarse como está compilado (direccionamiento relativo frente a direccionamiento directo), pero aún debe colocar el código de inicio en el lugar correcto para iniciar el procesador y todavía tiene que decirle dónde colocar el binario inicialmente y dónde es ram para cualquier variable. Más tarde, puede copiar / mover ese programa a una dirección diferente para ejecutarlo, pero las variables tal vez no. Aún necesita un enlazador incluso con código de posición independiente para microcontroladores y otros trabajos integrados de metal.

El script del enlazador es a menudo su propio lenguaje de programación si lo hace con su propia sintaxis. Puede pasar tanto o tan poco tiempo como desee haciendo tan complicado como desee el script de vinculador. Pero recuerde que las secuencias de comandos del vinculador no suelen ser portátiles de una cadena de herramientas a la siguiente, por lo que si termina cambiando las cadenas de herramientas para el mismo proyecto, su código fuente podría portarse como está, pero la secuencia de comandos del vinculador podría ser una reescritura completa.

    
respondido por el old_timer

Lea otras preguntas en las etiquetas