Was steckt in den verschiedenen Speichertypen eines Mikrocontrollers?

25

Es gibt verschiedene Speichersegmente, in die nach der Kompilierung verschiedene Datentypen aus C-Code eingefügt werden. Dh: .text, .data, .bss, Stack und Heap. Ich möchte nur wissen, wo sich jedes dieser Segmente in einem Mikrocontroller-Speicher befinden würde. Das heißt, welche Daten gehen in welchen Speichertyp, vorausgesetzt, die Speichertypen sind RAM, NVRAM, ROM, EEPROM, FLASH usw.

Ich habe hier Antworten auf ähnliche Fragen gefunden, aber sie haben nicht erklärt, was der Inhalt jedes der verschiedenen Speichertypen sein würde.

Jede Art von Hilfe wird sehr geschätzt. Danke im Voraus!

Soju T Varghese
quelle
1
NVRAM, ROM, EEPROM und Flash sind so ziemlich unterschiedliche Namen für dasselbe: nichtflüchtiger Speicher.
Lundin
Etwas tangential zu der Frage, aber Code kann (ausnahmsweise) in so ziemlich jedem dieser Bereiche vorhanden sein, insbesondere wenn Sie Patch- oder Kalibrierungsanwendungen in Betracht ziehen. Manchmal wird es vor der Ausführung verschoben, manchmal an Ort und Stelle ausgeführt.
Sean Houlihane
@SeanHoulihane Das OP fragt nach Mikrocontrollern, die fast immer in Flash ausgeführt werden (Sie haben Ihren Kommentar mit außergewöhnlich bewertet ). Micro - Prozessoren mit MBs von externem RAM Linux zum Beispiel ausgeführt wird , würden ihre Programme in den Arbeitsspeicher kopiert werden, um sie auszuführen, vielleicht aus einer SD - Karte als montierbare Volumen handeln.
Tcrosley
@tcrosley Es gibt jetzt Mikrocontroller mit TCM, und manchmal sind Mikrocontroller Teil eines größeren SoC. Ich vermute auch, dass es Fälle wie eMMC-Geräte gibt, in denen sich das mcu bootstrappt, um aus dem RAM aus seinem eigenen Speicher heraus zu laufen (basierend auf dem Speicher einiger Telefon-Hard-Bricks von vor ein paar Jahren). Ich stimme zu, es ist keine direkte Antwort - aber ich finde es sehr relevant, dass die typischen Zuordnungen keine harten Regeln sind.
Sean Houlihane
1
Es gibt keine Regeln, um eine Sache mit einer anderen zu verbinden. Sicher, dass die schreibgeschützten Inhalte wie Text und Rodata im Idealfall in Flash gespeichert werden sollen, aber die .data und der Offset und die Größe von .bss werden ebenfalls dort gespeichert (und dann von der kopiert) Bootstrap-Code). Diese Begriffe (.text usw.) haben nichts mit Mikrocontrollern zu tun. Es handelt sich um eine Compiler- / Toolchain-Sache, die für alle Ziele von Compilern / Toolchains gilt. Am Ende des Tages entscheidet der Programmierer, wohin die Dinge gehen, und informiert die Toolchain in der Regel über ein Linker-Skript.
old_timer

Antworten:

38

.Text

Das .text-Segment enthält den eigentlichen Code und ist für Mikrocontroller in den Flash-Speicher programmiert. Es kann mehr als ein Textsegment geben, wenn mehrere, nicht zusammenhängende Flash-Speicherblöcke vorhanden sind. zB ein Startvektor und Interruptvektoren, die sich oben im Speicher befinden, und Code, der bei 0 beginnt; oder separate Abschnitte für einen Bootstrap und ein Hauptprogramm.

.bss und .data

Es gibt drei Arten von Daten, die einer Funktion oder Prozedur extern zugewiesen werden können. Das erste sind nicht initialisierte Daten (historisch .bss genannt, die auch die mit 0 initialisierten Daten enthalten), und das zweite ist initialisiert (nicht-bss) oder .data. Der Name "bss" kommt historisch von "Block Started by Symbol", der vor etwa 60 Jahren in einem Assembler verwendet wurde. Beide Bereiche befinden sich im RAM.

Beim Kompilieren eines Programms werden Variablen einem dieser beiden allgemeinen Bereiche zugewiesen. Während der Verknüpfungsphase werden alle Datenelemente zusammen erfasst. Für alle Variablen, die initialisiert werden müssen, wird ein Teil des Programmspeichers zur Aufbewahrung der Anfangswerte reserviert. Kurz bevor main () aufgerufen wird, werden die Variablen initialisiert, in der Regel von einem Modul mit dem Namen crt0. Der Abschnitt bss wird durch denselben Startcode mit allen Nullen initialisiert.

Bei einigen Mikrocontrollern gibt es kürzere Anweisungen, die den Zugriff auf die erste Seite (die ersten 256 Stellen, manchmal auch Seite 0 genannt) des RAM ermöglichen. Der Compiler für diese Prozessoren kann ein Schlüsselwort reservieren near, um Variablen zu bestimmen, die dort platziert werden sollen. In ähnlicher Weise gibt es auch Mikrocontroller, die nur über ein Zeigerregister auf bestimmte Bereiche verweisen können (was zusätzliche Anweisungen erfordert), und solche Variablen werden bezeichnet far. Schließlich können einige Prozessoren einen Abschnitt des Speichers Bit für Bit adressieren, und der Compiler kann dies angeben (z. B. das Schlüsselwort bit).

Es kann also zusätzliche Segmente wie .nearbss und .neardata usw. geben, in denen diese Variablen gesammelt werden.

.rodata

Der dritte Datentyp außerhalb einer Funktion oder Prozedur entspricht den initialisierten Variablen, ist jedoch schreibgeschützt und kann vom Programm nicht geändert werden. In der Sprache C werden diese Variablen mit dem constSchlüsselwort bezeichnet. Sie werden normalerweise als Teil des Programm-Flash-Speichers gespeichert. Manchmal werden sie als Teil eines .rodata-Segments (Nur-Lese-Daten) identifiziert. Bei Mikrocontrollern, die die Harvard-Architektur verwenden , muss der Compiler spezielle Anweisungen verwenden, um auf diese Variablen zuzugreifen.

stapeln und haufen

Der Stack und der Heap befinden sich beide im RAM. Abhängig von der Architektur des Prozessors kann der Stapel größer oder kleiner werden. Wenn es wächst, wird es am unteren Rand des RAM platziert. Wenn es nachlässt, wird es am Ende des Arbeitsspeichers platziert. Der Heap verwendet den verbleibenden RAM, der nicht Variablen zugewiesen ist, und vergrößert die entgegengesetzte Richtung des Stapels. Die maximale Größe von Stack und Heap kann normalerweise als Linker-Parameter angegeben werden.

Variablen, die auf dem Stapel abgelegt werden, sind alle Variablen, die in einer Funktion oder Prozedur ohne das Schlüsselwort definiert sind static. Sie wurden einmal als automatische Variablen ( autoSchlüsselwort) bezeichnet, aber dieses Schlüsselwort wird nicht benötigt. Historisch gesehen autoexistiert es , weil es Teil der B-Sprache war, die C vorausging und dort gebraucht wurde. Funktionsparameter werden ebenfalls auf dem Stapel abgelegt.

Hier ist ein typisches Layout für RAM (vorausgesetzt, es gibt keinen speziellen Abschnitt für Seite 0):

Bildbeschreibung hier eingeben

EEPROM, ROM und NVRAM

Vor der Einführung des Flash-Speichers wurde EEPROM (elektrisch löschbarer programmierbarer Nur-Lese-Speicher) zum Speichern der Programm- und Konstantendaten (.text- und .rodata-Segmente) verwendet. Jetzt ist nur noch eine kleine Menge (z. B. 2 KB bis 8 KB Bytes) EEPROM verfügbar, und es wird normalerweise zum Speichern von Konfigurationsdaten oder anderen kleinen Datenmengen verwendet, die beim Herunterfahren beibehalten werden müssen Zyklus. Diese werden im Programm nicht als Variablen deklariert, sondern über spezielle Register im Mikrocontroller beschrieben. EEPROM kann auch in einem separaten Chip implementiert und über einen SPI- oder I²C-Bus angesprochen werden.

Das ROM ist im Wesentlichen dasselbe wie Flash, außer dass es im Werk programmiert wurde (nicht vom Benutzer programmierbar). Es wird nur für Geräte mit sehr hoher Lautstärke verwendet.

NVRAM (nichtflüchtiger RAM) ist eine Alternative zum EEPROM und wird normalerweise als externer IC implementiert. Normaler RAM kann als nicht flüchtig betrachtet werden, wenn er batteriegepuffert ist. In diesem Fall sind keine speziellen Zugriffsmethoden erforderlich.

Obwohl Daten in Flash gespeichert werden können, hat der Flash-Speicher eine begrenzte Anzahl von Lösch- / Programmzyklen (1000 bis 10.000), sodass er nicht wirklich dafür ausgelegt ist. Außerdem müssen Speicherblöcke sofort gelöscht werden, sodass es unpraktisch ist, nur wenige Bytes zu aktualisieren. Es ist für Code- und Nur-Lese-Variablen vorgesehen.

EEPROM hat viel höhere Grenzen für Lösch- / Programmierzyklen (100.000 bis 1.000.000), daher ist es für diesen Zweck viel besser. Wenn auf dem Mikrocontroller ein EEPROM verfügbar und groß genug ist, möchten Sie dort nichtflüchtige Daten speichern. Sie müssen jedoch auch zuerst in Blöcken (normalerweise 4 KB) löschen, bevor Sie schreiben können.

Wenn kein EEPROM vorhanden oder zu klein ist, wird ein externer Chip benötigt. Ein 32-KB-EEPROM ist nur 66 ¢ groß und kann 1.000.000-mal gelöscht / beschrieben werden. Ein NVRAM mit der gleichen Anzahl von Lösch- / Programmiervorgängen ist viel teurer (x10). NVRAMs sind normalerweise schneller zum Lesen als EEPROMs, aber langsamer zum Schreiben. Sie können entweder byteweise oder blockweise geschrieben werden.

Eine bessere Alternative zu beiden ist FRAM (ferroelektrischer RAM), der im Wesentlichen unendliche Schreibzyklen (100 Billionen) und keine Schreibverzögerungen aufweist. Es ist ungefähr der gleiche Preis wie NVRAM, ungefähr 5 US-Dollar für 32 KB.

Tcrosley
quelle
Das war eine wirklich nützliche Information. Könnten Sie bitte einen Verweis auf Ihre Erklärung geben? Wie Lehrbücher oder Zeitschriften, falls ich mehr darüber lesen möchte ..?
Soju T Varghese
eine weitere Frage, können Sie bitte eine Vorstellung von den Vor- oder Nachteilen eines Speichers gegenüber dem anderen (EEPROM, NVRAM und FLASH) geben?
Soju T Varghese
Gute Antwort. Ich habe einen ergänzenden Beitrag gepostet, der sich genauer mit dem befasst, was in der C-Sprache genau wohin geht.
Lundin
1
@ SojuTVarghese Ich habe meine Antwort aktualisiert und auch einige Informationen zu FRAM hinzugefügt.
Tcrosley
@Lundin Wir haben die gleichen Segmentnamen (zB .rodata) verwendet, damit sich die Antworten gut ergänzen.
Tcrosley
21

Normales eingebettetes System:

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.

Außerdem gibt es normalerweise separate Flash-Segmente für Startcode- und Interrupt-Vektoren.


Erläuterung:

Eine Variable hat eine statische Speicherdauer, wenn sie staticals "global" deklariert ist oder sich im Dateibereich befindet. C hat eine Regel, die besagt, dass alle statischen Speicherdauer-Variablen, die der Programmierer nicht explizit initialisiert hat, auf Null initialisiert werden müssen.

Jede statische Speicherdauer-Variable, die implizit oder explizit mit Null initialisiert wird, endet in .bss. Während diejenigen, die explizit auf einen Wert ungleich Null initialisiert werden, in enden .data.

Beispiele:

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
}

Bitte beachten Sie, dass ein sehr häufiges nicht standardmäßiges Setup für eingebettete Systeme ein "minimaler Start" ist, was bedeutet, dass das Programm die gesamte Initialisierung von Objekten mit statischer Speicherdauer überspringt . Daher ist es möglicherweise ratsam, niemals Programme zu schreiben, die auf den Initialisierungswerten solcher Variablen basieren, sondern diese vor der ersten Verwendung auf "Laufzeit" zu setzen.

Beispiele für die anderen Segmente:

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
}

Variablen, die auf dem Stack gespeichert werden können, landen während der Optimierung häufig in CPU-Registern. Als Faustregel kann jede Variable, deren Adresse nicht vergeben ist, in ein CPU-Register eingetragen werden.

Beachten Sie, dass Zeiger etwas komplizierter sind als andere Variablen, da sie zwei verschiedene Arten von zulassen const, je nachdem, ob die Daten, auf die verwiesen wird, schreibgeschützt sein sollen oder ob der Zeiger selbst schreibgeschützt sein soll. Es ist sehr wichtig, den Unterschied zu kennen, damit Ihre Zeiger nicht versehentlich im RAM landen, wenn Sie wollten, dass sie im Flash sind.

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

Im Fall von Ganzzahlkonstanten, Initialisierungslisten, Zeichenfolgenliteralen usw. können sie je nach Compiler entweder in .text oder .rodata enden. Wahrscheinlich enden sie als:

#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
quelle
Ich verstehe in Ihrem ersten Beispielcode nicht, warum 'static int b = 0;' geht in .bss und warum 'static int d = 1;' geht in .data ..? Nach meinem Verständnis sind beide statische Variablen, die vom Programmierer initialisiert wurden. Was macht dann den Unterschied? @Lundin
Soju T Varghese
2
@SojuTVarghese Da .bss-Daten als Block auf 0 initialisiert werden. bestimmte werte wie d = 1 müssen im flash gespeichert werden.
Tcrosley
@ SojuTVarghese Einige Klarstellungen hinzugefügt.
Lundin
@Lundin Bedeutet dies aus Ihrem letzten Beispielcode auch, dass alle initialisierten Werte in .text oder .rodata und ihre jeweiligen Variablen allein in .bss oder .data abgelegt werden? Wenn ja, wie werden die Variablen und ihre entsprechenden Werte aufeinander abgebildet (dh zwischen den Segmenten .bss / .data und .text / .rodata)?
Soju T Varghese
@SojuTVarghese Nein, hat .datanormalerweise eine sogenannte Ladeadresse in Flash, wo die Anfangswerte gespeichert werden, und eine sogenannte virtuelle Adresse (nicht wirklich virtuell in einem Mikrocontroller) in RAM, wo die Variable während der Ausführung gespeichert wird. Vor dem mainStart werden die Anfangswerte von der Ladeadresse in die virtuelle Adresse kopiert. Sie müssen keine Nullen speichern und müssen daher .bssauch keine Anfangswerte speichern. sourceware.org/binutils/docs/ld/…
starblue
1

Während alle Daten in jeden vom Programmierer gewählten Speicher abgelegt werden können, funktioniert das System im Allgemeinen am besten (und ist für die Verwendung vorgesehen), wenn das Nutzungsprofil der Daten mit den Lese- / Schreibprofilen des Speichers übereinstimmt.

Zum Beispiel ist der Programmcode WFRM (schreibe wenige, lese viele) und es gibt eine Menge davon. Das passt gut zu FLASH. ROM OTOH ist W einmal RM.

Stapel und Haufen sind klein, mit vielen Lese- und Schreibvorgängen. Das würde am besten zu RAM passen.

EEPROM würde für keine dieser Verwendungen gut geeignet sein, passt jedoch zum Profil kleiner Datenmengen, die über die Startvorgänge hinweg vorhanden sind, also zu benutzerspezifischen Initialisierungsdaten und möglicherweise zu Protokollierungsergebnissen.

Neil_UK
quelle