2009-06-06 13 views
15

Ich habe ein Problem in einem C-Programm auf einem AVR-Mikrocontroller (ATMega328P) festgestellt. Ich glaube, es liegt an einer Stapel/Heap-Kollision, aber ich möchte das bestätigen können.Wie kann ich die Speicherbelegung (SRAM) eines AVR-Programms visualisieren?

Gibt es eine Möglichkeit, SRAM-Nutzung durch den Stapel und den Heap zu visualisieren?

Hinweis: Das Programm ist mit avr-gcc kompiliert und verwendet avr-libc.

Update: Das eigentliche Problem, das ich habe, ist, dass die Malloc-Implementierung fehlschlägt (NULL zurückgibt). Alle malloc geschieht beim Start und alle free geschieht am Ende der Anwendung (was in der Praxis nie ist, da der Hauptteil der Anwendung in einer Endlosschleife ist). Ich bin mir sicher, dass Fragmentierung nicht das Problem ist.

+3

Wow. Sie müssen die einzige Person sein, die jemals Malloc auf einem Atmega verwendet hat. Ich bin überrascht, dass sie überhaupt funktionieren! Das war noch nie so, dass man es miteinbezieht. – Myforwik

Antwort

8

Sie sagen, malloc ist versagen und Rückkehr NULL:

Die offensichtliche Ursache, die man auf den ersten Blick sollte, ist, dass Ihr Haufen „voll“ ist - das heißt, der Speicher Sie malloc gefragt haben nicht zugeordnet werden können, weil es ist nicht verfügbar.

Es gibt zwei Szenarien im Auge zu behalten:

ein: Sie haben einen 16 K Haufen, haben Sie malloced bereits 10 K und Sie versuchen, eine weitere 10K malloc. Ihr Heap ist einfach zu klein.

b: Häufiger haben Sie einen 16 k Heap, Sie haben eine Reihe von malloc/free/realloc Calls gemacht und Ihr Heap ist weniger als 50% "voll": Sie rufen malloc für 1K und es scheitert - Was geht? Antwort - Der freie Speicherbereich des Heapspeichers ist fragmentiert. Es gibt keinen zusammenhängenden freien Speicher von 1 KB, der zurückgegeben werden kann. C Heap-Manager können den Heap nicht komprimieren, wenn dies geschieht, also sind Sie im Allgemeinen in einer schlechten Art und Weise. Es gibt Techniken, um Fragmentierung zu vermeiden, aber es ist schwierig zu wissen, ob dies wirklich das Problem ist. Sie müssen Logging-Shims zu malloc und free hinzufügen, damit Sie eine Vorstellung davon bekommen, welche dynamischen Speicheroperationen ausgeführt werden.

EDIT:

Sie sagen alle mallocs beim Start passieren, so Fragmentierung ist nicht das Problem.

In diesem Fall sollte es einfach sein, die dynamische Zuweisung durch statische zu ersetzen.

altes Codebeispiel:

char *buffer; 

void init() 
{ 
    buffer = malloc(BUFFSIZE); 
} 

neuer Code:

char buffer[BUFFSIZE]; 

Sobald Sie dies überall getan haben, Ihre LINKER sollten Sie warnen, wenn alles, was nicht in den Speicher verfügbar passen.Vergessen Sie nicht, die Größe des Heapspeichers zu reduzieren. Beachten Sie jedoch, dass einige Laufzeit-Systemfunktionen den Heapspeicher weiterhin verwenden können, sodass Sie ihn möglicherweise nicht vollständig entfernen können.

+0

+1 Es ist sehr wahrscheinlich ein) Alle mallocing geschieht beim Start und alle Freigabe erfolgt am Ende der Anwendung (was in der Praxis nie ist, da der Hauptteil der Anwendung in einer Endlosschleife ist). Also bin ich sicher, Fragmentierung ist nicht das Problem. –

+1

This ist ein sehr wichtiger Punkt! Bitte, hör auf diese kleinen Hinweise in den Kommentaren fallen und fügen Sie so viele Details wie möglich, um die Frage selbst! – Artelius

+0

@Artelius - Haben Sie das auf die Frage hinzugefügt. Danke. –

2

Der übliche Ansatz wäre, den Speicher mit einem bekannten Muster zu füllen und dann zu überprüfen, welche Bereiche überschrieben werden.

+0

Das ist eine sehr hacky Sache zu tun. Ich würde persönlich den Befehl avr-size command + freeRam() empfehlen, anstatt solch drastische Maßnahmen zu ergreifen ... –

2

Wenn Sie sowohl Stack als auch Heap verwenden, kann es etwas komplizierter sein. Ich werde erklären, was ich getan habe, wenn kein Heap verwendet wird. In der Regel haben alle Unternehmen, für die ich gearbeitet habe (im Bereich der Embedded C-Software), die Verwendung von Heap für kleine eingebettete Projekte vermieden, um die Unsicherheit der Verfügbarkeit von Heapspeicher zu vermeiden. Wir verwenden stattdessen statisch deklarierte Variablen.

Eine Methode besteht darin, den größten Teil des Stapelbereichs beim Start mit einem bekannten Muster (z. B. 0x55) zu füllen. Dies geschieht in der Regel durch einen kleinen Codecode in der Softwareausführung, entweder direkt am Anfang von main() oder vielleicht sogar vor main() im Startcode. Achten Sie darauf, die zu diesem Zeitpunkt verwendete kleine Stapelanzahl nicht zu überschreiben. Überprüfen Sie nach dem Ausführen der Software für eine Weile den Inhalt des Stack-Speicherplatzes und sehen Sie, wo die 0x55 noch intakt ist. Wie Sie "inspizieren", hängt von Ihrer Zielhardware ab. Angenommen, Sie haben einen Debugger angeschlossen, dann können Sie einfach den laufenden Mikro stoppen und den Speicher lesen.

Wenn Sie über einen Debugger verfügen, der einen Speicherzugriffshaltepunkt ausführen kann (etwas ausgefallener als der übliche Ausführungshaltepunkt), können Sie einen Haltepunkt an einer bestimmten Stapelposition festlegen, z. B. am weitesten vom Stapelspeicherbereich entfernt . Das kann sehr nützlich sein, weil es Ihnen auch genau zeigt, welches Bit des Codes gerade ausgeführt wird, wenn dieses Ausmaß der Stack-Nutzung erreicht wird. Aber es erfordert, dass Ihr Debugger die Speicherzugriffs-Haltepunktfunktion unterstützt, und sie wird oft nicht in den "Low-End" -Debuggern gefunden.

Wenn Sie auch Heap verwenden, kann es etwas komplizierter sein, da es unmöglich ist vorherzusagen, wo Stack und Heap kollidieren.

0

Wenn Sie den Code für Ihren Heap bearbeiten können, könnten Sie ihn mit ein paar zusätzlichen Bytes (knifflig bei so niedrigen Ressourcen) für jeden Speicherblock auffüllen. Diese Bytes könnten ein bekanntes Muster enthalten, das sich vom Stapel unterscheidet. Dies könnte Ihnen einen Hinweis geben, ob es mit dem Stapel kollidiert, indem Sie es innerhalb des Stapels erscheinen sehen oder umgekehrt.

+0

Das Muster könnte in der freien Funktion überprüft werden, aber es wird immer noch schwierig sein herauszufinden, wann der Fehler auftrat. Beachten Sie auch, dass manchmal zusätzlicher Stack-Speicherplatz für lokale Variablen reserviert ist, die möglicherweise nicht verwendet werden (abhängig vom Compiler/Code). In diesem Fall könnten die Muster unverändert bleiben, während der Heap noch immer beschädigt ist. – Ron

1

Angenommen, Sie verwenden nur einen Stack (also kein RTOS oder ähnliches) und der Stack befindet sich am Ende des Speichers, wird er größer, während der Heap nach der BSS/DATA-Region beginnt. Ich habe Implementierungen von malloc gesehen, die den Stackpointer tatsächlich überprüfen und bei einer Kollision fehlschlagen. Du könntest versuchen, das zu tun.

Wenn Sie den malloc-Code nicht anpassen können, können Sie den Stack am Anfang des Speichers platzieren (mithilfe der Linker-Datei). Im Allgemeinen ist es immer eine gute Idee, die maximale Größe des Stapels zu kennen/definieren. Wenn Sie es am Anfang eingeben, erhalten Sie einen Fehler beim Lesen über den Anfang des RAM hinaus.Der Heap wird am Ende sein und kann wahrscheinlich nicht über das Ende hinauswachsen, wenn es eine vernünftige Implementierung ist (wird stattdessen NULL zurückgeben). Gute Sache ist, dass Sie 2 getrennte Fehlerfälle für 2 separate Probleme haben.

Um die maximale Stapelgröße herauszufinden, könnten Sie Ihren Speicher mit einem Muster füllen, die Anwendung ausführen und sehen, wie weit es ging, siehe auch die Antwort von Craig.

+0

Die Malloc-Implementierung schlägt fehl (NULL wird zurückgegeben). Das Problem, das ich habe, ist, dass ich nicht sicher bin, dass es eine Kollision ist, die es verursacht ... –

+0

Eine Kollision würde in der Regel sehr seltsame Dinge wie die Rückkehr zur falschen Funktion oder Daten ändern oder plötzlich außerhalb des RAM/FLASH und führen einen Adressierungsfehler haben. Wenn Ihre Anwendung "normal" aussieht, handelt es sich wahrscheinlich nicht um eine Kollision. Um dies zu debuggen, setzen Sie einen Haltepunkt, wo der NULL zurückgegeben wird (oder wenn Sie die Malloc-Funktion selbst debuggen können, setzen Sie den Haltepunkt dort ist noch besser). An diesem Punkt überprüfen Sie den Stackpointer und sehen Sie, ob die Kollision aufgetreten ist. Ist Ihr Heap auch definiert als "welcher Speicher wird nicht verwendet?" Oder können Sie die Heap-Größe festlegen? – Ron

+0

Ich habe leider nicht den Luxus eines Debuggers ... –

3

Verwenden Sie nicht die Heap/dynamische Zuordnung für eingebettete Ziele. Vor allem mit einem Prozessor mit solchen begrenzten Ressourcen. Ordnen Sie Ihre Anwendung vielmehr neu, da das Problem mit dem Wachstum Ihres Programms erneut auftritt.

0

Auf Unix-ähnlichen Betriebssystemen ermöglicht eine Bibliotheksfunktion namens sbrk() mit einem Parameter von 0 den Zugriff auf die oberste Adresse des dynamisch zugewiesenen Heapspeichers. Der Rückgabewert ist ein void * -Zeiger und könnte mit der Adresse einer beliebigen zugewiesenen Stapelvariablen verglichen werden.

Das Ergebnis dieses Vergleichs sollte mit Vorsicht verwendet werden. Abhängig von der CPU- und Systemarchitektur kann der Stapel von einer beliebigen hohen Adresse abwachsen, während der zugewiesene Stapel aus dem niedrig gebundenen Speicher nach oben bewegt wird.

Manchmal hat das Betriebssystem andere Konzepte für die Speicherverwaltung (d. H. OS/9), die Heap und Stack in verschiedenen Speichersegmenten im freien Speicher ablegen. Auf diesen Betriebssystemen - insbesondere für Embedded-Systeme - müssen Sie im Voraus die maximalen Speicheranforderungen Ihrer -Anwendungen definieren, damit das System Speichersegmente mit übereinstimmenden Größen zuweisen kann.

+2

Der ATMega328P läuft nicht mit Linux/BSD (er hat nur 2K RAM und ich betreibe überhaupt kein Betriebssystem) und ja, der Stack wird kleiner und der Heap wächst. –

+0

AVR-Linie hat nicht das Konzept der Speicher geschützten Blöcke, so sichere Lösungen von "großen" CPUs sind nicht immer posisible. Zum Beispiel in x86 Zeile 286 hat teilweise, und 386 hat vollständige Umsetzung von geschützten Speicherbereichen. Jahre 386 geben 'Renaissance 'der neuen Generation OS, mit kompletten Windows (nicht-DOS), Linux, Minix etc .... wahrscheinlich am meisten geschützt auf 286 war OS/2, Totally Safe OS (Kernel) ist unmöglich auf AVR –

17

Sie RAM überprüfen können statische Verwendung avr-size Dienstprogramm, decribed wie in
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=62968,
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=82536,
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=95638,
und http://letsmakerobots.com/node/27115

avr-size -C -x Filename.elf

(avr-size Dokumentation: http://ccrma.stanford.edu/planetccrma/man/man1/avr-size.1.html)

Folgt ein Beispiel dafür, wie diese auf einer IDE zu setzen: auf Code :: Blocks, Projekt -> Optionen Erstellen -> Pre/Post Schritte bauen -> Post-Erstellungsschritte umfassen:

avr-size -C $(TARGET_OUTPUT_FILE) oder
avr-size -C --mcu=atmega328p $(TARGET_OUTPUT_FILE)

Beispielausgabe am Ende Baujahr:

AVR Memory Usage 
---------------- 
Device: atmega16 

Program: 7376 bytes (45.0% Full) 
(.text + .data + .bootloader) 

Data:   81 bytes (7.9% Full) 
(.data + .bss + .noinit) 

EEPROM:  63 bytes (12.3% Full) 
(.eeprom) 

Daten sind Ihre SRAM-Nutzung, und es ist nur der Betrag, der der Compiler bei der Kompilierung kennt. Sie brauchen auch Platz für Dinge, die unter Runtime (insbesondere Stack-Nutzung) erstellt wurden.

Um Stackverbrauch (dynamischer RAM) zu überprüfen, von http://jeelabs.org/2011/05/22/atmega-memory-use/

Hier ist ein kleines Programm, Funktion, wie viel RAM bestimmt ist derzeit nicht verwendet:

int freeRam() { 
    extern int __heap_start, *__brkval; 
    int v; 
    return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
} 

Und hier ist eine Skizze, die Code verwendet:

void setup() { 
    Serial.begin(57600); 
    Serial.println("\n[memCheck]"); 
    Serial.println(freeRam()); 
} 

Die Funktion freeRam() gibt zurück, wie viele Bytes zwischen dem Ende von th vorhanden sind e Heap und der letzte belegte Speicher auf dem Stack, so ist es effektiv, wie viel der Stack/Heap wachsen kann, bevor sie kollidieren.

Sie könnten die Rückgabe dieser Funktion um Code überprüfen, von dem Sie vermuten, dass er Stapel/Heap-Kollision verursacht.

+2

+1 - Danke für die Links! Beachten Sie, dass die Funktion 'freeRam()', wie hier definiert, fehlschlägt, wenn der freigegebene Speicher in der 'freien Liste' von malloc gehalten wird. Dies ist in der Praxis wahrscheinlich nur dann ein Problem, wenn der Speicher häufig dynamisch zugewiesen und freigegeben wird - wahrscheinlich ein seltenes Szenario auf einem AVR (in meinem Fall bin ich zum Beispiel während der Programminitialisierung nur dynamisch dem 'freeRam()' zuzuordnen Code funktioniert für mich). –

+0

Haben Sie Hinweise zur Funktion freeRam? Wenn ich mit O3 kompiliere, funktioniert es nicht mehr gut. Ich zeige den Wert nach einem Itoa an. (Debug funktioniert gut O1) – BennX

+0

@BennX Ich habe es nur bei O0 und O1 verwendet, und es hat gut funktioniert. Aber Sie sind sich wahrscheinlich bewusst, dass das Debuggen bei O3 schwieriger ist, daher würde ich vorschlagen, auf eine niedrigere Optimierungsstufe zu wechseln, während Ihr Programm nicht felsenfest ist - obwohl Sie wahrscheinlich einige Teile davon kommentieren müssen, wenn Sie kleine Mikrocontroller verwenden . – mMontu

Verwandte Themen