Hay diferentes segmentos de memoria en los que se colocan varios tipos de datos desde el código C después de la compilación. Es decir: .text
, .data
, .bss
, pila y montón. Solo quiero saber dónde residiría cada uno de estos segmentos en la memoria de un microcontrolador. Es decir, qué datos van en qué tipo de memoria, dado que los tipos de memoria son RAM, NVRAM, ROM, EEPROM, FLASH, etc.
Encontré respuestas a preguntas similares aquí, pero no pudieron explicar cuál sería el contenido de cada uno de los diferentes tipos de memoria.
Cualquier tipo de ayuda es muy apreciada. ¡Gracias por adelantado!
El segmento .text contiene el código real y está programado en la memoria Flash para microcontroladores. Puede haber más de un segmento de texto cuando hay varios bloques no contiguos de memoria Flash; por ejemplo, un vector de inicio y vectores de interrupción ubicados en la parte superior de la memoria, y un código que comienza en 0; o secciones separadas para un programa de arranque y principal.
Hay tres tipos de datos que se pueden asignar de forma externa a una función o procedimiento; el primero son datos no inicializados (históricamente llamados .bss, que también incluyen los 0 datos inicializados), y el segundo es inicializado (no bss) o .data. El nombre "bss" proviene históricamente de "Bloque iniciado por símbolo", utilizado en un ensamblador hace unos 60 años. Ambas áreas están ubicadas en RAM.
A medida que se compila un programa, las variables se asignarán a una de estas dos áreas generales. Durante la etapa de vinculación, todos los elementos de datos se recopilarán juntos. Todas las variables que deben inicializarse tendrán una parte de la memoria del programa reservada para contener los valores iniciales, y justo antes de llamar a main(), las variables se inicializarán, normalmente mediante un módulo llamado crt0. La sección bss se inicializa a todos ceros por el mismo código de inicio.
Con unos pocos microcontroladores, hay instrucciones más cortas que permiten el acceso a la primera página (las primeras 256 ubicaciones, a veces llamadas página 0) de RAM. El compilador de estos procesadores puede reservar una palabra clave como near
para designar variables que se colocarán allí. De manera similar, también hay microcontroladores que solo pueden hacer referencia a ciertas áreas a través de un registro de puntero (que requiere instrucciones adicionales), y dichas variables se designan far
. Finalmente, algunos procesadores pueden direccionar una sección de memoria poco a poco y el compilador tendrá una forma de especificar eso (como la palabra clave bit
).
Por lo tanto, puede haber segmentos adicionales como .nearbss y .neardata, etc., donde se recopilan estas variables.
El tercer tipo de datos externos a una función o procedimiento es como las variables inicializadas, excepto que son de solo lectura y no pueden ser modificados por el programa. En el lenguaje C, estas variables se denotan con la const
palabra clave. Por lo general, se almacenan como parte de la memoria flash del programa. A veces se identifican como parte de un segmento .rodata (datos de solo lectura). En los microcontroladores que usan la arquitectura Harvard , el compilador debe usar instrucciones especiales para acceder a estas variables.
Tanto la pila como el montón se colocan en la RAM. Según la arquitectura del procesador, la pila puede aumentar o disminuir. Si crece, se colocará en la parte inferior de la RAM. Si crece, se colocará al final de la RAM. El montón utilizará la RAM restante no asignada a las variables y crecerá en la dirección opuesta a la pila. El tamaño máximo de la pila y el montón generalmente se puede especificar como parámetros del enlazador.
Las variables colocadas en la pila son cualquier variable definida dentro de una función o procedimiento sin la palabra clave static
. Antes se llamaban variables automáticas ( auto
palabra clave), pero esa palabra clave no es necesaria. Históricamente, auto
existe porque era parte del lenguaje B que precedió a C, y allí se necesitaba. Los parámetros de función también se colocan en la pila.
Aquí hay un diseño típico para RAM (suponiendo que no haya una sección especial de página 0):
Antes de que apareciera la memoria Flash, se usaba EEPROM (memoria de solo lectura programable y borrable eléctricamente) para almacenar el programa y los datos constantes (segmentos .text y .rodata). Ahora solo hay una pequeña cantidad (por ejemplo, de 2 KB a 8 KB bytes) de EEPROM disponible, si es que hay alguna, y generalmente se usa para almacenar datos de configuración u otras pequeñas cantidades de datos que deben conservarse durante un apagado. ciclo. Estos no se declaran como variables en el programa, sino que se escriben utilizando registros especiales en el microcontrolador. La EEPROM también se puede implementar en un chip separado y se puede acceder a ella a través de un bus SPI o I²C.
ROM es esencialmente lo mismo que Flash, excepto que está programado en la fábrica (no programable por el usuario). Se usa solo para dispositivos de muy alto volumen.
NVRAM (RAM no volátil) es una alternativa a EEPROM y generalmente se implementa como un IC externo. La RAM normal se puede considerar no volátil si tiene respaldo de batería; en ese caso no se necesitan métodos de acceso especiales.
Aunque los datos se pueden guardar en Flash, la memoria Flash tiene un número limitado de ciclos de borrado/programación (1000 a 10 000), por lo que en realidad no está diseñada para eso. También requiere que se borren bloques de memoria a la vez, por lo que es un inconveniente actualizar solo unos pocos bytes. Está diseñado para código y variables de solo lectura.
La EEPROM tiene límites mucho más altos en los ciclos de borrado/programación (100 000 a 1 000 000), por lo que es mucho mejor para este propósito. Si hay una EEPROM disponible en el microcontrolador y es lo suficientemente grande, es donde desea guardar los datos no volátiles. Sin embargo, también tendrá que borrar primero en bloques (normalmente 4 KB) antes de escribir.
Si no hay EEPROM o es demasiado pequeño, se necesita un chip externo. Una EEPROM de 32KB cuesta solo 66¢ y se puede borrar/escribir 1,000,000 de veces. Una NVRAM con el mismo número de operaciones de borrado/programación es mucho más costosa (x10). Las NVRAM suelen ser más rápidas para leer que las EEPROM, pero más lentas para escribir. Se pueden escribir en un byte a la vez o en bloques.
Una mejor alternativa a ambos es FRAM (RAM ferroeléctrica), que tiene ciclos de escritura esencialmente infinitos (100 billones) y sin demoras de escritura. Tiene aproximadamente el mismo precio que NVRAM, alrededor de $ 5 por 32 KB.
static
palabra clave. Por lo tanto .bss
, siempre debe inicializarse a cero..data
y .bss
ejecutar, y luego se borren con la inicialización. Por ejemplo, esto nos ha pasado con la SystemCoreClock
variable en los controladores ARM Cortex-M. Tiene que ser puesto en una sección especial para evitar la inicialización.Sistema embebido normal:
Segment Memory Contents
.data RAM Explicitly initialized variables with static storage duration
.bss RAM Zero-initialized variables with static storage duration
.stack RAM Local variables and function call parameters
.heap RAM Dynamically allocated variables (usually not used in embedded systems)
.rodata ROM const variables with static storage duration. String literals.
.text ROM The program. Integer constants. Initializer lists.
Además, suele haber segmentos flash separados para el código de inicio y los vectores de interrupción.
Explicación:
Una variable tiene una duración de almacenamiento estático si se declara como static
o si reside en el ámbito del archivo (a veces se denomina de manera descuidada "global"). C tiene una regla que establece que todas las variables de duración de almacenamiento estático que el programador no inicializó explícitamente deben inicializarse a cero.
Cada variable de duración de almacenamiento estático que se inicializa a cero, implícita o explícitamente, termina en .bss
. Mientras que aquellos que se inicializan explícitamente en un valor distinto de cero terminan en .data
.
Ejemplos:
static int a; // .bss
static int b = 0; // .bss
int c; // .bss
static int d = 1; // .data
int e = 1; // .data
void func (void)
{
static int x; // .bss
static int y = 0; // .bss
static int z = 1; // .data
static int* ptr = NULL; // .bss
}
Tenga en cuenta que una configuración no estándar muy común para los sistemas integrados es tener un "inicio mínimo", lo que significa que el programa omitirá toda inicialización de objetos con duración de almacenamiento estático. Por lo tanto, sería prudente nunca escribir programas que se basen en los valores de inicialización de dichas variables, sino que los establezca en "tiempo de ejecución" antes de que se utilicen por primera vez.
Ejemplos de los otros segmentos:
const int a = 0; // .rodata
const int b; // .rodata (nonsense code but C allows it, unlike C++)
static const int c = 0; // .rodata
static const int d = 1; // .rodata
void func (int param) // .stack
{
int e; // .stack
int f=0; // .stack
int g=1; // .stack
const int h=param; // .stack
static const int i=1; // .rodata, static storage duration
char* ptr; // ptr goes to .stack
ptr = malloc(1); // pointed-at memory goes to .heap
}
Las variables que pueden ir a la pila a menudo pueden terminar en los registros de la CPU durante la optimización. Como regla general, cualquier variable que no tenga su dirección tomada puede colocarse en un registro de la CPU.
Tenga en cuenta que los punteros son un poco más complejos que otras variables, ya que permiten dos tipos diferentes de const
, dependiendo de si los datos apuntados deben ser de solo lectura o si el puntero en sí debe serlo. Es muy importante saber la diferencia para que sus punteros no terminen en la RAM por accidente, cuando usted quería que estuvieran en flash.
int* j=0; // .bss
const int* k=0; // .bss, non-const pointer to const data
int* const l=0; // .rodata, const pointer to non-const data
const int* const m=0; // .rodata, const pointer to const data
void (*fptr1)(void); // .bss
void (*const fptr2)(void); // .rodata
void (const* fptr3)(void); // invalid, doesn't make sense since functions can't be modified
En el caso de constantes enteras, listas de inicializadores, cadenas literales, etc., pueden terminar en .text o .rodata según el compilador. Probablemente, terminan como:
#define n 0 // .text
int o = 5; // 5 goes to .text (part of the instruction)
int p[] = {1,2,3}; // {1,2,3} goes to .text
char q[] = "hello"; // "hello" goes to .rodata
.data
normalmente tiene una llamada dirección de carga en flash, donde se almacenan los valores iniciales, y una llamada dirección virtual (no realmente virtual en un microcontrolador) en RAM, donde la variable se almacena durante la ejecución. Antes de que main
comience, los valores iniciales se copian desde la dirección de carga a la dirección virtual. No necesita almacenar ceros, por lo .bss
que no necesita almacenar sus valores iniciales. sourceware.org/binutils/docs/ld/….rodata
? Me doy cuenta de que depende del compilador, pero ¿por qué un compilador de ejemplo podría elegir .text
enteros pero .rodata
literales de cadena?.text
porque se cargan en el propio código de máquina. Esta parte depende más del compilador/enlazador que el resto.non-volatile
en los comentarios, pero ninguno a volatile
. ¿Qué sección de memoria contendría las variables modificadas con volatile
?volatile
palabra clave en el lenguaje C significa variable que se puede cambiar en cualquier momento desde fuentes externas. Cualquier variable puede ser volatile
calificada sin importar su tipo, vinculación o lugar donde esté asignada.int o = 5 ;
debería ir a .text sin embargo? ¿No va a .data o .stack dependiendo de global/static o auto? ¿O se basa en cómo se usó esta variable en el código, lo que la convirtió en una 'constante'?5
es probable que esté integrado en la memoria de instrucciones del programa en .text
. O menos probable, se lee desde la ROM.Si bien cualquier dato puede ir a cualquier memoria que elija el programador, generalmente el sistema funciona mejor (y está diseñado para usarse) cuando el perfil de uso de los datos coincide con los perfiles de lectura/escritura de la memoria.
Por ejemplo, el código del programa es WFRM (escriba pocos, lea muchos), y hay mucho. Esto encaja muy bien con FLASH. ROM OTOH es W una vez RM.
La pila y el montón son pequeños, con muchas lecturas y escrituras. Eso encajaría mejor con la RAM.
La EEPROM no se adaptaría bien a ninguno de esos usos, pero sí se adapta al perfil de pequeñas cantidades de datos persistentes en los encendidos, por lo que los datos de inicialización específicos del usuario y quizás los resultados de registro.
Lundin
Sean Houlihane
crosley
Sean Houlihane
viejo contador de tiempo