Hallo zusammen,
ich habe nun schon mehrmals gelesen, dass man auf dynamische
Speicherveraltung bei Steuergeräten eher verzichtet. Begründet wurde
dies meistens mit der Fragmentierung des Speichers und das man damit
kein deterministisches Verhalten mehr hat.
Verstehen kann ich das allerdings nicht so ganz. Kann mir das vielleicht
mal einer näher erläutern oder gibt es generell Gründe dafür oder
dagegen.
Danke!
Gruß Mike
Die zwei einfachen Gründe sind dass man einen Algorithmus braucht, der
den Speicher vergibt (was nicht ganz trivial ist) und dass man bei CPUs
ohne MMU tatsächlich Probleme mit der Fragmentierung bekommt.
Mike schrieb:> Verstehen kann ich das allerdings nicht so ganz.
Wenn der Speicher fragmentiert ist, sagen wir mal, in 32Byte Blöckchen,
getrennt durch vergebene Blöckchen, und du versuchst 128Byte zu
reservieren...
Was passiert dann?
Richtig, er nimmt sichs am oberen Ende.
Und da kommt ihm der Stack entgegen.
Heap <-> Stack Kollisionen drohen.
Nein, das möchte ich in keinem Steuergerät sehen!
Schon gar nicht beim Airbus, der gerade zu den Malediven fliegt.
Und auch nicht bei einem ABS System, während einer Vollbremsung.
Die Fragmentierung entsteht so:
Stell Dir vor du hast insgesamt 50 Bytes RAM frei und belegst 10 mal 2
Bytes, und dann 1 mal 10 Bytes:
AABBCCDDEEFFGGHHIIJJKKKKKKKKKK
Und dann gibst du jeden zweiten Block frei:
AA..CC..EE..GG..II..KKKKKKKKKK
Danach hast du einen fragmentierten Speicher. Du hast zwar insgesamt
noch 10 Bytes frei, kannst aber höchstens 2 Bytes am Stück belegen.
Ob solche Lücken entstehen, und ob diese Lücken zu klein sind, hängt
natürlich sehr vom jeweiligen Programm ab. Fragmentierung ist daher
nicht zwangsläufig ein Problem.
Vor vielen Jahren hatte ich dieses Problem auf meinem Windows 98 Rechner
beobachtet. Größere Programme liessen sich irgendwann nicht mehr
starten, dann musste man rebooten und es ging wieder. Ich vermute, dass
Microsoft inzwischen eine clevere Lösung dagegen gefunden hat.
Bei dieser Veranschaulichung habe ich vernachlässigt,
a) dass die Speicherverwaltung selbst auch Speicher benötigt und
b) dass der Heap je nach Systemarchitektur mit dem Stack kollidieren
kann und
c) Speicher meisst in größeren Blöcken vergeben wird
Genaue Details dazu kannst du in der Doku der jeweiligen C Library
nachlesen.
U. F. schrieb:> Wenn der Speicher fragmentiert ist, sagen wir mal, in 32Byte Blöckchen,> getrennt durch vergebene Blöckchen, und du versuchst 128Byte zu> reservieren...> Was passiert dann?> Richtig, er nimmt sichs am oberen Ende.> Und da kommt ihm der Stack entgegen.>> Heap <-> Stack Kollisionen drohen.
Das verstehe ich natürlich. Vielleicht muss ich meine Frage etwas anders
formulieren. Angenommen ich habe ein Programm, dass 10 mal calloc
aufruft und ich bin mir sicher, dass es keine Kollision zwischen Heap
und Stack geben kann. Das Programm ruft bei jedem Start exakt 10 mal
calloc auf und immer in der gleichen Init Funktion. Ich weiss also zur
Compilezeit, dass genau 10 Instanzen angelegt werden. Gibts hier Gründe
für dynamische Speicherverwaltung?
Wenn du schon vor der Laufzeit weisst, wie viel Speicher das Programm
brauchen wird, dann belege ihn besser statisch. Denn dann kann der
Compiler das auch prüfen und "unerklärliche" Fehler sind bei künftigen
Änderungen ausgeschlossen.
Moin,
Mike schrieb:> Gibts hier Gründe> für dynamische Speicherverwaltung?
Ich wuesste keine Gruende fuer gewaltsamen calloc() Einsatz, nichtmal
bei "ausgebetteten" Systemen, wenn von vorneherein feststeht, wie gross
der Platzbedarf an Speicher wird.
Gruss
WK
Der Sinn von malloc() und calloc() ist ja gerade, dass man sie verwendet
wenn man NICHT vorher weiß wieviel Speicher zur Laufzeit benötigt werden
wird.
Bei einem Webshop weißt Du zum Beispiel vorher nicht, wie viele und
welche Artikel der Kunde in seinen Warenkorb legen wird.
Bei einem Steuergerät für Automotive, Luft- und Raumfahrt etc. weiß man
in aller Regel vorher, wieviel Speicher man wofür braucht.
Wenn man verschiedene speicherintensive Aufgaben hat, die aber nie
zugleich ausgeführt werden, dann kann man das statt über malloc auch mit
einem entsprechend groß dimensionierten Stack lösen. Der fragmentiert
dann nicht.
Muß man nur drauf achten, daß der Compiler die entsprechenden Funktionen
nicht inlined. Gerade bei GCC sollte man das mit entsprechenden
Funktionsattributen erzwingen.
Im Übrigen halte ich es ohnehin für Wahnsinn, den Heap auf den Stack
zuwachsen zu lassen. Oder den Stack auf den Heap, bzw. bei Abwesenheit
des Heaps auf die globalen Variablen. Den Heap legt man nach ganz oben,
den Stack nach ganz unten (bei descending stack). Wenn man einen Stack
Overflow kriegt, dann hat man kein undefiniertes Systemverhalten, das
womöglich zunächst einmal gar nicht auffällt, sondern einen klaren
Absturz. Wenigstens weiß man dann, daß da irgendwo ein Bug ist.
Mike schrieb:> ich habe nun schon mehrmals gelesen, dass man auf dynamische> Speicherveraltung bei Steuergeräten eher verzichtet. Begründet wurde> dies meistens mit der Fragmentierung des Speichers und das man damit> kein deterministisches Verhalten mehr hat.> Verstehen kann ich das allerdings nicht so ganz. Kann mir das vielleicht> mal einer näher erläutern oder gibt es generell Gründe dafür oder> dagegen.
Die Fragmentierung ist das eine, aber bei einem Steuergerät dürfen in
der Regel Teile der Funktion nicht einfach temporär mal nicht tun, nur
weil grad der Speicher voll ist. Es muss für den Worst-Case für alle
Situationen genug Speicher da sein. Dann kann man aber auch gleich alles
statisch allokieren und braucht gar keinen dynamischen Speicher.
Nop schrieb:> Im Übrigen halte ich es ohnehin für Wahnsinn, den Heap auf den Stack> zuwachsen zu lassen. Den Heap legt man nach ganz oben, den Stack nach> ganz unten (bei descending stack). Wenn man einen Stack> Overflow kriegt, dann hat man kein undefiniertes Systemverhalten, das> womöglich zunächst einmal gar nicht auffällt, sondern einen klaren> Absturz. Wenigstens weiß man dann, daß da irgendwo ein Bug ist.
Was ist denn bei einem µC ein "klarer Absturz"? Je nach Prozessor gibt's
evtl. auch einfach einen Wrap-around, und der Stack läuft vom
Speicherende runter weiter. Oder auf z.B. einem AVR läuft er zuerst in
die I/O-Register, dann in die ALU-Register rein. Gibt bestimmt auch
lustige Effekte.
Rolf M. schrieb:> Was ist denn bei einem µC ein "klarer Absturz"?
Dasselbe wie bei jedem anderen Prozessor auch: eine fault exception. Auf
Cortex-M beispielsweise hat man einen hard fault, wenn man unterhalb des
RAMs reinschreibt.
Dann gehts in den exception handler, um erstens den Fehler zu
signalisieren und zweitens zu rebooten. Immer noch besser, als mit einem
undefinierten Zustand weiterzulaufen. Wobei der handler für einen stack
overflow nicht ganz trivial ist, das geht nur in Assembler.
> Je nach Prozessor gibt's> evtl. auch einfach einen Wrap-around, und der Stack läuft vom> Speicherende runter weiter.
Kann natürlich auch sein. Wenn der Prozessor sowas halt nicht
unterstützt, dann hilft nur noch das Erstellen eines call trees, der die
benötigte Stackgröße ausgibt. Was zumindest dann geht, wenn man keine
Rekursion verwendet. Den Stackbedarf der ISRs muß man natürlich auch
noch draufschlagen.
> Oder auf z.B. einem AVR läuft er zuerst in> die I/O-Register, dann in die ALU-Register rein.
Ja, das zusammen damit, daß der Stack dann auch noch nach unten wächst,
finde ich das kein besonders glückliches Hardwaredesign. Allerdings,
wenn man z.B. nur 512 Bytes an SRAM hat, dann werden das ohnehin nur
sehr kleine Anwendungen sein, die man dann auch von Hand noch
analysieren kann.
Ich ziehe halt MCUs vor, bei denen ich da ein klares Fehlerverhalten
kriegen kann. Zumal der Compiler einem auch noch dazwischenhauen kann,
wenn der bei Optimierung auf Größe mit GCSE arbeitet und man dann
abenteuerliche Verschachtelungen kriegen kann.