K (→Nutzung des SRAM durch avr-gcc) |
(→Nutzung des SRAM durch avr-gcc) |
||
Zeile 31: | Zeile 31: | ||
;<tt>.noinit</tt>: Statische und (modul-)globale Daten, die nicht vom Startup-Code initialisiert werden. | ;<tt>.noinit</tt>: Statische und (modul-)globale Daten, die nicht vom Startup-Code initialisiert werden. | ||
− | Auf diese Sektions folgen noch zwei Speicherbereiche für dynamische Daten | + | Auf diese Sektions folgen noch zwei Speicherbereiche für dynamische Daten. |
;<tt>Heap</tt>: Danach folgt der Heap. Das ist ein Speicherbereich, aus dem Speicherplatz via <tt>malloc</tt> etc. allokiert wird. | ;<tt>Heap</tt>: Danach folgt der Heap. Das ist ein Speicherbereich, aus dem Speicherplatz via <tt>malloc</tt> etc. allokiert wird. | ||
Zeile 37: | Zeile 37: | ||
Die Größe der ersten drei Speicherbereiche kann man bereits zur Compile-Zeit bestimmen, da sie unabhängig sind von der Programmausführung. Wie viel Platz die letzten beiden Bereiche brauchen, ergibt sich erst zur Laufzeit des Programms. Diese Größen ändern sich in aller Regel mit der Zeit. | Die Größe der ersten drei Speicherbereiche kann man bereits zur Compile-Zeit bestimmen, da sie unabhängig sind von der Programmausführung. Wie viel Platz die letzten beiden Bereiche brauchen, ergibt sich erst zur Laufzeit des Programms. Diese Größen ändern sich in aller Regel mit der Zeit. | ||
+ | |||
+ | Heap und Stack müssen sich den Speicher teilen, der nicht von <tt>.data</tt>, <tt>.bss</tt> und <tt>.noinit</tt> belegt ist. | ||
+ | |||
+ | Wird der Stackbereich zu groß, weil dort zu viele Daten abgelegt werden (zu viele lokale Variablen (lokale Arrays!), zu tief verschaltelte (rekursive) Funktionen, kaskadierende [[Interrupt]] Service Routinen, ...), dann überschreibt man damit womöglich andere Daten und es kommt zur Fehlfunktion des Programmes. | ||
=Flash- und statischer RAM-Verbrauch = | =Flash- und statischer RAM-Verbrauch = |
Version vom 24. März 2006, 20:00 Uhr
Der Speicherverbrauch eines C-Programmes lässt sich unter verschiedenen Gesichtspunkten betrachten.
- Ort der Datenablage
- SRAM
- Flash
- EEPROM
- Art der Daten
- Programmcode
- Daten
- veränderlich/unveränderlich
- persistent oder flüchtig
- Speicherklasse der Daten
- Statischer Speicherverbrauch
- Dynamischer Speicherverbrauch
Der Ort der Datenablage wird bestimmt von der Art der Daten. Daten, die nicht verändert werden dürfen oder nicht verändert werden müssen, wie der Programm-Code oder konstante Strings, wie sie oft zur Ausgabe verwendet werden, können im Flash gespeichert werden. Konstanten/Tabellen kann man auch im EEPROM speichern, wenn der Platz im Flash knapp ist. Der Programmcode solbst muss bei AVR im Flash liegen. Man kann den Code zwar auch ins RAM oder ins EEPROM legen, aber von dort nicht ausführen.
Daten, die zur Laufzeit verändert werden müssen, wird man im SRAM speichern, wenn sie einen Reset bzw. ein Ausschalten des Controllers nicht überleben müssen. Sollen die Daten auch ohne Strom erhalten bleiben, muss man den EEPROM oder den Flash als Ablageort wählen. Dabei das Speichern von Daten im Flash zur Laufzeit sehr aufwändig, weil man einen Bootloader für ihre Änderung anstrengen muss. Andererseits kann wesentlich schneller auf das Flash zugegriffen werden als auf den EEPROM-Speicher.
Die Art der Daten ergibt sich aus dem Programm und den zu lösenden Aufgaben, gleiches gilt für die Speicherklasse. Die Ablageorte der statische Daten sind bereits zur Compilezeit bekannt. Hierzu gehören globale und statische Variablen. Dementsprechend ist auch schon zur Compile- bzw. Linkzeit bekannt, wieviel Speicher diese Daten belegen. Speicherorte und Platzverbrauch dynamischer Variablen sind dem Compiler nicht bekannt. Sie ergeben sich erst zur Laufzeit durch das Allokieren von Speicher mit malloc, oder durch den Aufbau eines Stapels, auf dem lokale Variablen gesichert werden, während eine Funktion aufgerufen wird. Je nach Verschachtelungstiefe der Funktionsaufrufe — dazu gehören auch Interrupt Service Routinen, die prinzipiell jederzeit aufgerufen werden können — wird dafür auch unterschiedlich viel Speicher benötigt.
Nutzung des SRAM durch avr-gcc
avr-gcc legt die Daten in Sections an. Nach aufsteigenden Speicheradressen sortiert sind diese:
- .data
- Statische und (modul-)globale, initialisierte Daten, denen man per Initializer einen Wert ungleich 0 zuweist. Beginnt an er unteren SRAM-Adresse 0x60 nach dem SFR-Bereich.
- .bss
- Statische und (modul-)globale, initialisierte Daten, die zu 0 initialisiert sind bzw. keinen Initializer haben (und also auch zu 0 initialisiert werden).
- .noinit
- Statische und (modul-)globale Daten, die nicht vom Startup-Code initialisiert werden.
Auf diese Sektions folgen noch zwei Speicherbereiche für dynamische Daten.
- Heap
- Danach folgt der Heap. Das ist ein Speicherbereich, aus dem Speicherplatz via malloc etc. allokiert wird.
- Stack
- Auf dem Stapel werden lokale Variablen/Register während Funktionsaufrufen gesichert. Der Stack wird auch zur Parameterübergabe verwendet und die return-Adresse von Funktionen und ISRs wird dort abgelegt. Der Stack beginnt an der oberen SRAM-Adresse und wächst nicht wie die andern Bereiche nach oben, sondern nach unten.
Die Größe der ersten drei Speicherbereiche kann man bereits zur Compile-Zeit bestimmen, da sie unabhängig sind von der Programmausführung. Wie viel Platz die letzten beiden Bereiche brauchen, ergibt sich erst zur Laufzeit des Programms. Diese Größen ändern sich in aller Regel mit der Zeit.
Heap und Stack müssen sich den Speicher teilen, der nicht von .data, .bss und .noinit belegt ist.
Wird der Stackbereich zu groß, weil dort zu viele Daten abgelegt werden (zu viele lokale Variablen (lokale Arrays!), zu tief verschaltelte (rekursive) Funktionen, kaskadierende Interrupt Service Routinen, ...), dann überschreibt man damit womöglich andere Daten und es kommt zur Fehlfunktion des Programmes.
Flash- und statischer RAM-Verbrauch
Zur Bestimmung des Speicherplatzes, den statische Daten belegen, verwendet man avr-size, das zu den Binutils gehört und z.B. bei WinAVR dabei ist.
Abhängig von der Section schlägt ihr Platzverbrauch in Flash/SRAM/EEPROM zu Buche:
belegter Speicher | Sections (Einzelgrößen addieren) | Beschreibung |
---|---|---|
Flash | .text + .data | Programmcode und Tabelle für initialisierte Daten |
SRAM | .data + .bss + .noinit | Daten (initialisiert, zu 0 initialisiert, nicht initialisiert) |
EEPROM | .eeprom | Daten, die man ins EEPROM gelegt hat |
Beispiele:
Das '>' nicht mittippen, es ist der Kommandozeilen-Prompt.
Verbrauch der einzelnen Module auflisten:
> avr-size -x foo1.o foo2.o ...
Verbrauch des gesamten Programms auflisten:
> avr-size -x -A foo.elf
Platzverbrauch von Funktionen, Objekten, etc. nach Größe sortiert:
> avr-nm --size-sort -S foo.elf
Dynamischer RAM-Verbrauch
Um den momentan freien Speicher zu bestimmen, zieht man einfach den Anfang des Heaps vom Stackpointer ab:
#include <avr/io.h> // From linker script extern unsigned char __heap_start; ... uint16_t momentan_frei = SP - (uint16_t) &__heap_start;
Interessanter ist es jedoch, den Maximalverbrauch an Speicher zu kennen, bzw das Minimum an freiem Speicher, also den worst case zu kennen.
Mit der folgenden kleinen Routine kann der noch freie SRAM-Bereich bestimmt werden. Es wird allerdings nicht der momentan freie Speicher bestimmt, sondern das Minimum an Speicher, das bis dato frei geblieben ist. Dazu wird im Startup-Code das Muster 0xaa in den SRAM geschrieben. Durch Aufruf der Funktion get_mem_unused wird bestimmt, wieviel von diesem Muster noch intakt ist.
Mit optimierendem Compiler brauchen die beiden Routinen 42 Bytes an Flash.
- mem-check.h
#ifndef _MEM_CHECK_H_ #define _MEM_CHECK_H_ extern unsigned short get_mem_unused (void); #endif /* _MEM_CHECK_H_ */
- mem-check.c
#include <avr/io.h> // RAMEND #include "mem-check.h" // Mask to init SRAM and check against #define MASK 0xaa // From linker script extern unsigned char __heap_start; unsigned short get_mem_unused (void) { unsigned short unused = 0; unsigned char *p = &__heap_start; do { if (*p++ != MASK) break; unused++; } while (p <= (unsigned char*) RAMEND); return unused; } /* !!! never call this function !!! */ void __attribute__ ((naked, section (".init8"))) __init8_mem (void) { __asm volatile ( "ldi r30, lo8 (__heap_start)" "\n\t" "ldi r31, hi8 (__heap_start)" "\n\t" "ldi r24, %0" "\n\t" "ldi r25, hi8 (%1)" "\n" "0:" "\n\t" "st Z+, r24" "\n\t" "cpi r30, lo8 (%1)" "\n\t" "cpc r31, r25" "\n\t" "brlo 0b" : : "i" (MASK), "i" (RAMEND+1) ); }