¿Qué reside en los diferentes tipos de memoria de un microcontrolador?

25

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 a qué tipo de memoria, dado que los tipos de memoria son RAM, NVRAM, ROM, EEPROM, FLASH, etc.

He encontrado 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!

Soju T Varghese
fuente
1
NVRAM, ROM, EEPROM y Flash son básicamente nombres diferentes para la misma cosa: memoria no volátil.
Lundin
Ligeramente tangencial a la pregunta, pero el código puede (excepcionalmente) existir en cualquiera de estos, especialmente si considera el uso de parches o calibración. A veces se moverá antes de la ejecución, a veces se ejecutará en su lugar.
Sean Houlihane
@SeanHoulihane El OP pregunta por los microcontroladores, que casi siempre se ejecutan fuera de Flash (calificaste tu comentario con excepcional). Los microprocesadores con MB de RAM externa que ejecutan Linux, por ejemplo, copiarían sus programas en la RAM para ejecutarlos, tal vez desde una tarjeta SD que actúe como un volumen montable.
tcrosley
@tcrosley Ahora hay microcontroladores con TCM, y a veces los microcontroladores son parte de un SoC más grande. También sospecho que hay casos como dispositivos eMMC en los que el mcu arranca para ejecutarse desde la RAM desde su propio almacenamiento (basado en la memoria de algunos ladrillos de teléfonos de hace un par de años). Estoy de acuerdo, no es una respuesta directa, pero creo que es muy relevante que las asignaciones típicas no sean de ninguna manera reglas estrictas.
Sean Houlihane
1
no hay reglas para conectar una cosa con otra, seguro que las cosas de solo lectura como texto y rodata idealmente querrían ir en flash, pero los datos y el desplazamiento y el tamaño de .bss también van allí (y luego se copian por el código de arranque). estos términos (.text, etc.) no tienen nada que ver con los microcontroladores, es una cosa del compilador / cadena de herramientas que se aplica a todos los objetivos de los compiladores / cadenas de herramientas. Al final del día, el programador decide a dónde van las cosas e informa a la cadena de herramientas a través de un script de enlace generalmente.
old_timer

Respuestas:

38

.texto

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 múltiples 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.

.bss y .data

Hay tres tipos de datos que pueden asignarse de forma externa a una función o procedimiento; el primero es información no inicializada (históricamente llamada .bss, que también incluye los datos inicializados 0), y el segundo es inicializado (no bss), o .data. El nombre "bss" históricamente proviene de "Block Started by Symbol", 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 mantener los valores iniciales, y justo antes de que se llame a main (), las variables se inicializarán, generalmente por un módulo llamado crt0. La sección bss se inicializa en todos los 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 (primeras 256 ubicaciones, a veces llamada página 0) de RAM. El compilador para estos procesadores puede reservar una palabra clave como nearpara designar variables que se colocarán allí. Del mismo modo, 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 tales variables se designan far. Finalmente, algunos procesadores pueden abordar una sección de memoria bit a bit 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.

.rodata

El tercer tipo de datos externos a una función o procedimiento es como las variables inicializadas, excepto que es de solo lectura y el programa no puede modificarlo. En el lenguaje C, estas variables se denotan usando la constpalabra clave. Generalmente 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.

apilar y apilar

La pila y el montón se colocan en RAM. Dependiendo de la arquitectura del procesador, la pila puede crecer o disminuir. Si crece, se colocará en la parte inferior de la RAM. Si crece hacia abajo, se colocará al final de la RAM. El montón usará la RAM restante no asignada a las variables, y crecerá en la dirección opuesta de la pila. El tamaño máximo de la pila y el montón generalmente se puede especificar como parámetros de enlace.

Las variables colocadas en la pila son cualquier variable definida dentro de una función o procedimiento sin la palabra clave static. Alguna vez se llamaron variables automáticas ( autopalabra clave), pero esa palabra clave no es necesaria. Históricamente, autoexiste porque era parte del lenguaje B que precedió a C, y allí era necesario. 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 la página 0):

ingrese la descripción de la imagen aquí

EEPROM, ROM y NVRAM

Antes de que apareciera la memoria Flash, EEPROM (memoria de solo lectura programable y borrable eléctricamente) se usaba para almacenar el programa y los datos constantes (segmentos .text y .rodata). Ahora solo hay una pequeña cantidad (por ejemplo, 2KB a 8KB 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 retenerse durante un apagado. ciclo. No se declaran como variables en el programa, sino que se escriben utilizando registros especiales en el microcontrolador. La EEPROM también puede implementarse en un chip separado y acceder a través de un bus SPI o I²C.

La ROM es esencialmente lo mismo que Flash, excepto que se programa 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 puede considerarse no volátil si está respaldada por 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 / programa (1000 a 10,000), por lo que no está realmente diseñada para eso. También requiere que los bloques de memoria se borren de una vez, por lo que es inconveniente actualizar solo unos pocos bytes. Está destinado a código y variables de solo lectura.

EEPROM tiene límites mucho más altos en ciclos de borrado / programa (100,000 a 1,000,000) por lo que es mucho mejor para este propósito. Si hay 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 los bloques (normalmente 4KB) antes de escribir.

Si no hay EEPROM o es demasiado pequeño, se necesita un chip externo. Una EEPROM de 32 KB tiene solo 66 ¢ y puede borrarse / escribirse hasta 1,000,000 de veces. Una NVRAM con la misma cantidad de operaciones de borrado / programa es mucho más costosa (x10) Las NVRAM son típicamente 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 retrasos de escritura. Tiene el mismo precio que NVRAM, alrededor de $ 5 por 32 KB.

tcrosley
fuente
Esa fue una pieza de información realmente útil. ¿Podría por favor proporcionar una referencia a su explicación? Como libros de texto o revistas, en caso de que quiera leer más sobre esto ...
Soju T Varghese
Una pregunta más, ¿podría dar una idea de las ventajas o desventajas de una memoria sobre la otra (EEPROM, NVRAM y FLASH)?
Soju T Varghese
Buena respuesta. Publiqué uno complementario que se enfoca más en detalle en lo que ocurre específicamente en el lenguaje C.
Lundin
1
@SojuTVarghese Actualicé mi respuesta e incluí también información sobre FRAM.
tcrosley
@Lundin utilizamos los mismos nombres de segmento (por ejemplo, .rodata), por lo que las respuestas se complementan muy bien.
tcrosley
21

Sistema integrado 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, generalmente hay 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 statico si reside en el alcance del archivo (a veces llamado descuidadamente "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 embebidos es tener un "inicio mínimo", lo que significa que el programa omitirá toda inicialización de objetos con una duración de almacenamiento estático. Por lo tanto, sería aconsejable no escribir nunca programas que se basen en los valores de inicialización de tales variables, sino que los establezcan en "tiempo de ejecución" antes de que se usen 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 en la pila a menudo pueden terminar en registros de CPU durante la optimización. Como regla general, cualquier variable que no tenga su dirección tomada se puede colocar en un registro de 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í debería serlo. Es muy importante saber la diferencia para que sus punteros no terminen en RAM por accidente, cuando 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 las constantes enteras, las listas de inicializadores, los literales de cadena, etc., pueden terminar en .text o .rodata dependiendo del 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
Lundin
fuente
No entiendo, en su primer código de ejemplo, por qué 'static int b = 0;' entra en .bss y por qué 'static int d = 1;' entra en .data ..? Según tengo entendido, ambas son variables estáticas que han sido inicializadas por el programador ... entonces, ¿qué hace la diferencia? @Lundin
Soju T Varghese
2
@SojuTVarghese Debido a que los datos .bss se inicializan a 0 como un bloque; Los valores específicos como d = 1 deben almacenarse en flash.
tcrosley
@SojuTVarghese Agregó algunas aclaraciones.
Lundin
@Lundin Además, desde su último código de ejemplo, ¿significa que todos los valores inicializados van a .text o .rodata y sus respectivas variables solo van a .bss o .data? Si es así, ¿cómo se asignan entre sí las variables y sus valores correspondientes (es decir, entre los segmentos .bss / .data y .text / .rodata)?
Soju T Varghese
@SojuTVarghese No, .datageneralmente 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 maincomenzar, los valores iniciales se copian de la dirección de carga a la dirección virtual. No necesita almacenar ceros, por lo .bssque no necesita almacenar sus valores iniciales. sourceware.org/binutils/docs/ld/…
starblue
1

Si bien cualquier información puede ir a cualquier memoria que elija el programador, generalmente el sistema funciona mejor (y está destinado a ser utilizado) donde el perfil de uso de los datos se corresponde con los perfiles de lectura / escritura de la memoria.

Por ejemplo, el código del programa es WFRM (escriba pocos, lea muchos), y hay muchos. Esto se adapta muy bien a 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 RAM.

EEPROM no se adaptaría bien a ninguno de esos usos, pero sí se adapta al perfil de pequeñas cantidades de datos perisistentes en los arranques, por lo que los datos de inicialización específicos del usuario y tal vez los resultados de registro.

Neil_UK
fuente