Go Down

Topic: Speicherbelegung / RAM access (Read 658 times) previous topic - next topic

TERWI

Jul 05, 2013, 05:14 pm Last Edit: Jul 05, 2013, 05:19 pm by TERWI Reason: 1
Entgegen (vielen) anders lautenden Meinungen, man sollte einen µC nicht mit dynamischer Speicherverwaltung "traktieren", habe ich mal eine wenig mehr rumgekugelt und dabei u. a. das hier gelesen:
http://forum.arduino.cc/index.php?topic=27536.msg204023#msg204023

Mit dieser Basis hab ich mal als eigentlich immer noch Arduino-Newbie heute mal ein wenig rumgebastelt und mir folgende daraus zusammengestellt:
Code: [Select]

void GetMemInfo()
{
 extern unsigned int __data_start;
 extern unsigned int __data_end;
 extern unsigned int __bss_start;
 extern unsigned int __bss_end;
 extern unsigned int __heap_start;
 extern void *__brkval;
//  extern void *__malloc_heap_start; --> apparently already declared as char*
//  extern void *__malloc_margin; --> apparently already declared as a size_t
//  RAMEND and SP seem to be available without declaration here

 Serial.println("--------------------------------------------");
 
 // ---------------- Print memory profile -----------------
 int16_t ramSize=0;   // total amount of ram available for partitioning
 int16_t dataSize=0;  // partition size for .data section
 int16_t bssSize=0;   // partition size for .bss section
 int16_t heapSize=0;  // partition size for current snapshot of the heap section
 int16_t stackSize=0; // partition size for current snapshot of the stack section
 int16_t freeMem1=0;  // available ram calculation #1
 int16_t freeMem2=0;  // available ram calculation #2
 // summaries:
 ramSize   = (int) RAMEND       - (int) &__data_start;
 dataSize  = (int) &__data_end  - (int) &__data_start;
 bssSize   = (int) &__bss_end   - (int) &__bss_start;
 heapSize  = (int) __brkval     - (int) &__heap_start;
 stackSize = (int) RAMEND       - (int) SP;
 freeMem1  = (int) SP           - (int) __brkval;
 freeMem2  = ramSize - stackSize - heapSize - bssSize - dataSize;
 Serial.println("----- GET MEMORY INFORMATIONS -----");
 Serial.println("");
 Serial.println("--- max. available memory by hardware ---");
 Serial.print("ram total  = "); Serial.print( ramSize, DEC ); Serial.println(" bytes");
 Serial.println("--- DATA ---");
 Serial.print("data_start = 0x"); Serial.print((int) &__data_start, HEX ); Serial.print(" / "); Serial.println( (int) &__data_start, DEC);
 Serial.print("data_end   = 0x"); Serial.print((int) &__data_end, HEX ); Serial.print(" / "); Serial.println( (int) &__data_end, DEC);
 Serial.print("data size  = "); Serial.println( dataSize, DEC );
 Serial.println("--- BSS ---");
 Serial.print("bss_start  = 0x"); Serial.print((int) & __bss_start, HEX ); Serial.print(" / "); Serial.println( (int) &__bss_start, DEC);
 Serial.print("bss_end    = 0x"); Serial.print((int) &__bss_end, HEX ); Serial.print(" / "); Serial.println( (int) &__bss_end, DEC);
 Serial.print("bss size   = "); Serial.println( bssSize, DEC );
 Serial.println("--- HEAP ---");
 Serial.print("heap_start =0x"); Serial.print((int) &__heap_start, HEX ); Serial.print(" / "); Serial.println( (int) &__heap_start, DEC);
 Serial.println("heap_margin = ? ? ?");
//  Serial.print("\n__malloc_margin=[0x"); Serial.print( (int) &__malloc_margin, HEX ); Serial.print("] which is ["); Serial.print( (int) &__malloc_margin, DEC); Serial.print("] bytes decimal");
 Serial.print("heap end    = 0x"); Serial.print((int) __brkval, HEX ); Serial.print(" / "); Serial.println( (int) __brkval, DEC);
 Serial.print("heap size   = "); Serial.println(heapSize, DEC );
 Serial.println("--- STACK ---");
 Serial.print("stack start = 0x"); Serial.print((int) SP, HEX ); Serial.print(" / "); Serial.println((int) SP, DEC);
 Serial.print("stack end   = 0x"); Serial.print( (int) RAMEND, HEX ); Serial.print(" / "); Serial.println( (int) RAMEND, DEC);
 Serial.print("stack size= "); Serial.println( stackSize, DEC );
 Serial.println("--- FREE MEMORY ---");
 Serial.print("free size1 = "); Serial.println( freeMem1, DEC );
 Serial.print("free size2 = "); Serial.println( freeMem2, DEC );
 // --- free_memory ---
 Serial.println("");
 Serial.println("--- calculated with free_memory:");
 int free_memory;
 if((int)__brkval == 0)
    free_memory = ((int)&free_memory) - ((int)&__bss_end);
 else
   free_memory = ((int)&free_memory) - ((int)__brkval);
 Serial.print("free size3 = "); Serial.println(free_memory);

 // --- check-memory ---
 Serial.println("");
 Serial.println("--- calculated with check_memory:");
 uint8_t *heapptr, *stackptr;
 uint16_t diff=0;
 stackptr = (uint8_t *)malloc(4);          // use stackptr temporarily
 heapptr = stackptr;                     // save value of heap pointer
 free(stackptr);      // free up the memory again (sets stackptr to 0)
 stackptr =  (uint8_t *)(SP);           // save value of stack pointer
 Serial.print("heap strat = 0x"); Serial.print( (int) heapptr, HEX); Serial.print(" / "); Serial.println( (int) heapptr, DEC);
 Serial.print("stackptr   = 0x"); Serial.print( (int) stackptr, HEX); Serial.print(" / "); Serial.println( (int) stackptr, DEC);
 diff=stackptr-heapptr;
 Serial.print("free size4 = "); Serial.println( (int) diff, DEC);
}

Das funktioniert hier auch einwandfrei auf (m)einem UNO und MEGA.
Allerdings läuft das nicht in der LOOP, sondern erst nach einer speziellen Anfrage via Seriell.
Den restlichen Code hab ich hier mal der übersichtlichkeit weggelassen.
Dazu hab ich mein eigenes Steuerprogramm in Delphi, was aber auch nix anderes wie ein lauschendes Terminal macht und ich kann mit div. Buttons spezielle Befehle absenden ...

Ergebnis auf dem MEGA:
Quote

----- GET MEMORY INFORMATIONS -----
--- max. available memory by hardware ---
ram total  = 8191 bytes
--- DATA ---
data_start = 0x200 / 512
data_end   = 0x566 / 1382
data size  = 870
--- BSS ---
bss_start  = 0x566 / 1382
bss_end    = 0x866 / 2150
bss size   = 768
--- HEAP ---
heap_start =0x866 / 2150
heap_margin = ? ? ?
heap end    = 0x866 / 2150
heap size   = 0
--- STACK ---
stack start = 0x21D2 / 8658
stack end   = 0x21FF / 8703
stack size = 45
--- FREE MEMORY ---
free size1 =  6508
free size2 =  6508

--- calculated with free_memory:
free size3 = 6509

--- calculated with check_memory:
heapptr  = 0x868 / 2152
stackptr = 0x21D2 / 8658
free size4 = 6506

Ergebnis auf dem UNO:
Quote

----- GET MEMORY INFORMATIONS -----
--- max. available memory by hardware ---
ram total  = 2047 bytes
--- DATA ---
data_start = 0x100 / 256
data_end   = 0x46A / 1130
data size  = 874
--- BSS ---
bss_start  = 0x46A / 1130
bss_end    = 0x56C / 1388
bss size   = 258
--- HEAP ---
heap_start =0x56C / 1388
heap_margin = ? ? ?
heap end    = 0x56C / 1388
heap size   = 0
--- STACK ---
stack start = 0x8D7 / 2263
stack end   = 0x8FF / 2303
stack size = 40
--- FREE MEMORY ---
free size1= 875
free size2 = 875

--- calculated with free_memory:
free size3 = 876

--- calculated with check_memory:
heap strat = 0x56E / 1390
stackptr   = 0x8D7 / 2263
free size4 = 873

Was hier jedoch zunächst auffält, das beim max. RAM-Speicher jeweils 1 Byte zur eigentlich gesamt verfügbaren Menge fehlt.

Ebenso beim verfügbaren Speicher:
MEGA: 6508, 6509 oder 6506 - UNO:  875, 876, 873. Siehe Source, wie das jeweils berechnet wird.
Woher stammen denn da die Unterschiede aus 2 bis 3 Bytes, welche sicher NICHT unerheblich sind, wenn Heap und STack zusammenlaufen.
Ein einziges Byte in der Überlappung der Zeiger ist da schon tödlich !

Wenn ich das bisher richtig verstanden habe, wird der Speicher in einem Arduino wie folgt belegt:
Offset(0x200h) --- Data_Start --- Data_End/BSS-Start --- BSS_End/HeapPointer --- Freier Speicher --- StackPointer --- Max_Speicher.

Einiges versteh ich ja schon - aber:
- Warum steht der Speicher im Start beim UNO bei 0x100 und beim MEGA bei 0x200 ?
- Data_End und BSS-Start sollten sich doch eigentlich um 1 (ein) Byte unterscheiden ?
- Dito BSS_End und Heap_Start !
- Warum fehlt am Gesamtspeicher 1 Byte ?  (.... NULL für String_ende ?)
- .... und die 2-3 Byte tödliche Heap-Stack-Differenz siehe oben.

.... ich gönn mir mal nen Gerstensaft und warte auf die wissenden "EggsBährden" ....  :smiley-mr-green:
To young to die - never to old for rock'n roll

michael_x

Quote
Warum steht der Speicher im Start beim UNO bei 0x100 und beim MEGA bei 0x200 ?

Da schau mal in das Datenblatt des atmega328 und atmega2560, was du wohl erwischst wenn du zum Spass
Code: [Select]
Serial.print("Was ist z.B. hier : ");
Serial.println( * (byte*) 0x0004 ); 

machst.
Ich weiss es nicht auswendig, aber es wird schon was sein, und zwar keine "access violation" wie bei richtigen Computern.

Quote
Beim max. RAM-Speicher jeweils 1 Byte zur eigentlich gesamt verfügbaren Menge fehlt


Code: [Select]
Serial.println((int)RAMEND,HEX);
könnte dir die Frage beantworten.
Willst du nun als Ende die Anfangs-Adresse des nächsten oder 1 weniger ?
Je nachdem ist
    size = end - start;
richtig oder falsch. Aber das hast du doch alles selbst programmiert, wieso fragst du ?

P.S. Auch wenn es nicht so klingt, vielen Dank für dein interessantes Testprogramm, ehrlich ;)
Das Bier hast du dir verdient !

jurs


Was hier jedoch zunächst auffält, das beim max. RAM-Speicher jeweils 1 Byte zur eigentlich gesamt verfügbaren Menge fehlt.


Stelle Dir nur mal vor, der RAM-Speicher wäre exakt 1 Byte groß.

Dann wäre "Pointer auf das erste Byte im RAM" und "Pointer auf das letzte Byte im RAM" exakt dieselbe Zahl.
Bildest Du die Differenz der beiden Pointer, wäre das 0.

D.h. Du rechnest in den Bereichen falsch: Die Gesamtgröße eines Bereichs ist "Pointer auf das letzte Byte im Bereich" minus "Pointer auf das erste Byte im Bereich" plus eins!


TERWI

#3
Jul 05, 2013, 06:18 pm Last Edit: Jul 05, 2013, 06:21 pm by TERWI Reason: 1
Eben vergessen:  Was bitte ist "__bss" ? ? ?
Ich hab da schon gesucht, aber ....
Irgendwo (hier) gelesen: Reservierter Stackbereich. Nicht wirklich richtig - oder ?

Wenn ich im Quelltext
//  extern void *__malloc_margin; --> apparently already declared as a size_t
auskommentiere, gibt das Mecker. Warum ?
Braucht man das eigentlich wirklich für was ?

@jurs
Logo ist mir das klar, dass wenn 2 Speichervariablen auf den gleichen Wert gucken / diesen nutzen, es zum Chaos kommt.
Nur warum bitte ist das hier in dem Source eben genau so ? (Ich hab nur abgekupfert ....)
Normalerweise sollte da ein BYTE Differenz sein ! Das erklärt dann auch, warum .....

1 Byte von Data_End zu Bss_Start ....
1 Byte con Bss_end zu Heap_Start ...
und 1 Byte weniger vom Stack-Pointer würde die max. Different erklären.
ABER:
-> Warum wird das denn so nicht ausgegeben ???



... ich taumel im Nirwane mangels Karma ....  8)
To young to die - never to old for rock'n roll

Serenifly

Bei solchen Ausgaben kannst du mal versuchen ob du C++-style Output Streams zum laufen bekommst:
http://playground.arduino.cc/Main/StreamingOutput

Wesentlich komfortabler und lesbarer :)

TERWI

Die Sache mit dem Streaming ist mir auch schon mal über den Weg gelaufen.
Das von dir verlinkte Beispiel nur mit dem template funktioniert bei mir aber nicht mit formatierter Ausgabe .... ich habe dann mal das dort verlinke Streaming gezogen - das tut gut. Macht zudem das Kompilat kleiner und wahrscheinlich auch die Übertragung ne µs schneller.  :.
Und ist logo lesbarer.

Ich hab das ehrlicherweise nicht selbstprogrammiert, sondern wie eingangs hingewiesen, das ganze aus dem Posting mal zusammengefasst.

Verstehen tue ich das aber immer noch nicht wirklich.
Wird doch hier offensichtlich mit variablen/einer funktion hantiert, die extern in Systembibliotheken deklariert sind und vom "Speichermanagement" gesetzt werden.
Wie kann das dann sein, dass __data_end = __bss_start und __bss_end = __heap_start sind ?
Korrekterweise müssten ja auch 2 Byte jeweils dazwischen liegen - hier wird ja mit uint gerechnet.
Denkt sich da die Maschine je 2 Byte auf __bss_start und dann 4 Byte auf __heap_start dazu ? Oder werden die jeweiligen "end"-adressen um 2 Byte decrementiert (was meiner Auffassung eher Sinn macht) ?

Warum gibt diese Formelei
Code: [Select]
free_memory = ((int)&free_memory) - ((int)&__bss_end);
ein Byte mehr aus als die var SP ?
Und die Berechnung nach "// --- check-memory ---" oben im Source für den HP 2 Byte mehr - demzufolge 2 Byte weniger freien speicher ?

Was __bss ist, weiß ich auch noch nicht.
To young to die - never to old for rock'n roll

Serenifly

Schneller wird es dadurch auch nicht. Das ist nur inline Code. Der ersetzt halt das "<<" im Hintergrund durch "print()"

jurs


Normalerweise sollte da ein BYTE Differenz sein ! Das erklärt dann auch, warum .....

1 Byte von Data_End zu Bss_Start ....
1 Byte con Bss_end zu Heap_Start ...
und 1 Byte weniger vom Stack-Pointer würde die max. Different erklären.
ABER:
-> Warum wird das denn so nicht ausgegeben ???


Wahrscheinlich, weil direkt die Pointer aus der Speicherverwaltung verwendet werden.

Die übliche lehrbuchmäßige Verwaltung von dynamischem Speicherbereich sieht so aus:
Wenn "start" und "end" Pointer gleich sind, heißt es "Speicher ist komplett leer", 0 Bytes reserviert
Falls sich die Pointer unterscheiden, dann gilt:
Der "start"-Pointer zeigt auf das erste tatsächlich reservierte Byte
Der "end"-Pointer zeigt dann immer auf das nächste FREIE Byte, das reserviert werden könnte.
Die Differenz zwischen beiden Pointern entspricht immer der Anzahl der reservierten Bytes.

Wenn __data_end und __bss_start denselben Wert haben, dann kann es nichts anderes heißen als dass da die Werte aus der intenen Speicherverwaltung verwendet wurden: __data_end gehört also gerade nicht mehr zum data Bereich dazu. Die Größe des data-Bereichs ergibt sich aus der Differenz __data_end - __data_start.

Und am oberen Ende ist es genau so: RAMEND ist ein Pointer auf das letzte Byte im RAM-Speicher, also das erste Byte, das für den Stackpointer SP reserviert werden kann. Wenn der Stack leer ist ist SP == RAMEND. Mit jedem reservierten Byte vermindert sich SP um eins. SP zeigt also immer auf das erste freie Byte, das als nächstes für den Stack reserviert wird.

Und weil das alles so schulmäßiger Standard ist, ist auch das mit der RAM-Speichergröße klar. Ich rechne mal mit Offset 0, dann würde ein 2 KB RAM-Speicher als Variable ungefähr aussehen wie:
byte ram[2048];
Der Pointer (Index) für das erste Byte im RAM ist 0, das entspricht also __data_start==0.
Der Pointer (Index) für das letzte Byte im RAM ist 2047, das entspricht also RAMEND == 2047
Und die Gesamtgröße des RAM ergibt sich zu (Pointer auf letztes Byte) minus (Pointer auf erstes Byte) plus 1.
RAMEND-(int)&__data_start+1

Man muß eben für die Größenberechnung immer berücksichtigen:
Ist es eine Differenz zwischen zwei Pointern, von denen einer nicht zum reservierten Bereich dazugehört?
Oder ist es eine Differenz zwischen zwei Pointern, die beide zum reservierten Bereich dazugehören?

Was die unterschiedlichen Datensegmente data und bss betrifft, so wird der Unterschied hier erklärt:
http://www.nongnu.org/avr-libc/user-manual/mem_sections.html
Offenbar sind in "data" die "mit Werten inititialisierten Variablen" drin und in "bss" die "nicht initialisierten Variablen", die beim Programmstart einfach standardmäßig ausgenullt sind.

Go Up