Hallo,
Bei einem Mikrocontroller gibt es ja, soweit mein rudimentäres Wissen,
die Bereiche: text, bss, data, heap und stack.
Die Größe des Stack und des Heap kann man selber festlegen, innerhalb
gewisser Rahmenbedingungen natürlich. Dies kann ich im Linker Script
*.ld durchführen.
Sofern der Code aus dem Ram ausgeführt werden soll, können auch die
anderen Segment in Startadresse und Länge frei konfiguriert werden?
Quasi:
.text Segment beginnt an Adresse ... und hat die Länge ... .
.bss Segment beginnt an Adresse ... und hat die Länge ... .
.data Segment beginnt an Adresse ... und hat die Länge ... .
.heap Segment beginnt an Adresse ... und hat die Länge ... .
.stack Segment beginnt an Adresse ... und hat die Länge ... .
Geht das und wo wird dieses "Setting" gemacht? Linker Script *.ld bei
gnu Compiler / Linker? Die Frage gilt insbesondere für das .text
Segment.
Peter schrieb:> Bei einem Mikrocontroller gibt es ja, soweit mein rudimentäres Wissen,> die Bereiche: text, bss, data, heap und stack.
Diese Segmente gelten für die Anwendung, nicht für den Mikrocontroller
selbst. Der hat nur Adressbereiche, die irgendwie auf Flash, RAM etc.
abgebildet sind.
Das Linkerscript gibt an, welche Segment der Anwendung in welchen
Speicherbereich des Controllers gelegt werden soll.
Normalerweise ergibt sich Größe der Segmente text, bss und data aus dem
Quelltext. Eine manuelle Größenvorgabe erscheint mir wenig Sinnvoll.
heap und stack teilen sich üblicherweise den gesamten übrigen RAM, der
nicht fest vergeben ist. Der Heap beginnt an der ersten freien RAM zelle
und wächst nach oben, während der Stack am oberen Ende des RAM Beginnt
und nach unten wächst (oder umgekehrt).
Bei heap und stack kann man Größenbeschränkungen einstellen, welche von
der Toolchain beim Bauen des Programm grob* überprüft werden. Sie sind
in der Regel aber keine harte Grenze, da wie gesagt der ganze freie RAM
dynamisch für Heap und Stack aufgeteilt wird.
Bei PC's sieht das anders aus, da bekommst du normalerweise einen
Programmabbruch, wenn das Programm mehr Stack oder Heap belegt, als es
darf. Wobei meisten nur der Stack begrenzt ist.
*) Wobei ich die Prüfung für wenig hilfreich halte, denn die Toolchain
weiß zu wenig vom Verhalten der Anwendung zur Laufzeit.
Peter schrieb:> .text Segment beginnt an Adresse ... und hat die Länge ... .
Programm und Daten belegen soviel RAM wie sie eben belegen, es ist wenig
sinnvoll zur Länge Angaben zu machen - gibt man zu wenig an, läuft das
Programm nicht (bzw. wird garnicht erst gelinkt), gibt man zuviel an ist
der Platz verschwendet. Zum Stack ist zu beachten, dass der immer von
oben herab belegt wird, jedenfalls ist mir noch nie eine Architektur
begegnet, bei der ein Push die Adresse nicht dekrementiert. Daher
bewegen sich Heap und Stack i.A. aufeinander zu. Die mögliche Kollision
kann man zwar überwachen, aber was soll ein Embedded Controller denn für
eine Konsequenz ziehen ausser die Arbeit einzustellen bzw. neu zu
starten?
Georg
Peter schrieb:> Bei einem Mikrocontroller gibt es ja, soweit mein rudimentäres Wissen,> die Bereiche: text, bss, data, heap und stack.
Nö, der µC hat von solchen Bereichen keinerlei Ahnung. Diese Bereiche
(es gibt noch ein paar mehr) sind mehr so Konventionen, nach denen die
Ergebnisse von Kompilern halbwegs organisiert einsortiert werden, um
dann letztlich auf den tatsächlich verfügbaren Speicher des µC verteilt
zu werden.
> Sofern der Code aus dem Ram ausgeführt werden soll, können auch die> anderen Segment in Startadresse und Länge frei konfiguriert werden?
Grundsätzlich ja.
> Geht das und wo wird dieses "Setting" gemacht? Linker Script *.ld bei> gnu Compiler / Linker?
Genau, dafür ist der Linker zuständig. Wie der nun genau konfigieriert
wird, hängt wiederum vom Linker ab. Und welche Konfiguration sinnvoll
ist, hängt von der Funktion des Programms und dem Zielsystem ab.
Stefanus F. schrieb:> heap und stack teilen sich üblicherweise den gesamten übrigen RAM, der> nicht fest vergeben ist. Der Heap beginnt an der ersten freien RAM zelle> und wächst nach oben, während der Stack am oberen Ende des RAM Beginnt> und nach unten wächst (oder umgekehrt).
Das kann man frei festlegen. Es gibt auch Controller mit mehreren
unzusammenhängenden Speicherbänken (z.B. SAM3), da kann man dann Heap
und Stack unterschiedlich verteilen. Ob das sinnvoll ist, hängt vom
Programm ab...
> Bei heap und stack kann man Größenbeschränkungen einstellen, welche von> der Toolchain beim Bauen des Programm grob* überprüft werden. Sie sind> in der Regel aber keine harte Grenze, da wie gesagt der ganze freie RAM> dynamisch für Heap und Stack aufgeteilt wird.
...aber auf diese Weise bekommt man bei Überschreitung der reservierten
Speicherbereiche möglicherweise direkt eine Exception, oder man kann der
MPU die Arbeit zumindest erleichtern, wenn man denn will.
Die "data-bss-heap-stack"-Reihenfolge ist nur Konvention. Es kann auch
durchaus sinnvoll sein, einen Block fixer Größe für data und heap oben
in den Adressraum zu legen und den Stack darunter beginnen zu lassen.
Dann knallt ein Stack-Overflow immer und Heap-Overflows kann man in
_brk() feststellen.
> Bei PC's sieht das anders aus, da bekommst du normalerweise einen> Programmabbruch, wenn das Programm mehr Stack oder Heap belegt, als es> darf. Wobei meisten nur der Stack begrenzt ist.
Sowohl Stack als auch Heap sind auf PCs durch das Betriebssystem
vorgegeben, und beide sind (mit unterschiedlichen Grenzen) begrenzt.
Allerdings bekommt man statt "Speicher ist alle" im Zweifelsfall eher
ein SIGKILL, denn reale Anwendungen sind notorisch schlecht darin, mit
fehlendem Speicher umzugehen.
Du kannst ja testweise mal das Overcommitment im Linux-Kernel
abschalten. :-)
> *) Wobei ich die Prüfung für wenig hilfreich halte, denn die Toolchain> weiß zu wenig vom Verhalten der Anwendung zur Laufzeit.
Dafür gibt es eine MPU und Guard-Pages (da die MPU nicht bytegenau ist).
Die Prüfung erfolgt sinnvollerweise durch Hardware, aber die zu
prüfenden Parameter kommen von der Anwendung.
Die Toolchain prüft nur, ob die Segmente auch in die zugewiesenen
Speicher passen (d.h. text+rodata und data+bss, heap und stack werden
nicht geprüft). Schlägt das fehl, läuft das Programm garantiert nicht.
georg schrieb:> Die mögliche Kollision> kann man zwar überwachen, aber was soll ein Embedded Controller denn für> eine Konsequenz ziehen ausser die Arbeit einzustellen bzw. neu zu> starten?
Das wäre oft durchaus eine bessere Option, als einfach irgendwas
unvorhergesehenes zu machen, weil Variablen sich gegenseitig
überschreiben.
S. R. schrieb:> Die "data-bss-heap-stack"-Reihenfolge ist nur Konvention. Es kann auch> durchaus sinnvoll sein, einen Block fixer Größe für data und heap oben> in den Adressraum zu legen und den Stack darunter beginnen zu lassen.> Dann knallt ein Stack-Overflow immer und Heap-Overflows kann man in> _brk() feststellen.
Die klassische Reihenfolge hat allerdings den Vorteil, dass man den
verfügbaren RAM am besten nutzt. Bei fixer Größe für Heap und Stack
bleibt immer ein gewisser Verschnitt, und der Stack kann z.B.
überlaufen, obwohl noch ein Haufen RAM frei ist.
@Stefanus F. (stefanus)
>*) Wobei ich die Prüfung für wenig hilfreich halte, denn die Toolchain>weiß zu wenig vom Verhalten der Anwendung zur Laufzeit.
Kommt drauf an. Wenn man keine dynamische Speicherverwaltung hat, ist
der Speicherbedarf des Stacks eindeutig definiert, denn man hat nur
konstante, globale Variabeln und lokale Variablen auf dem Stack. Die
Reihenfolge der Funktionsaufrufe ist auch aus dem Quelltext
analysierbar, selbst mit Interrupt.
Einzig bei rekursiven Funktionen wird es nicht vorhersagbar, denn sooo
schlau ist der Compiler eher nicht, daß er die Programmlogik analysieren
und die maximale Rekursionstiefe allein berechnen kann.
Falk B. schrieb:> Kommt drauf an. Wenn man keine dynamische Speicherverwaltung hat, ist> der Speicherbedarf des Stacks eindeutig definiert
Unsinn. Offensichtliche Gegenanzeigen (für jeden echten Programmierer
sofort als solche erkennbar):
- rekursive Algorithmen jeglicher Art
- zur Laufzeit festgelegte Funktionen (sprich: die Verwendung von
Funktionszeigern)
> Einzig bei rekursiven Funktionen wird es nicht vorhersagbar, denn sooo> schlau ist der Compiler eher nicht, daß er die Programmlogik analysieren> und die maximale Rekursionstiefe allein berechnen kann.
Naja, immerhin. Einen offensichtlichen Knackpunkt dann doch noch selber
gefunden. Aber nicht erkannt:
1) Ein Knackpunkt reicht als Gegenargument bereits völlig aus!
2) mit Funktionszeigern kann man immer auch Rekursionen erzeugen, von
denen der Kompiler nicht einmal die Chance hat, den Rekursioncharakter
zu erkennen, geschweige denn die maximale Tiefe der Rekursion...
Also ist es, wie Stefanus F. (sinngemäß) völlig korrekt gesagt hat: im
Prinzip alles huppse...