Forum: Mikrocontroller und Digitale Elektronik Statische Speicherverwaltung bei MCUs


von char0n (Gast)


Lesenswert?

Hallo,

ich verstehe da eine "Kleinigkeit" nicht.

Erstmal die Sache die ich verstehe:

Dynamischen Speicher allokiere ich mit malloc() usw. und gebe ihn mit 
free() wieder frei. Die Daten die dabei entstehen liegen im Heap, wobei 
dieser Heap in der Regel im vorderen Bereich des RAMs liegt (hinter bss, 
data usw). So weit so gut.


Jetzt lese ich oft, dass dynamischer Speicher nicht das Wahre für MCUs 
ist.
Man sollte lieber seinen RAM statisch verwalten.
Wie geht das (einfaches Codebeispiel)?

Und wo liegt dieser Speicher dann? Im Stack? Zwischen Heap und Stack?
Ist eine lokale Variable somit statische Speicherverwaltung, weil sie 
beim Funktionsaufruf in den Stack eingelesen wird und am Ende der 
Funktion dann verworfen wird?
Oder ist statische Speicherverwaltung wenn ich gezielt auf Adressen im 
RAM schreibe und diese dann mit einem Zeiger verbinde?
Darf man das, bzw sollte man das? Was ist wenn diese Adresse schon einen 
Wert beinhaltet den eine andere Variable benutzt?

Oder ist statische Speicherverwaltung das Anlegen von globalen 
Variablen?
Da Frage ich mich jedoch, wie man da mit dem Speicher auskommt, wenn man 
mehrere Datenmengen hat die komplett nicht in den RAM passen, jedoch 
wenn man sie einzeln initialisieren würde.

Irgendwie verstehe ich das nicht so recht.

Vielen Dank für eure Antworten.

p.s. wenn ihr ein gutes Tutorial dazu kennt, dann her damit.

von Stefan E. (sternst)


Lesenswert?

char0n schrieb:
> Und wo liegt dieser Speicher dann? Im Stack? Zwischen Heap und Stack?

bss oder data, je nach dem.

char0n schrieb:
> Ist eine lokale Variable somit statische Speicherverwaltung,

Nein, eine lokale Variable ist nur eine andere Art von dynamischen 
Speicher.

char0n schrieb:
> Oder ist statische Speicherverwaltung das Anlegen von globalen
> Variablen?

Ja.

char0n schrieb:
> Da Frage ich mich jedoch, wie man da mit dem Speicher auskommt, wenn man
> mehrere Datenmengen hat die komplett nicht in den RAM passen, jedoch
> wenn man sie einzeln initialisieren würde.

Wenn die Daten gleichzeitig benutzt werden, passen sie so oder so nicht 
rein. Und wenn man sie nicht gleichzeitig benutzt, kann man ja auch 
statischen Speicher "recyceln". Wenn sich die Daten in ihrer Art 
unterscheiden z.B. am besten per union.

von André A. (nummer5) Benutzerseite


Lesenswert?

Statische Speicherverwaltung  heißt, dass du zur Compilezeit weist wie 
viel RAM benötigt wird. Erreicht wird das wie du schon vermutet hast 
durch globale Variablen. Diese liegen dann in der data oder bss Sektion.

Wenn du die Variablen im Stack anlegst ist das auch dynamisch da du 
normalerweise nicht genau weist auf welche verschachtelten Wege deine 
Funktion aufgerufen wird, deshalb sollte man in Funktionen keine großen 
Variablen auf den Stack legen. An ein paar ints scheitert es meistens 
nicht, dafür ist der Stack ja auch gedacht.

Das Problem mit den mehreren Datenmengen die nicht gleichzeitig ins RAM 
passen muss man entweder dynamisch lösen wobei man sich genau Gedanken 
darüber machen muss ob das klappt oder man legt z.B. ein Byte array an
1
uint8_t daten[256];

und benutzt das dann einmal für die 1. Daten und danach dann für die 2. 
Daten. Wenns um Strukturen geht kann man das meist noch elegant mit 
Unions lösen um nicht soviel casten zu müssen. Beide Varianten gehen 
natürlich nur, wenn sichergestellt ist, dass die Daten nicht 
gleichzeitig benutzt werden.

von char0n (Gast)


Angehängte Dateien:

Lesenswert?

Vielen vielen Dank für eure Antworten!
Das hat mir schonmal gut geholfen, jedoch auch weitere Fragen 
aufgeworfen. Würdet ihr euch vielleicht noch diese Frage zu Gemüte 
führen? :-)


Stefan Ernst schrieb:
> char0n schrieb:
>> Und wo liegt dieser Speicher dann? Im Stack? Zwischen Heap und Stack?
>
> bss oder data, je nach dem.
>

Woran liegt das denn?

Ich arbeite zur Zeit mit einem STM32 und bei dem kann ich in der Startup 
Assembler Datei die Größe vom Heap und Stack definieren (siehe unten).
Wieso kann ich da keine bss oder data definieren. Und vorallem wundere 
ich mich das der Stack und Heap so klein definiert ist, immerhin habe 
ich über 100kByte internen RAM, laut dem Code habe ich "nur" 1kByte 
Stack und 512Byte Heap... Wo ist der Rest hin? Ich dachte eigentlich 
Heap und Stack würden sich berühren und davor liegen bss und data, wie 
in dem Bild im Anhang. Somit sind laut dem Assemblercode mehrere kByte 
ungenutzt?! Verstehe ich nicht... :-/
1
; Amount of memory (in bytes) allocated for Stack
2
; Tailor this value to your application needs
3
; <h> Stack Configuration
4
;   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
5
; </h>
6
7
Stack_Size      EQU     0x00000400
8
9
                AREA    STACK, NOINIT, READWRITE, ALIGN=3
10
Stack_Mem       SPACE   Stack_Size
11
__initial_sp
12
13
14
; <h> Heap Configuration
15
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
16
; </h>
17
18
Heap_Size       EQU     0x00000200
19
20
                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
21
__heap_base
22
Heap_Mem        SPACE   Heap_Size
23
__heap_limit
24
25
.
26
.
27
.
28
.
29
Hier stehen Vector Table usw
30
.
31
.
32
.
33
34
;*******************************************************************************
35
; User Stack and Heap initialization
36
;*******************************************************************************
37
                 IF      :DEF:__MICROLIB
38
                
39
                 EXPORT  __initial_sp
40
                 EXPORT  __heap_base
41
                 EXPORT  __heap_limit
42
                
43
                 ELSE
44
                
45
                 IMPORT  __use_two_region_memory
46
                 EXPORT  __user_initial_stackheap
47
                 
48
__user_initial_stackheap
49
50
                 LDR     R0, =  Heap_Mem
51
                 LDR     R1, =(Stack_Mem + Stack_Size)
52
                 LDR     R2, = (Heap_Mem +  Heap_Size)
53
                 LDR     R3, = Stack_Mem
54
                 BX      LR
55
56
                 ALIGN
57
58
                 ENDIF
59
60
                 END


Was mich auch interessiert, wenn ich eine globale Variable anlege und 
diese dann in einer Funktion beschreibe oder dergleichen, wird diese 
dann auch in den Stack geschrieben? Das würde für mich dann ja heißen, 
dass der Stack mindestens so groß sein muss wie die Summe aller 
Variablen die in einer Funktion aufgerufen werden könnten. :-/ Gibts 
keine maximale Größte für den Stack?

André Althaus schrieb:
> und benutzt das dann einmal für die 1. Daten und danach dann für die 2.
> Daten. Wenns um Strukturen geht kann man das meist noch elegant mit
> Unions lösen um nicht soviel casten zu müssen. Beide Varianten gehen
> natürlich nur, wenn sichergestellt ist, dass die Daten nicht
> gleichzeitig benutzt werden.

Gleichtzeitig werde ich sie nicht nutzen, aber nacheinander... das mit 
dem Unions muss ich mir mal ansehen.
Kann ich denn in einer Union nicht nur verschiedene Datentypen, sondern 
auch verschiedene Größen für die Arrays nutzen und gleichzeitig die 
gleiche Startadresse haben?

Bsp.:
1
uint8_t daten[256];
2
oder 
3
uint8_t daten2[512];
4
oder 
5
uint32_t daten3[1024];


Vielen Dank für eure Bemühungen.

Grüße
char0n

von André A. (nummer5) Benutzerseite


Lesenswert?

> Was mich auch interessiert, wenn ich eine globale Variable anlege und
> diese dann in einer Funktion beschreibe oder dergleichen, wird diese
> dann auch in den Stack geschrieben?

Der Stack dienst als Zwischenspeicher für Funktionen. Globale Variablen 
liegen an einer dem Compiler bekannten festen Stelle im RAM und die 
Werte werden dort geschrieben (deshalb global).

Beim Booten legt man den Anfang des Stacks in Form des Stackpointers an 
eine Stelle des RAMs (oft ans Ende) und bei jedem Funktionsaufruf wird 
der Pointer in der Funktion verschoben. Man weis also nicht wie viel 
Platz der Stack braucht, da dies von dem genauen Verlauf der 
Funktionsaufrufe abhängt. Deshalb steht im Assemblercode auch

> ; Tailor this value to your application needs

Wenn du die Stackgröße zu klein wählst und der Heap direkt an den Stack 
grenzt kann es passieren dass bei einem Funktionsaufruf der Stack in den 
Heap hineinwächst und dort Werte überschreibt. Du legst hier auch keine 
endgültige Stackgröße (der Stack kann auch größer werden) an, sondern 
nimmst soviel dass du sicher bist, dass diese Größe nicht überschritten 
wird. Überprüfen und messen kann man den Stackverbrauch indem man den 
Stack am Anfang mit einem Muster komplett füllt (z.B. 0xAA) und in 
regelmäßigen Abständen (überprüfen ob der Stack nicht zu groß wird) bzw. 
nachdem das Programm lange gelaufen ist und somit alle möglichen 
Funktionsverläufe aufgetreten sind (Stackverbrauch ermitteln) einfach 
nachschaut wie weit das Muster noch vorhanden ist.

Der Heap ist der Speicher den malloc/free zu Verfügung haben, den legst 
du hier auch selber fest je nachdem wie viel deine Anwendung braucht.

bss (genullte globale Variablen) und data (mit Werten initialisierte 
globale Variable) brauchst du nicht festlegen, das macht der Compiler 
für dich. Du sagst ihm im Code was für globale Variablen du brauchst und 
der Compiler macht die beiden Sektionen dann so groß wie nötig.


An welcher Stelle im Flash und RAM die einzelnen Sektionen landen legt 
das Linkerskript fest. Bei deiner Architektur dürfte es dann so aussehen 
wie auf dem Bild wobei ich vermute, dass zwischen Heap und Stack der 
ganze ungenutze RAM liegt.

> Kann ich denn in einer Union nicht nur verschiedene Datentypen, sondern
> auch verschiedene Größen für die Arrays nutzen und gleichzeitig die
> gleiche Startadresse haben?

Wenn du folgendes machst
1
union test {
2
    uint8_t daten[256];
3
    uint8_t daten2[512];
4
    uint32_t daten3[1024];
5
}

Dann ist der Union insgesamt so groß wie die größte Komponente also 
1024*sizeof(uint32_t). Alle drei Arrays fangen an der gleichen 
Speicheradresse an. Wegen den gleichen Datentypen wäre test.daten eine 
Teilmenge von test.daten2.

von Frank K. (fchk)


Lesenswert?

char0n schrieb:

> Jetzt lese ich oft, dass dynamischer Speicher nicht das Wahre für MCUs
> ist.
> Man sollte lieber seinen RAM statisch verwalten.

Ja, genau.

Beispiel aus dem realen Leben (Amiga A2000, der hatte das Problem auch):
Du hast 8 MB zusammenhängendes RAM. Jetzt forderst Du nacheinander 
2,5MB, 1MB und 4MB an Speicher an. Bleibt also noch 0,5MB frei. Jetzt 
gibst Du den 2,5MB-Block und den 4MB-Block wieder frei. Du hast jetzt 
wieder 7MB freien Platz. Jetzt forderst Du 6MB an und bekommst eine 
Fehlermeldung. Wieso? Ganz klar, Du hast zwar in der Summe 7 MB frei, 
das aber nicht an einem Stück, weil der 1MB-Block mitten im Adressraum 
liegt. Der Adressraum fragmentiert im Laufe der Zeit, weil das 
Betriebssystem die belegten Blöcke nicht umherschieben konnte, um Platz 
zu schaffen. Da half dann nur ein Reboot.

Bei Systemen mit einer MMU sind logischer und physikalischer Adressraum 
voneinander getrennt, d.h. jedes Programm hat seinen eigenen 
Adressbereich, sodass eine Fragmentierung des Adressraums nicht so 
leicht auftritt.

Windows und das alte MacOS haben es anders gemacht. Du hast bei einem 
LocalAlloc nur ein Handle bekommen. Erst nach einem LocalLock hast Du 
einen Pointer auf Deinen Speicher bekommen. Nach einem LocalUnlock 
konnte das Betriebssystem den Speicherbereich umherschieben, um einer 
Fragmentation entgegenzuwirken.

Das AmigaDos hatte keinen derartigen Mechanismus, wegwegen der Speicher 
im Laufe der Zeit fragmentierte. ucLinux (die Spar-Version für 
Prozessoren ohne MMU) hat prinzipbedingt das gleiche Problem.

Daher ist die empfehlenswerte Strategie im Embedded-Bereich die, ALLE 
benötigten Resourcen EINMAL am Anfang anzufordern und anschließend damit 
zu arbeiten.

fchk

von char0n (Gast)


Lesenswert?

char0n schrieb:
> Was mich auch interessiert, wenn ich eine globale Variable anlege und
> diese dann in einer Funktion beschreibe oder dergleichen, wird diese
> dann auch in den Stack geschrieben? Das würde für mich dann ja heißen,
> dass der Stack mindestens so groß sein muss wie die Summe aller
> Variablen die in einer Funktion aufgerufen werden könnten.

André Althaus schrieb:
>
> Der Stack dienst als Zwischenspeicher für Funktionen. Globale Variablen
> liegen an einer dem Compiler bekannten festen Stelle im RAM und die
> Werte werden dort geschrieben (deshalb global).

Danke für die ausführliche Erklärung, jedoch konnte ich daraus nicht 
rauslesen, ob die globale Variable komplett in den Stack gelegt wird 
oder nur die Adresse zu der globalen Variable, sobald diese Variable in 
einer Funktion genutzt wird. Wenn der komplette Wert in den Stack 
gelesen wird, dann muss ich ja den Stack mindestens so groß machen wie 
theoretisch globale Variablen in der Funktion aufgerufen werden könnten.
Also wie ist das nun?
Oder muss ich dann darauf achten wie ich den Wert übergebe, Stichpunkt 
"Call by Value/Reference"?

Vielen Dank nochmal.

von André A. (nummer5) Benutzerseite


Lesenswert?

Weder noch. Bei diesem Beispielcode:
1
int global;
2
3
void test(int foo)
4
{
5
    global = foo;
6
}

weis der Compiler z.B. dass global an Adresse 0x10 liegt. Und dann wird 
da pseudo Assembler mäßig nur ein
1
lade register0 in adresse 0x10
2
return

Wenn du eine globale Variable in einer Funktion benutzt landet nichts 
davon im Stack. Du kannst dir ja auch mal den Assemblercode anschauen, 
da sieht man was auf den Stack geschrieben wird. Ich finde durch 
anschauen vom Assemblercode lernt man sowas am besten. Vielleicht auch 
mal die elf Datei vollständig disassemblern dann siehst du auch wo die 
einzelnen Sektionen landen.

> Oder muss ich dann darauf achten wie ich den Wert übergebe, Stichpunkt
> "Call by Value/Reference

Die Werteübergabe der Parameter hat mit globalen Variablen nichts zu 
tun. Bei allen Standard Datentypen (char, short, int) werden bei "Call 
by Value" die Werte normalerweise direkt in Registern übergeben. Nur 
wenn keine Register mehr frei sind (weil du z.B. ein array by Value 
übergibts) werden die Werte auf den Stack geschrieben.
Das hängt natürlich alles von der Architektur und der Sprache ab. Ich 
beziehe mich hier auf C.

Als Faustregel alle Werte die in einfache Datentypen passen per Value 
übergeben, alles größere (Array, Struktur) per Zeiger (oder Referenz je 
nach Sprache)

Falls noch was unklar ist poste mal ein kompilierbares Minimalbeispiel 
an konkreten Code ist es etwas einfacher die Sachen zu erklären.

von char0n (Gast)


Lesenswert?

Ich kann mich hier nur am laufenden Band bedanken!
Das Beispiel hat mir sehr gut geholfen!

Ich hatte mir noch überlegt was in diesem Fall wäre:
1
int global;
2
3
void test(void)
4
{
5
   int foo;
6
7
   foo = global;
8
}

Dabei würde die Variable foo dynamisch erstellt und im Stack landen. Bei 
der statischen Speicherverwaltung will ich ja genau das verhindern. 
Richtig?

Dieses Beispiel kommt mir auch noch in den Sinn:
1
int foo;
2
int bar;
3
4
void test(void)
5
{
6
   foo = bar;
7
}

Entsteht hier wieder ein Pseudocode der foo die Adresse von bar 
übergibt?
Somit liegen foo und bar auf der selben Adresse und verweisen auf den 
gleich Wert.
Richtig?


Wenn du meine Beispiele und Erklärungen dazu absegnest bin ich voll auf 
zufrieden! :)

Danke!

von Peter II (Gast)


Lesenswert?

char0n schrieb:
> Dieses Beispiel kommt mir auch noch in den Sinn:
> int foo;
> int bar;
>
> void test(void)
> {
>    foo = bar;
> }
>
> Entsteht hier wieder ein Pseudocode der foo die Adresse von bar
> übergibt?
> Somit liegen foo und bar auf der selben Adresse und verweisen auf den
> gleich Wert.
> Richtig?

nein, foo und bar habe beide ihre eigene speicherzelle.

Hier wird der inhalt von der speicherzelle von bar in den Inhalt der 
speicherzelle von foo geschrieben.

Danach steht also 2mal im speicher der gleiche wert.

von char0n (Gast)


Lesenswert?

Peter II schrieb:
> Hier wird der inhalt von der speicherzelle von bar in den Inhalt der
> speicherzelle von foo geschrieben.
>
> Danach steht also 2mal im speicher der gleiche wert.

Läuft das Schreiben von bar in foo dann über den Stack?

von André A. (nummer5) Benutzerseite


Lesenswert?

> Dabei würde die Variable foo dynamisch erstellt und im Stack landen. Bei
> der statischen Speicherverwaltung will ich ja genau das verhindern.
> Richtig?
Genau (wenn es nur sehr wenig Code ist auch direkt in einem Register), 
aber eine Funktion braucht ja bestimme Variablen als Zwischenspeicher. 
Solange es nur um ein paar einfache Datentypen geht, sollte man sich um 
den Stack nicht soviel Sorgen machen, zumal du ja genug RAM hast, ob du 
den Stack dann 1kB oder 2kB groß machst ist dann egal.

Es geht bei der statischen Speicherverwaltung besonders um so Sachen wie 
Puffer, Warteschlangen, große Arrays und Strukturen, die halt mehr als 
ein paar Byte belegen und für die man sonst dynamisch bei Bedarf den 
Speicher anfordern würde.

> void test(void)
> {
>    foo = bar;
> }

Über den Stack sollte hierbei nichts laufen, ich denke da kommt so was 
bei raus:
1
lade Adresse von foo in Register A
2
lade den Wert, der an der Adresse in Register A steht in Register B
3
lade Adresse von bar in register A
4
speichere den Wert in Register B in die Adresse in register A

Es hängt auch immer davon ab wie gut der Compiler optimiert. Wie gesagt 
schau dir mal den Assemblercode an den der Compiler produziert. Du 
brauchst nicht viel Assembler wissen, zum lesen und groben verstehen 
reicht es wenn man die Befehlsreferenz für den Befehl kurz überfliegt.

von char0n (Gast)


Lesenswert?

Vielen Dank, Ihr habt mir sehr geholfen!
Mit dem Union und den Infos über die statische Speicherverwaltung komme 
ich jetzt gut weiter.  :)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.