Hallo zusammen!
Ich würde gerne auf meinen STM32 sowas wie Apps realisieren.
Auf dem Controller läuft die Ablaufumgebung. Die "Apps" sollen von
SD-Karte dann (an beliebiger freier) ins RAM geladen und von dort
ausgeführt werden. Dabei nutzen die Apps logischer Weise auch Code aus
dem Flash...
Letzeres habe ich mir gedacht löse ich einfach damit, dass ich einfach
alle Funktionspointer beim starten der App aus dem RAM dieser als
Callback mitgebe. Zudem soll die App dann als eigener Task/Thread des
RTOS laufen.
Was mir noch nicht klar ist:
Wie erstelle ich solche Apps? Also wie muss ich sie linken?!? ich weiß
ja nie, an welcher Stelle sprich Speicheradresse die App später im RAM
liegen wird! :-O
Wie rufe ich sie richtig auf?
hoffe ihr könnt mir helfen!?!
Danke und Grüße
Christopher
Rufus Τ. F. schrieb:> Das Stichwort lautet "relocatable code".
Bei der Onlinesuche lande ich mehr oder weniger hier:
https://de.wikipedia.org/wiki/Position-Independent_Code
Und das der Lader die Adressen passen muss...
Und woher weiß ich wo die Adressen im Code liegen, die ich ändern muss?
und worauf beziehen die sich? Offset?
Vincent H. schrieb:> Das Stichwort lautet Lua, Python oder JS...
Auf einem Cortex-M4!?
Guest schrieb:> Welches RTOS benutzt du denn?
FreeRTOS
Christopher schrieb:> Und das der Lader die Adressen passen muss...
Adressen müssen nur angepasst werden, wenn das kein relocatable code
ist. Es gibt Prozessorarchitekturen, bei denen das der Fall ist, da muss
dann der Code mit einer entsprechenden "Patchtabelle" ausgestattet
werden, die der Lader abarbeiten kann. So wird es beispielsweise auf
x86-Systemen gehandhabt.
Andere Prozessorarchitekturen kennen vollständig verschiebbaren Code,
der läuft, egal an welche Speicheradresse er geladen wird (sofern man
gewisse Alignmentvorgaben einhält). Das ist beispielsweise beim 68k der
Fall, oder schon deutlich früher bei 6800 bzw. 6502 möglich gewesen.
Voraussetzung ist halt, daß nicht nur Programmsprünge und -Verzweigungen
relativ zum PC adressiert werden, sondern daß auch Speicherzugriffe
relativ zu einer in ein Prozessorregister geladenen Basisadresse
ausgeführt werden, also der Prozessor entsprechende indirekte und
indizierte Adressierungsarten beherrscht.
ARMe sind wohl nicht mit dieser Designvorgabe entwickelt worden.
Solange immer nur eine "App" laufen soll, ist das kein Problem, da der
Adressbereich fix ist. Ich hatte das schon mal für eine andere
Architektur (S12X) gemacht.
- Im Linkerscript für das "OS" wird das RAM so angegeben, dass der für
die APP benötigte Bereich gar nicht existiert. Um Daten dorthin zu
bekommen, benutzt man einfach Pointer.
- Im Linkerscript für die "App" gibt es nur den einen Bereich, nämlich
den im RAM. Dort landen dann alle Segmente.
- Funktionen im Flash werden über eine Sprungtabelle an festgelegter
Adresse im Flash realisiert. Dazu gibt es passend eine ASM-Library, die
nur die entsprechenden Sprünge zu den Funktionen enthält. Diese Library
wird anstelle der "normalen" Library zu den Apps gelinkt. Die
Sprungtabelle kann man z.B. an den Startup-Code oder die Vektortabelle
anhängen.
Schwieriger wird es, wenn mehrere dieser Apps quasi gleichzeitig laufen
sollen. Hier müsste man wohl oder übel beim Laden relozieren oder der
Controller hat eine MMU bzw. man kann über Bankswitching tricksen
(S12XE).
Die zweite Möglichkeit ist, einen Interpreter für eine höhere
Programmiersprache/Scriptsprache zu benutzen, wie in den Beiträgen
weiter oben.
Als dritte Möglichkeit könnte man auch eine andere Architektur
emulieren. Beim AX81 habe ich z.B. einen Z80 im AVR emuliert, bei meinem
aktuellen Projekt (noch nicht veröffentlicht, da noch lange nicht
fertig) ist es eine PDP11-CPU. Compiler gibt es für diese Architektur ja
noch genug (GCC, CC, PCC). Wenn man dann gleich noch eine MMU mit
implementiert, lassen sich auch ohne Probleme Host-Flash oder I/O mit in
den Adressraum der Gast-CPU mappen.
Jörg
Wenn mehrere Apps nicht nur "quasi" !gleichzeitig! laufen sollen,
brauchst Du mehrere Kerne...
"Beliebige Adressen im RAM" halte ich für extrem aufwändig, weil
letztendlich die fertige App auf dem STM32 gelinkt werden müsste.
Abhängig von der Größe des RAM würden mir jetzt zwei Ideen einfallen:
internes RAM
------------------------
Da die Apps im Vergleich zur Größe der SD-Karte recht winzig sind,
könnte man verschiedene Versionen auf verschiedene Adressen (z.B.
4K-Raster) linken und dann beim Laden entscheiden, welche Version
genommen wird. Wenn man mit Makefiles arbeitet, lässt sich das Erstellen
weitestgehend automatisieren.
externes RAM
------------------------
Hier könnte man mit einee kleinen zusätzlichen Logik eine
Bankumschaltung realisieren. Das "verschwendet" zwar Speicher, ist aber
recht einfach zu realisieren. Vor allen Dingen sind dadurch die
einzelnen Tasks gut voneinander isoliert, wobei eine Fehlfunktion in
einer App ohne MMU/MPU trotzdem das ganze System zerschießen kann.
Wenn das Ganze nicht nur für Dich ist und einigermaßen robust sein soll,
kommst Du meiner Meinung nach um einen Controller mit MMU/MPU oder
Interpreter/Emulation nicht drumherum.
Jörg
Ich hab's noch nie probiert, aber die gängigen ARM-Compiler sollten
eigentlich PIC- (position independent) Code erzeugen können.
Dazu müsstest Du dir dann wohl eine Art Loader schreiben.
Das wird's wohl noch nicht gewesen sein. Zusätzlich muß wahrscheinlich
das Linkerscript angepaßt werden (da kommen ein paar neue Sections
hinzu) und (falls verwendet) müssen Libraries mit -fPIC neu compiliert
werden.
Wie gesagt, auf ARM habe ich's noch nie selbst probiert, aber gehen
müsste das eigentlich schon.
Der gcc kann es im Prinzip, er erzeugt dann eine Global Offset Table und
mehr relative Adressierung. Er akzeptiert zwei äußerst interessante
Optionen
1
-msingle-pic-base
2
Treat the register used for PIC addressing as read-only, rather than loading it in the prologue for each function. The runtime system is responsible for initializing this register with an appropriate value before execution begins.
1
-mpic-data-is-text-relative
2
Assume that the displacement between the text and data segments is fixed at static link time. This permits using PC-relative addressing operations to access data known to be in the data segment. For non-VxWorks RTP targets, this option is enabled by default. When disabled on such targets, it will enable -msingle-pic-base by default.
Aber die Wirkung ist seltsam. Dieses PIC base register ist nicht zu
sehen, er packt dann static const Daten (z.B. Texte) ins RAM, die normal
im Flash landen und die Offset Table ist bei einem 24K Programm nur 48
Byte groß?? Andererseits, warum braucht er überhaupt noch eine Offset
Table?
eagle user schrieb:> Aber die Wirkung ist seltsam.
Wenn ich das richtig sehe, hast Du mit -O2 compiliert?
Da ist wahrscheinlich das meiste, was der gcc für -fPIC "dazugetan" hat,
schon wieder wegoptimiert, weil's in deinem Fall nicht gebraucht wird.
Probier' mal mit -O0, dann sieht man möglicherweise eher, was da anders
ist.
eagle user schrieb:> Dieses PIC base register ist nicht zu sehen, er packt dann static const> Daten (z.B. Texte) ins RAM, die normal im Flash landen
Das aber ist doch logisch, wenn Programme als "Apps" zur Laufzeit ins
RAM geladen werden sollen. Da kann und darf nichts im Flash landen.
o.k., das war missverständlich, ich sollte "text section" statt "Flash"
und "data section" statt "RAM" schreiben. Normal werden solche const
Daten ja direkt aus dem Flash gelesen und nicht ins RAM umkopiert. Warum
müssen die mit -fpic dann zweimal im RAM stehen? Wie doof das ist, sagt
arm-none-eabi-size; normal ist:
1
text data bss dec hex filename
2
24276 0 1696 25972 6574 BUILD/display.elf
und mit pic:
1
text data bss dec hex filename
2
22436 2872 1696 27004 697c BUILD/display.elf
Markus F. schrieb:
> Probier' mal mit -O0, dann sieht man möglicherweise eher, was da> anders ist.
Ich compile immer mit -Os, der Größenunterschied zu -O0 oder -O2 ist
immens (-O0 -fpic):
1
text data bss dec hex filename
2
35551 4072 1696 41319 a167 BUILD/display.elf
und dieses Spezialregister sehe ich immer noch nicht:
Christopher schrieb:> Ich würde gerne auf meinen STM32 sowas wie Apps realisieren.> Auf dem Controller läuft die Ablaufumgebung. Die "Apps" sollen von> SD-Karte dann (an beliebiger freier) ins RAM geladen und von dort> ausgeführt werden.
Ich schätze mal, daß dein Projekt in die Hose gehen wird.
Zunächst brauchst du resident im µC eine Art Betriebssystem, das die HW
von den Apps trennt und verwaltet. Das ist der einfachste Teil. Ich
hatte sowas vor langer Zeit in der Lernbetty vorexerziert.
Als API hat man da bei den ARM's den SVC, also den Supervisor-Call. Das
ist ne recht effiziente Sache, bei der die App garnicht wissen muß, wo
die angeforderte OS-Leistung erbracht wird. Allerdings ist der GCC für
sowas zu blöd. Da sollte man den Keil nehmen, der beherrscht das
prächtig. Wenn man partout den GCC nehmen will, braucht man nen
Assembler-Wrapper dafür (gibt's auch bereits in der Lernbetty).
Alles, was bisher an Sprungleisten und so genannt wurde, ist dagegen nur
umständlich und bringt nur zusätzliche Abhängigkeiten zwischen OS und
App ins Spiel.
So, das war der leichte Teil.
Der weitaus schwierigere Teil ist die Forderung nach mehreren Apps zu
gleicher Zeit im RAM. Das läuft auf ein selbstdefiniertes EXE-Format für
die Apps hinaus, um dort die benötigten Relokations-Informationen
unterzubringen. Dazu gehört natürlich auch eine Art Nachbrenner nach dem
Linker, um so eine EXE-Datei zu generieren. Macht ne Menge Arbeit.
Von sowas wie positionsunabhängigem Code würde ich heftigst die Finger
lassen - wer sich mit dem Eröffnungsbeitrag schon an das Forum wenden
muß, wird das General-Geeiere im Code der Apps zum Auseinanderhalten von
positionsabhängigen Daten und positionsunabhängigen Daten (jeweils
inclusive Pointer-Turnerei) eher nicht erfolgreich absolvieren können.
W.S.
W.S. schrieb:
> Der weitaus schwierigere Teil ist die Forderung nach mehreren Apps zu> gleicher Zeit im RAM. Das läuft auf ein selbstdefiniertes EXE-Format für> die Apps hinaus, um dort die benötigten Relokations-Informationen> unterzubringen. Dazu gehört natürlich auch eine Art Nachbrenner nach dem> Linker, um so eine EXE-Datei zu generieren.
Das ELF-Format sollte für den Zweck ausreichen und der gcc hat
anscheinend auch den passenden Nachbrenner dazu. Jedenfalls funktioniert
es hier soweit, dass die LED blinkt und printf() über ein UART ausgeben
kann.
Das ging so:
* es gibt ein Blinkprogramm, das ab 0x20002000 im RAM läuft
* man übersetzt es neu mit -fpic -msingle-pic-base
* es wird deutlich größer, aber läuft immer noch ab 0x20002000
* man lädt die unveränderte Hex-Datei nach 0x20003000
* es funktioniert immer noch
Ja, das ist nur die halbe Miete. Globale Variablen funktionieren nicht,
statische vielleicht auch nicht. Aber dafür haben gcc/ld eine global
offset table und ein "base register(?)" (r9) eingebaut und printf()
funktioniert. Jetzt muss man "nur" noch die Feinheiten verstehen, z.B.
wie man r9 initialisieren muss. Das kann ja erst nach dem Laden von der
SD-Karte passieren.
Im Flash steht momentan nur eine Art Boot Loader, der ein gültiges (CRC
usw.) Programm im RAM sucht und startet.
Eine Hex-Datei enthält ja normalerweise feste Adressen, auch wenn sie
mit -fpic erzeugt wurde. Mein Hex-Loader ignoriert die und lädt den
unveränderten Inhalt woanders hin (also jetzt für den Versuch).
W.S. schrieb:
> Alles, was bisher an Sprungleisten und so genannt wurde, ist dagegen nur> umständlich und bringt nur zusätzliche Abhängigkeiten zwischen OS und> App ins Spiel.
Ja, es ist umständlich, aber wo wäre der Unterschied zum SVC? Man muss
der "App" ja auch beibringen, welcher SVC was macht; die Abhängigkeit
bleibt uns. Ich mache es so, dass die "App" nur Funktionen sieht, auch,
wenn ein SVC drin steckt. Tatsächlich habe ich aus einem Funktionsaufruf
per Sprungleiste einen SVC gemacht, ohne dass eine "App" etwas gemerkt
hätte.
Der SVC entspricht, soweit ich das verstanden habe, dem was "früher"
Software-Interrupts und Traps waren.
Damit wechselt man beim Aufruf der System-Routine aus dem User- in den
Supervisormodus. Richtig Nutzen hat man davon erst, wenn eine MMU/MPU
mit im Spiel ist. Dann kann man nämlich dafür sorgen, dass eine "App"
nur ihren eigenen Speicherbereich "sieht" und z.B. nicht auf die
Hardware-Register oder Speicher anderer Tasks zugreifen kann.
Global Offset Table (GOT) muss auf den Anfang vom BSS zeigen. Die
eigentliche Berechnung könnte man einfach in den Startup-Code
integrieren. Also (aktuelle PC-Position) + (Größe Code-Segment) + (Größe
Data-Segment) - (bereits "verbrauchte" Bytes im Startup-Code, das müsste
man einmalig aus dem Disassembly des Startup-Codes bestimmen) -> R9
Jörg
Joerg W. schrieb:> Die> eigentliche Berechnung könnte man einfach in den Startup-Code> integrieren. Also (aktuelle PC-Position) + (Größe Code-Segment) + (Größe> Data-Segment) - (bereits "verbrauchte" Bytes im Startup-Code, das müsste> man einmalig aus dem Disassembly des Startup-Codes bestimmen) -> R9
Das läßt man m.E. besser das Linker-Script machen, wenn man keine
unliebsamen Überraschungen mag.
Joerg W. schrieb:
> Der SVC entspricht, soweit ich das verstanden habe, dem was> "früher" Software-Interrupts und Traps waren.> Damit wechselt man beim Aufruf der System-Routine aus dem User- in den> Supervisormodus.
Das ist in gewisser Weise eine Nebenwirkung, für die App ist es vor
allem eine Art Unterprogrammaufruf bei dem der Aufrufer die Zieladresse
nicht kennen muss.
> Richtig Nutzen hat man davon [vom SVC] erst, wenn eine MMU/MPU> mit im Spiel ist. Dann kann man nämlich dafür sorgen, dass eine "App"> nur ihren eigenen Speicherbereich "sieht" und z.B. nicht auf die> Hardware-Register oder Speicher anderer Tasks zugreifen kann.
Und ich sag' euch, das ist so dermaßen angenehm, wenn z.B. Null-Pointer
sofort abgefangen werden und keine Folgefehler haben. Die unerlaubte
Adresse und die des "bösen" Befehls werden angezeigt und stimmen fast
immer auf das Byte genau. Das geht auch schon mit der MPU vom
Cortex-M0+; den M0 ohne + sollte man garnicht mehr einsetzen.
> Global Offset Table (GOT) muss auf den Anfang vom BSS zeigen. Die> eigentliche Berechnung könnte man einfach in den Startup-Code> integrieren. Also (aktuelle PC-Position) + (Größe Code-Segment) + (Größe> Data-Segment) - (bereits "verbrauchte" Bytes im Startup-Code, das müsste> man einmalig aus dem Disassembly des Startup-Codes bestimmen) -> R9
Warum nicht einfach (Start Code-Segment) + (Größe Code-Segment) + (Größe
Data-Segment)?
Markus F. schrieb:
> Das läßt man m.E. besser das Linker-Script machen, wenn man keine> unliebsamen Überraschungen mag.
Man braucht wohl beides. Im Linker-Script vergibt man Namen für z.B.
(Start des BSS) und der Startup-Code rechnet die aktuelle Position der
App dazu und lädt R9. Das gleiche muss auch für den Stack Pointer
gemacht werden.
eagle user schrieb:> Das ELF-Format sollte für den Zweck ausreichen und der gcc hat> anscheinend auch den passenden Nachbrenner dazu. Jedenfalls funktioniert> es hier soweit, dass die LED blinkt und printf() über ein UART ausgeben> kann.>> Das ging so:> * es gibt ein Blinkprogramm, das ab 0x20002000 im RAM läuft> * man übersetzt es neu mit -fpic -msingle-pic-base> * es wird deutlich größer, aber läuft immer noch ab 0x20002000> * man lädt die unveränderte Hex-Datei nach 0x20003000> * es funktioniert immer noch>> Ja, das ist nur die halbe Miete. Globale Variablen funktionieren nicht,> statische vielleicht auch nicht. Aber dafür haben gcc/ld eine global> offset table und ein "base register(?)" (r9) eingebaut und printf()> funktioniert. Jetzt muss man "nur" noch die Feinheiten verstehen, z.B.> wie man r9 initialisieren muss. Das kann ja erst nach dem Laden von der> SD-Karte passieren.>> Im Flash steht momentan nur eine Art Boot Loader, der ein gültiges (CRC> usw.) Programm im RAM sucht und startet.>> Eine Hex-Datei enthält ja normalerweise feste Adressen, auch wenn sie> mit -fpic erzeugt wurde. Mein Hex-Loader ignoriert die und lädt den> unveränderten Inhalt woanders hin (also jetzt für den Versuch).
Könntest du den Code dazu mal hochladen, würde mich interressieren.
mfg
Felix F. schrieb:> Könntest du den Code dazu mal hochladen, würde mich interressieren.
Der stammt von einem anderen Programm. Die crt0 daraus hab' ich für
diesen Versuch missbraucht, sie heisst jetzt vdrive/crt-vdrive.c. Das
soll mal ein Boot Loader werden. Der allererste Versuch dazu steht in
boot/ bzw. im Flash. vdrive/BUILD/vdrive.s19 wird ins RAM geladen und
von boot gestartet.
vdrive wird immer nach 0x20002000 gelinkt, im letzten Schritt erzeugt
tools/elf-to-srec aus dem vdrive.elf ein vdrive.s19. Normal auch auf
0x20002000, aber mit "export PIC_OFFSET=0x1000" vor dem make bekommt das
.s19 Adressen ab 0x20003000.
Alles maximal unübersichtlich, viel Spaß damit...