Qu'est-ce qui réside dans les différents types de mémoire d'un microcontrôleur?

25

Il existe différents segments de mémoire dans lesquels divers types de données sont insérés à partir du code C après la compilation. Ie: .text, .data, .bss, pile et tas. Je veux juste savoir où chacun de ces segments résiderait dans une mémoire de microcontrôleur. Autrement dit, quelles données vont dans quel type de mémoire, étant donné que les types de mémoire sont RAM, NVRAM, ROM, EEPROM, FLASH, etc.

J'ai trouvé des réponses à des questions similaires ici, mais elles n'ont pas expliqué quel serait le contenu de chacun des différents types de mémoire.

Toute aide est très appréciée. Merci d'avance!

Soju T Varghese
la source
1
NVRAM, ROM, EEPROM et Flash sont à peu près des noms différents pour la même chose: mémoire non volatile.
Lundin
Légèrement tangent à la question, mais le code peut (exceptionnellement) exister dans la plupart de ceux-ci, en particulier si vous envisagez des utilisations de correctifs ou d'étalonnage. Parfois, il sera déplacé avant l'exécution, parfois exécuté sur place.
Sean Houlihane
@SeanHoulihane L'OP pose des questions sur les microcontrôleurs, qui s'exécutent presque toujours en Flash (vous avez qualifié votre commentaire d'exceptionnel). Les microprocesseurs avec des Mo de RAM externe exécutant Linux par exemple, copieraient leurs programmes dans la RAM pour les exécuter, peut-être sur une carte SD agissant comme un volume montable.
tcrosley
@tcrosley Il existe maintenant des microcontrôleurs avec TCM, et parfois les microcontrôleurs font partie d'un SoC plus grand. Je soupçonne également qu'il existe des cas comme les appareils eMMC où le mcu démarre lui-même pour fonctionner à partir de la RAM à partir de son propre stockage (basé sur la mémoire de certains téléphones durs à briser il y a quelques années). Je suis d'accord, ce n'est pas une réponse directe - mais je pense qu'il est très pertinent que les mappages typiques ne soient en aucune façon des règles strictes.
Sean Houlihane
1
il n'y a pas de règles pour connecter une chose à une autre, bien sûr, les éléments en lecture seule comme le texte et les rodata voudraient idéalement aller en flash, mais les données .et le décalage et la taille de .bss y vont aussi code bootstrap). ces termes (.text, etc.) n'ont rien à voir avec les microcontrôleurs, c'est une chose compilateur / chaîne d'outils qui s'applique à toutes les cibles des compilateurs / chaînes d'outils. à la fin de la journée, le programmeur décide où vont les choses et informe généralement la chaîne d'outils via un script de l'éditeur de liens.
old_timer

Réponses:

38

.texte

Le segment .text contient le code réel et est programmé dans la mémoire Flash pour les microcontrôleurs. Il peut y avoir plusieurs segments de texte lorsqu'il existe plusieurs blocs de mémoire Flash non contigus; par exemple un vecteur de démarrage et des vecteurs d'interruption situés en haut de la mémoire et un code commençant à 0; ou des sections distinctes pour un programme d'amorçage et principal.

.bss et .data

Il existe trois types de données qui peuvent être allouées à l'extérieur d'une fonction ou d'une procédure; la première est des données non initialisées (historiquement appelées .bss, qui incluent également les données 0 initialisées), et la seconde est initialisée (non bss), ou .data. Le nom "bss" vient historiquement de "Block Started by Symbol", utilisé dans un assembleur il y a environ 60 ans. Ces deux zones sont situées dans la RAM.

Lors de la compilation d'un programme, des variables seront affectées à l'un de ces deux domaines généraux. Au cours de l'étape de liaison, tous les éléments de données seront collectés ensemble. Toutes les variables qui doivent être initialisées auront une partie de la mémoire du programme réservée pour contenir les valeurs initiales, et juste avant que main () soit appelée, les variables seront initialisées, typiquement par un module appelé crt0. La section bss est initialisée à tous les zéros par le même code de démarrage.

Avec quelques microcontrôleurs, il existe des instructions plus courtes qui permettent d'accéder à la première page (256 premiers emplacements, parfois appelée page 0) de RAM. Le compilateur de ces processeurs peut réserver un mot-clé comme nearpour désigner les variables à y placer. De même, il existe également des microcontrôleurs qui ne peuvent référencer que certaines zones via un registre de pointeurs (nécessitant des instructions supplémentaires), et de telles variables sont désignées far. Enfin, certains processeurs peuvent traiter une partie de la mémoire bit par bit et le compilateur aura un moyen de le spécifier (comme le mot-clé bit).

Il peut donc y avoir des segments supplémentaires comme .nearbss et .neardata, etc., où ces variables sont collectées.

.rodata

Le troisième type de données externes à une fonction ou une procédure est similaire aux variables initialisées, sauf qu'il est en lecture seule et ne peut pas être modifié par le programme. En langage C, ces variables sont désignées par le constmot - clé. Ils sont généralement stockés dans le cadre de la mémoire flash du programme. Parfois, ils sont identifiés comme faisant partie d'un segment .rodata (données en lecture seule). Sur les microcontrôleurs utilisant l'architecture Harvard , le compilateur doit utiliser des instructions spéciales pour accéder à ces variables.

empiler et tas

La pile et le tas sont tous deux placés dans la RAM. Selon l'architecture du processeur, la pile peut augmenter ou diminuer. S'il grandit, il sera placé au bas de la RAM. S'il croît, il sera placé à la fin de la RAM. Le tas utilisera la RAM restante non allouée aux variables et augmentera la direction opposée de la pile. La taille maximale de la pile et du tas peut généralement être spécifiée en tant que paramètres de l'éditeur de liens.

Les variables placées sur la pile sont toutes les variables définies dans une fonction ou une procédure sans le mot clé static. Elles étaient autrefois appelées variables automatiques ( automot-clé), mais ce mot-clé n'est pas nécessaire. Historiquement, autoexiste parce qu'il faisait partie du langage B qui a précédé C, et là, il était nécessaire. Les paramètres de fonction sont également placés sur la pile.

Voici une disposition typique pour la RAM (en supposant qu'aucune section spéciale de la page 0):

entrez la description de l'image ici

EEPROM, ROM et NVRAM

Avant l'arrivée de la mémoire Flash, l'EEPROM (mémoire morte programmable effaçable électriquement) était utilisée pour stocker le programme et les données const (segments .text et .rodata). Maintenant, il n'y a qu'une petite quantité (par exemple 2 Ko à 8 Ko d'octets) d'EEPROM disponible, le cas échéant, et elle est généralement utilisée pour stocker des données de configuration ou d'autres petites quantités de données qui doivent être conservées lors d'une mise hors tension. cycle. Celles-ci ne sont pas déclarées comme variables dans le programme, mais sont écrites à la place à l'aide de registres spéciaux dans le microcontrôleur. L'EEPROM peut également être implémentée dans une puce séparée et accessible via un bus SPI ou I²C.

La ROM est essentiellement la même que Flash, sauf qu'elle est programmée en usine (non programmable par l'utilisateur). Il est utilisé uniquement pour les appareils à très haut volume.

La NVRAM (RAM non volatile) est une alternative à l'EEPROM et est généralement implémentée en tant que CI externe. La RAM régulière peut être considérée comme non volatile si elle est sauvegardée sur batterie; dans ce cas, aucune méthode d'accès spéciale n'est nécessaire.

Bien que les données puissent être enregistrées dans Flash, la mémoire Flash a un nombre limité de cycles d'effacement / programme (1000 à 10 000), elle n'est donc pas vraiment conçue pour cela. Il nécessite également l'effacement simultané de blocs de mémoire, il n'est donc pas pratique de mettre à jour quelques octets. Il est destiné au code et aux variables en lecture seule.

L'EEPROM a des limites beaucoup plus élevées sur les cycles d'effacement / programme (100 000 à 1 000 000), c'est donc beaucoup mieux à cet effet. S'il y a une EEPROM disponible sur le microcontrôleur et qu'elle est suffisamment grande, c'est là que vous souhaitez enregistrer des données non volatiles. Cependant, vous devrez également effacer les blocs en premier (généralement 4 Ko) avant d'écrire.

S'il n'y a pas d'EEPROM ou qu'elle est trop petite, une puce externe est nécessaire. Une EEPROM de 32 Ko ne coûte que 66 ¢ et peut être effacée / écrite à 1 000 000 de fois. Une NVRAM avec le même nombre d'opérations d'effacement / programme est beaucoup plus chère (x10). Les NVRAM sont généralement plus rapides à lire que les EEPROM, mais plus lentes à écrire. Ils peuvent être écrits sur un octet à la fois ou en blocs.

Une meilleure alternative à ces deux est la FRAM (RAM ferroélectrique), qui a des cycles d'écriture essentiellement infinis (100 billions) et aucun retard d'écriture. C'est à peu près le même prix que la NVRAM, environ 5 $ pour 32 Ko.

tcrosley
la source
C'était une information vraiment utile. Pourriez-vous s'il vous plaît fournir une référence à votre explication? Comme des manuels ou des revues, au cas où je voudrais en savoir plus à ce sujet ..?
Soju T Varghese
une dernière question, pourriez-vous s'il vous plaît donner une idée des avantages ou des inconvénients d'une mémoire par rapport à l'autre (EEPROM, NVRAM et FLASH)?
Soju T Varghese
Bonne réponse. J'en ai posté un complémentaire qui se concentre plus en détail sur ce qui se passe où dans le langage C.
Lundin
1
@SojuTVarghese J'ai mis à jour ma réponse et inclus également des informations sur FRAM.
tcrosley
@Lundin, nous avons utilisé les mêmes noms de segment (par exemple .rodata) afin que les réponses se complètent bien.
tcrosley
21

Système embarqué 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.

De plus, il existe généralement des segments flash séparés pour le code de démarrage et les vecteurs d'interruption.


Explication:

Une variable a une durée de stockage statique si elle est déclarée en tant que staticou si elle réside dans la portée du fichier (parfois appelée bâclée "globale"). C a une règle stipulant que toutes les variables de durée de stockage statique que le programmeur n'a pas initialisées explicitement doivent être initialisées à zéro.

Chaque variable de durée de stockage statique initialisée à zéro, implicitement ou explicitement, se retrouve dans .bss. Tandis que ceux qui sont explicitement initialisés à une valeur non nulle se retrouvent dans .data.

Exemples:

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
}

Veuillez garder à l'esprit qu'une configuration non standard très courante pour les systèmes embarqués consiste à avoir un "démarrage minimal", ce qui signifie que le programme sautera toute initialisation d'objets avec une durée de stockage statique. Par conséquent, il peut être judicieux de ne jamais écrire de programmes qui s'appuient sur les valeurs d'initialisation de ces variables, mais les définissent plutôt en "exécution" avant de les utiliser pour la première fois.

Exemples des autres segments:

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
}

Les variables pouvant aller sur la pile peuvent souvent se retrouver dans les registres du processeur lors de l'optimisation. En règle générale, toute variable dont l'adresse n'est pas prise peut être placée dans un registre CPU.

Notez que les pointeurs sont un peu plus complexes que les autres variables, car ils permettent deux types différents de const, selon que les données pointées doivent être en lecture seule ou si le pointeur lui-même doit l'être. Il est très important de connaître la différence pour que vos pointeurs ne se retrouvent pas dans la RAM par accident, lorsque vous vouliez qu'ils soient 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

Dans le cas des constantes entières, des listes d'initialisation, des littéraux de chaîne, etc., ils peuvent se retrouver soit en .text soit en .rodata selon le compilateur. Probablement, ils finissent par:

#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
la source
Je ne comprends pas, dans votre premier exemple de code, pourquoi 'static int b = 0;' va dans .bss et pourquoi 'static int d = 1;' entre en .data ..? À ma connaissance, les deux sont des variables statiques qui ont été initialisées par le programmeur .. alors qu'est-ce qui fait la différence? @Lundin
Soju T Varghese
2
@SojuTVarghese Parce que les données .bss sont initialisées à 0 sous forme de bloc; des valeurs spécifiques comme d = 1 doivent être stockées en flash.
tcrosley
@SojuTVarghese Ajout d'une clarification.
Lundin
@Lundin De plus, d'après votre dernier exemple de code, cela signifie-t-il que toutes les valeurs initialisées vont dans .text ou .rodata et que leurs variables respectives seules vont dans .bss ou .data? Dans l'affirmative, comment les variables et leurs valeurs correspondantes sont-elles mappées les unes aux autres (c'est-à-dire entre les segments .bss / .data et .text / .rodata)?
Soju T Varghese
@SojuTVarghese Non, a .datagénéralement une soi-disant adresse de chargement en flash, où les valeurs initiales sont stockées, et une soi-disant adresse virtuelle (pas vraiment virtuelle dans un microcontrôleur) en RAM, où la variable est stockée pendant l'exécution. Avant le maindémarrage, les valeurs initiales sont copiées de l'adresse de chargement vers l'adresse virtuelle. Vous n'avez pas besoin de stocker des zéros, donc .bsspas besoin de stocker ses valeurs initiales. sourceware.org/binutils/docs/ld/…
starblue
1

Alors que toutes les données peuvent entrer dans n'importe quelle mémoire choisie par le programmeur, le système fonctionne généralement mieux (et est destiné à être utilisé) lorsque le profil d'utilisation des données est mis en correspondance avec les profils de lecture / écriture de la mémoire.

Par exemple, le code du programme est WFRM (en écrire quelques-uns en lire plusieurs), et il y en a beaucoup. Cela convient parfaitement à FLASH. ROM OTOH est W une fois RM.

La pile et le tas sont petits, avec beaucoup de lectures et d'écritures. Cela conviendrait le mieux à la RAM.

L'EEPROM ne conviendrait pas à l'une ou l'autre de ces utilisations, mais elle convient au profil de petites quantités de données qui persistent à travers les mises sous tension, donc des données d'initialisation spécifiques à l'utilisateur et peut-être des résultats de journalisation.

Neil_UK
la source