Frage

In der eingebetteten Software mit mehreren Threads (geschrieben in C oder C ++) muss ein Thread genügend Stapelraum erhalten, damit er seine Vorgänge ohne Überlauf abschließen kann. Die korrekte Größe des Stacks ist in einigen in Echtzeit eingebetteten Umgebungen von entscheidender Bedeutung, da das Betriebssystem (zumindest in einigen Systemen, mit denen ich zusammengearbeitet habe) dies nicht für Sie erkennt.

Normalerweise wird die Stapelgröße für einen neuen Thread (außer dem Hauptfaden) zum Zeitpunkt des Erstellens des Threads bezeichnet (dh in einem Argument an pThread_create () oder dergleichen). Oft sind diese Stapelgrößen für Werte, die zum Zeitpunkt des ursprünglich geschriebenen oder getesteten Codes bekannt sind, hart codiert.

Zukünftige Änderungen des Codes brechen jedoch häufig die Annahmen, auf denen die hartcodierten Stapelgrößen basieren, und ein schicksalhafter Tag tritt Ihr Thread in einen der tieferen Zweige seines Anrufdiagramms ein und überläuft den Stapel-und bringt das gesamte System herunter oder schweigend korrupten Gedächtnis.

Ich habe dieses Problem persönlich in dem Fall gesehen, in dem Code, der im Thread ausgeführt wurde, Strukturinstanzen auf dem Stapel deklariert. Wenn die Struktur erweitert wird, um zusätzliche Daten zu halten, blüht die Stapelgröße entsprechend auf und ermöglicht möglicherweise Stapelüberläufe. Ich kann mir vorstellen, dass dies ein großes Problem für etablierte Codebasen sein könnte, bei denen die vollständigen Auswirkungen des Hinzufügens von Feldern zu einer Struktur nicht sofort bekannt werden können (zu viele Threads/Funktionen, um alle Orte zu finden, an denen diese Struktur verwendet wird).

Da die übliche Antwort auf "Stapelgrößen" -Fragen "sie nicht tragbar" ist, nehmen wir an, dass der Compiler, das Betriebssystem und der Prozessor für diese Untersuchung bekannte Mengen sind. Nehmen wir auch an, dass eine Rekursion nicht verwendet wird, sodass wir uns nicht mit der Möglichkeit befassen, ein "unendliches Rekursion" -Szenario zu haben.

Was sind einige zuverlässige Möglichkeiten, um die erforderliche Stapelgröße für einen Thread abzuschätzen? Ich würde Methoden, die offline (statische Analyse) und automatisch sind, bevorzugen, aber alle Ideen sind willkommen.

War es hilfreich?

Lösung

Laufzeitbewertung

Eine Online -Methode besteht darin, den vollständigen Stapel mit einem bestimmten Wert wie 0xaaaa (oder 0xaa, unabhängig von Ihrer Breite) zu malen. Dann können Sie überprüfen, wie groß der Stapel in der Vergangenheit maximal gewachsen ist, indem Sie prüfen, wie viel von dem Gemälde unberührt bleibt.

Sich ansehen Dies Link für eine Erklärung mit Illustration.

Der Vorteil ist, dass es einfach ist. Ein Nachteil ist, dass Sie nicht sicher sein können, dass Ihre Stapelgröße die Menge an gebrauchten Stack während Ihres Tests nicht überschreitet.

Statische Bewertung

Es gibt einige statische Überprüfungen und ich denke, es gibt sogar eine gehackte GCC -Version, die versucht, dies zu tun. Das einzige, was ich Ihnen sagen kann, ist, dass statische Überprüfung im allgemeinen Fall sehr schwierig ist.

Schauen Sie sich auch an Dies Frage.

Andere Tipps

Sie können ein statisches Analyse -Tool wie verwenden Stackanalyzer, wenn Ihr Ziel den Anforderungen entspricht.

Wenn Sie erhebliches Geld ausgeben möchten, können Sie ein kommerzielles statisches Analyse -Tool wie Klocwork verwenden. Obwohl Klocwork hauptsächlich darauf abzielt, Software -Defekte und Sicherheitslücken zu erkennen. Es verfügt jedoch auch über ein Tool namens "kwstackoverflow", mit dem der Stapelüberlauf innerhalb einer Aufgabe oder eines Threads erfasst werden kann. Ich benutze für das eingebettete Projekt, an dem ich arbeite, und ich habe positive Ergebnisse erzielt. Ich denke nicht, dass ein solches Werkzeug perfekt ist, aber ich glaube, diese kommerziellen Werkzeuge sind sehr gut. Die meisten Werkzeuge, die ich mit Funktionszeigern kämpfte. Ich weiß auch, dass viele Compiler -Anbieter wie Green Hills jetzt ähnliche Funktionen direkt in ihre Compiler aufbauen. Dies ist wahrscheinlich die beste Lösung, da der Compiler über alle Details verfügt, die erforderlich sind, um genaue Entscheidungen über die Stapelgröße zu treffen.

Wenn Sie Zeit haben, können Sie sicher eine Skriptsprache verwenden, um Ihr eigenes Tool für Stapelüberlaufanalyse zu erstellen. Das Skript müsste den Einstiegspunkt der Aufgabe oder des Threads identifizieren, einen vollständigen Funktionsaufrufbaum erstellen und dann die Menge des Stapelraums berechnen, den jede Funktion verwendet. Ich vermute, dass wahrscheinlich kostenlose Tools verfügbar sind, mit denen eine vollständige Funktionsanrufbaum generiert werden kann, die es einfacher machen sollte. Wenn Sie die Einzelheiten Ihrer Plattform kennen, die den Stapelraum generieren, kann jede Funktion sehr einfach sein. Beispielsweise ist die erste Montageanweisung einer PowerPC -Funktion häufig das Speicherwort mit Aktualisierungsanweisung, der den Stapelzeiger an den für die Funktion benötigten Betrag anpasst. Sie können die Größe in Bytes direkt von der ersten Anweisung übernehmen, wodurch der gesamte Stapelraum relativ einfach ermittelt wird.

Diese Arten von Analysen geben Ihnen alle eine Annäherung an die schlimmste Obergrenze für die Stapelverwendung, was genau das ist, was Sie wissen möchten. Natürlich beschweren sich Experten (wie die, mit denen ich arbeite), dass Sie zu viel Stapelraum bereitstellen, aber sie sind Dinosaurier, die sich nicht um eine gute Softwarequalität kümmern :)

Eine andere Möglichkeit, obwohl die Stapelverwendung nicht berechnet wird, wäre die Verwendung der Speicherverwaltungseinheit (MMU) Ihres Prozessors (falls er eine), um den Stapelüberlauf zu erkennen. Ich habe dies auf VXWorks 5.4 mit einem PowerPC gemacht. Die Idee ist einfach. Stellen Sie einfach eine Seite mit schreibgeschütztem Speicher ganz oben in Ihrem Stapel ein. Wenn Sie überlaufen, tritt eine Prozessor -Execption auf und Sie werden schnell auf das Problem des Stapelüberlaufs aufmerksam gemacht. Natürlich sagt es nicht, wie viel Sie benötigen, um die Stapelgröße zu erhöhen. Wenn Sie jedoch mit Debugging -Ausnahme-/Kerndateien gut sind, können Sie zumindest die Aufrufsequenz herausfinden, die den Stapel überflutete. Sie können diese Informationen dann verwenden, um Ihre Stapelgröße angemessen zu erhöhen.

-djhaus

Nicht frei, aber Deckung führt statische Analyse des Stapels.

Die statische (Offline-) Stapelprüfung ist nicht so schwierig, wie es scheint. Ich habe es für unsere eingebettete IDE implementiert (Schnelligkeit)-Derzeit funktioniert es für ARM7 (NXP LPC2XXX), CORTEX-M3 (STM32 und NXP LPC17XX), X86 und unser internes MIPS ISA-kompatibler FPGA-Soft-Core.

Im Wesentlichen verwenden wir einen einfachen Analyse des ausführbaren Code, um die Stapelverwendung jeder Funktion zu bestimmen. Die wichtigste Stapelzuweisung erfolgt zu Beginn jeder Funktion; Sehen Sie einfach, wie es sich mit unterschiedlichen Optimierungsstufen und gegebenenfalls den Arm-/Daumenanweisungssätzen usw. verändert. Denken Sie auch daran, dass Aufgaben normalerweise ihre eigenen Stapel haben, und ISRs teilen sich oft (aber nicht immer) einen separaten Stapelbereich!

Sobald Sie die Verwendung jeder Funktion haben, ist es ziemlich einfach, einen Call-Baum aus dem Analyse aufzubauen und die maximale Verwendung für jede Funktion zu berechnen. Unsere IDE generiert Scheduler (effektive dünne Rtosen) für Sie, sodass wir genau wissen, welche Funktionen als „Aufgaben“ bezeichnet werden und welche ISRs sind, sodass wir für jeden Stapelbereich die schlimmste Nutzung für jeden Stapelbereich erkennen können.

Natürlich sind diese Zahlen fast immer über dem tatsächlich maximal. Denken Sie an eine Funktion wie sprintf das kann a verwenden viel des Stapelraums, variiert jedoch enorm ab, abhängig von der Formatzeichenfolge und den von Ihnen bereitgestellten Parametern. Für diese Situationen können Sie auch dynamische Analyse verwenden - füllen Sie den Stapel mit einem bekannten Wert in Ihrem Startup und laufen Sie dann eine Weile im Debugger, pausieren Sie eine Pause und sehen Sie, wie viel von jedem Stapel noch mit Ihrem Wert gefüllt ist (Tests mit hohem Wasserzeichenstil). .

Keiner der beiden Ansätze ist perfekt, aber das Kombinieren von beiden gibt Ihnen ein ziemlich gutes Bild davon, wie die reale Verwendung aussehen wird.

Wie in der Antwort auf diese Frage, Eine gemeinsame Technik besteht darin, den Stapel mit einem bekannten Wert zu initialisieren und den Code für eine Weile auszuführen und zu sehen, wo das Muster stoppt.

Dies ist keine Offline -Methode, sondern für das Projekt, an dem ich arbeite, haben einen Debug -Befehl, der die Hochwassermarke für alle Aufgabenstapel innerhalb der Anwendung liest. Dies gibt eine Tabelle der Stapelverwendung für jede Aufgabe und die verfügbare Menge an verfügbaren Kopffreien aus. Wenn Sie diese Daten nach einem 24 -Stunden -Lauf mit viel Benutzerinteraktion überprüfen, können wir sicher sein, dass die definierten Stapelzuweisungen "sicher" sind.

Dies funktioniert mit der gut versuchten Technik, um die Stapel mit einem bekannten Muster zu füllen, und unter der Annahme, dass der einzige Weg, wie dies neu geschrieben werden kann am wenigsten deine Sorgen!

Wir haben versucht, dieses Problem bei meiner Arbeit auf einem eingebetteten System zu lösen. Es wurde verrückt, es gibt einfach zu viel Code (sowohl unsere eigenen und dritten Party -Frameworks), um eine zuverlässige Antwort zu erhalten. Glücklicherweise war unser Gerät Linux basiert, sodass wir auf das Standardverhalten zurückgegangen sind, jedem Thread 2 MB zu geben und den virtuellen Speichermanager die Verwendung optimieren zu lassen.

Unser einziges Problem mit dieser Lösung war eines der Drittanbieter -Tools, die eine durchgeführt haben mlock auf seinem gesamten Speicherraum (idealerweise zur Verbesserung der Leistung). Dies führte dazu, dass alle 2 MB Stapel für jeden Thread seiner Fäden (75-150 von ihnen) aufgebaut wurden. Wir haben die Hälfte unseres Speicherraums verloren, bis wir sie herausgefunden haben und die beleidigende Zeile kommentierten.

SIDENOTE: Linux 'Virtual Memory Manager (VMM) weist RAM in 4K -Stücken zu. Wenn ein neuer Thread nach 2 MB Adressraum für seinen Stapel fragt, weist der VMM allen außer der obersten Seite falsche Speicherseiten zu. Wenn der Stapel in eine falsche Seite wächst, erkennt der Kernel einen Seitenfehler und tauscht die falsche Seite mit einem echten (der einen weiteren 4K tatsächlichen RAM konsumiert). Auf diese Weise kann der Stapel eines Threads auf jede Größe wachsen, die er benötigt (solange er weniger als 2 MB ist), und der VMM sorgt dafür, dass nur eine minimale Menge an Speicher verwendet wird.

Abgesehen von einigen der bereits vorgenommenen Vorschläge möchte ich darauf hinweisen, dass Sie in eingebetteten Systemen häufig die Stapelnutzung fest steuern müssen, da Sie die Stapelgröße in angemessener Größe halten müssen.

In gewissem Sinne ist die Verwendung von Stapelraum ein wenig wie das Zuweisen von Speicher, aber ohne (einfache) Möglichkeit, festzustellen, ob Ihre Zuweisung erfolgreich ist, so dass die Nutzung von Stapeln nicht kontrolliert wird, führt ein für immer Schwierigkeiten, herauszufinden, warum Ihr System wieder abstürzt. Wenn Sie also beispielsweise ein System für lokale Variablen aus dem Stapel Speicher zuweisen, wenden Sie diesen Speicher entweder mit malloc () oder, wenn Sie Malloc () Ihren eigenen Speicherhandler nicht verwenden können (was eine einfache Aufgabe ist).

No-No:

void func(myMassiveStruct_t par)
{
  myMassiveStruct_t tmpVar;
}

Ja ja:

void func (myMassiveStruct_t *par)
{
  myMassiveStruct_t *tmpVar;
  tmpVar = (myMassiveStruct_t*) malloc (sizeof(myMassicveStruct_t));
}

Scheint ziemlich offensichtlich, aber oft ist es nicht - besonders wenn man malloc () nicht benutzen kann.

Natürlich werden Sie immer noch Probleme haben, also ist dies nur etwas zu helfen, aber Ihr Problem löst nicht. Es wird Ihnen jedoch helfen, die Stapelgröße in Zukunft zu schätzen, da Sie nach einigen Codeänderungen nach einigen Code -Änderungen, sobald Sie eine gute Größe für Ihre Stapel gefunden haben Andere Probleme (zu tiefe Anrufstapel für einen).

Nicht 100% sicher, aber ich denke, das kann auch getan werden. Wenn Sie einen JTAG -Anschluss exponiert haben, können Sie eine Verbindung zu Trace32 herstellen und die maximale Stapelnutzung überprüfen. Dazu müssen Sie jedoch eine anfängliche, ziemlich große willkürliche Stapelgröße geben.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top