Hallo,
für das Nachfolgeprojekt von meinem ChipBasic wollte ich die Möglichkeit
schaffen, dass man auch Anwenderprogramme für den AVR in C schreiben
kann. Allerdings bin ich mir nicht sicher, ob das überhaupt möglich ist.
Denn dazu müsste man dem Compiler bestimmte Dinge vorgeben bzw.
vorgaukeln. Das (Betriebs-) System selbst ist in ASM geschrieben und
muss die Programme (mit derzeit maximal 9K Größe) an 8 verfügbare
Programmplätze laden können. Ich habe zwar einige Erfahrung in C (GCC),
aber halt nur auf dem PC.
1. Der Code muss relozierbar sein. Dazu habe ich bereits einen
Relocator, der beim Speichern des Programmes in das Flash die Befehle
CALL,JMP und LPM der entsprechenden Startadresse anpasst. Bei
Tabellenzugriffen über das Z Register sehe ich dabei aber keine Chance.
Es gibt zwar eine API-Funktion, die die Startadresse des Programmes
ermittelt und im Z-Register zurückgibt aber ich weiss nicht ob sich die
so nutzen lässt.
2. Der nutzbare RAM-Bereich muss begrenzbar sein, außerdem darf das
Programm keinen eigenen Stack benutzen. Letzteres könne ich vielleicht
realisieren, indem ich vom Relocator Direktzugriffe auf den Stackpointer
z.B. in NOP umwandle.
3. Es gibt mind. 6 Register (im Bereich R0-R15), die nicht genutzt
werden dürfen da die Video-Engine sie benötigt.
4. Man müsste zumindest eine neue STDIO schreiben, damit die Ausgaben
auch auf dem Bildschirm landen.
Ist so etwas überhaupt realisierbar ohne den Compiler zu modifizieren?
Jörg
Joerg Wolfram schrieb:> Ist so etwas überhaupt realisierbar ohne den Compiler zu modifizieren?
Nein.
Ein Beispiel: Der Compiler bringt lokale Variable im Stack unter. Dazu
ist es nötig, dass er in den Stackpointer schreibt.
Aber auch andere Bedingungen sind nicht erfüllbar.
Hallo Joerg,
so ganz will mir nicht einleuchten, was das ganze bringen soll. Wäre es
nicht einfacher, die Videoausgabe mit einer C-Schnittstelle zu versehen
und eine Library zu erstellen, die der Anwender zu seinem Code linken
kann?
Ansonsten ruft deine Anwendung nach einem RTOS-System, das u.a.
einzelnen Prozessen eigene Stacks zuordnet. Das Relozieren könnte man
dem gcc-Linker überlassen. Da musst du einmal schauen, welche
Möglichkeiten einem Skripte ermöglichen und das Ganze in eine
entsprechende toolchain integrieren. Vielleicht kann man z.B. den Linker
dazu bringen, den Code für alle 8 Programmplätze zu erzeugen (und auch
das RAM entsprechend aufzuteilen) und dein Loader pickt sich dann das
passende Teil-Stück heraus.
Um das Sichern der Register für deine Videofunktion kommst du aber nicht
herum, wenn du den Compiler nicht komplett neu schreiben willst.
Gruß, DetlevT
Danke erstmal für die schnelle Antwort.
Den Stack kann das Programm schon benutzen, aber eben halt "blind", nur
mit PUSH/POP/CALL/RET.
Aber letztendlich muss es auch nicht gehen, es war nur so eine Idee...
Gruß Jörg
Hallo Joerg,
mal so eine Idee: Es muss ja nicht C und avr-gcc sein. Wenn du eine Art
Programmier-Lern-System machen willst, kannst du mit deiner Erfahrung ja
vielleicht einen Compiler für eine (einfache) Sprache selbst schreiben,
der am Ende Assembler ausspuckt. Das wäre so eine Art Preprozessor, wo
du die Umsetzung selbst unter Kontrolle hast. RJMP und RCALL muss man
nicht relozieren und an die aktuelle Adresse kommt man z.B. via rcall
und POP der Rücksprungadresse vom Stack.
Oder es kommt so eine Art p-Code heraus, den dein AVR interpretiert.
Dann kann das Programm auch im RAM stehen.
Gruß, DetlevT
Hallo Detlev,
wenn ich aus dem Code eine dazulinkbare Library machen würde, würde ich
vielleicht anderen einen Gefallen tun, aber mir selbst nur Steine in den
Weg legen da ich selbst das System nicht nochmal in C nachprogrammieren
möchte und auch nicht vorhabe die darauf laufenden Anwendungen in C zu
programmieren.
Also müssen sich die Anwendungen nach dem System und seinen
Schnittstellen richten und nicht umgekehrt. Es ging mir einfach darum,
ob neben BASIC und ASM auch C zur Erstellung von Anwenderprogrammen
möglich ist. Und das scheint halt nicht zu gehen.
Gruß Jörg
Nein, ein Programmierlern-System wird es nicht, eher eine Art "AVR-PAD".
Aber die Idee mit dem P-Code ist gut, vielleich kann man ja aus C-Code
mit einer Art "Compiler" den Bytecode erstellen, mit dem auch die
Runtime des BASIC-"Interpreters" läuft.
Gruß Jörg
Joerg Wolfram schrieb:> Den Stack kann das Programm schon benutzen, aber eben halt "blind", nur> mit PUSH/POP/CALL/RET.
Genügt nicht, größere Datenmengen auf dem Stack benötigen eine
Stackpointermanipulation. Alles, was du willst ist doch, dass
die Applikation sich den initialen Stackpointerwert nicht setzen
darf, das sollte möglich sein (durch Modifizieren des Startup-
Codes).
Im Prinzip müsstest du deine Applikation zur dynamischen Bibliothek
linken und dann einen Lader schreiben, der die verbliebenen Dinge
zur Laufzeit aktualisiert. Das wird aber nicht ganz trivial sein,
außerdem hat für den AVR-GCC ganz sicher noch keiner die Optionen
-fpic und -fPIC tatsächlich implementiert.
Du bist dir aber auch im Klaren, dass du für die Applikationen
jedesmal aus dem Bootloaderbereich heraus den Flash-Inhalt beschreiben
musst, ja?
Joerg Wolfram schrieb:> 1. Der Code muss relozierbar sein.
Möglicherweise ist das ELF-File relozierbar, indem die nötigen
Relocations Record da noch drin sind - oder es lässt sich vielleicht
beim Linken so einrichten. In diesem Fall würde es reichen, einen
relozierenden Loader mit dem ELF-File an Stelle des Hex/Bin-Files zu
füttern.
A. K. schrieb:> oder es lässt sich vielleicht> beim Linken so einrichten.
Genau das wäre der Sinn einer dynamischen Bibliothek (shared object).
Aber wie schon geschrieben, der Compiler müsste dafür position
independent code generieren können, und das wird für den AVR keiner
wirklich bis zu Ende implementiert haben. Die Optionen -fpic und
-fPIC existieren zwar, aber es wird niemand garantieren können, dass
der sich ergebende Code tatsächlich zur Laufzeit relozierbar ist
noch dass der Compiler nicht mit einem internal compiler error
aussteigt (weil halt notwendige Teile vielleicht gar nicht implementiert
sind).
Jörg Wunsch schrieb:> Aber wie schon geschrieben, der Compiler müsste dafür position> independent code generieren können
Nicht unbedingt. Solcher -fPIC-Code ist ohne Modifikation verschiebbar.
Es gibt aber auch EXE-Formate, die weiterhin Relokationsinformation
enthalten. Damit wird es dem OS-Loader ermöglicht, den Code an jede
beliebige Adresse zu laden, indem er in anhand dieser Relokationen Code
und Daten anpasst. Solcher Code wird für eine präferierte Adresse
gelinkt, kann aber auch mit leichtem Zusatzaufwand zum Ladezeitpunkt an
jede andere geladen werden. Eine spezielle Codeerzeugung ist dabei nicht
erforderlich. Ich glaube Windows arbeitet so, jedenfalls aber tat dies
OS/2.
Jörg Wunsch schrieb:> Aber wie schon geschrieben, der Compiler müsste dafür position> independent code generieren können, und das wird für den AVR keiner> wirklich bis zu Ende implementiert haben.
Nein, muss er für diese Anwendung nicht.
Position Independent Code ist bei gängigen "grossen" Betriebssystemen
nötig, damit alle inneren Referenzen in der Library (also z.B. Jumps
bei Verzweigungen, aber noch vieles, vieles mehr) bereits beim Linken
der Shared Library aufgelöst werden können (bzw. schon beim Assemblieren
eine Konstante ergeben), und beim Laden der Lib nur noch Referenzen auf
andere (externe) Shared Libraries reloziert werden müssen.
Dass man das so macht hat aber nichts damit zu tun, dass es anders
garnicht funktionieren würde. Natürlich kann man grundsätzlich auch
alle Relokationen auf den Zeitpunkt des Ladens der Lib verschieben.
Der Grund warum man stattdessen Position Independent Code verwendet
(trotzdem er auf manchen Architekturen mit leichten Performanceeinbußen
verbunden ist) ist, dass innere Referenzen so zahlreich und dicht im
Code vorkommen, dass so gut wie keine Pages in der Lib mehr ohne
Relokationen auskämen. Letzteres würde dazu führen, dass von dem
"Shared" zumindest im Arbeitsspeicher nicht mehr viel übrigbleiben würde
(da der Code durch die Relokation geändert wird, muss von der Page eine
Kopie für den einzelnen Prozess angelegt werden), was natürlich dem
ganzen Zweck von Shared Libaries zuwiderliefe.
Für die oben genannten Anforderungen ist das aber alles irrelevant, und
deshalb wird kein PIC benötigt. Es müssen dann eben alle Relokationen
beim Laden statt beim Linken endgültig durchgeführt werden. Evtl. kann
man das noch dahingehend verfeinern (damit der Loader auf dem AVR
kleiner wird), dass die eigentliche Relokation (z.B. mit Basisadresse 0)
bereits vom Linker durchgeführt wird, aber (durch ein selbst zu
schreibendes Tool) zusätzlich noch eine Tabelle erstellt wird, in der
festgehalten wird, an welchen Stellen noch die Basisadresse zu addieren
ist. Dann braucht sich der Loader zumindest nicht mehr mit dem Auflösen
von Symbolen rumschlagen, sondern muss nur noch die Tabelle einmal
durchgehen und überall den Offset addieren. Die relative Lage der
Symbole untereinander kann sich ja nicht mehr ändern, es wird nur alles
en bloc im Adressraum verschoben.
BTW kamen Shared Libraries im a.out-Format damals unter Linux auch ohne
PIC aus. Stattdessen wurde dort die Ladeadresse der Lib bereits beim
Linken der Lib endgültig festgelegt. Da der von der Lib verwendete
Adressraum nicht mit anderen Libraries überlappen durfte, gab es eine
zentrale Registry, über die man Adressraum für seine Library
registrieren musste.
Letzteres würde quasi dem Vorschlag entsprechen, für jede mögliche
Position eine eigene Kopie des Codes zu linken, und dann im Loader das
passende Stück herauszupicken.
Andreas
Andreas Ferber schrieb:> (da der Code durch die Relokation geändert wird, muss von der Page eine> Kopie für den einzelnen Prozess angelegt werden), was natürlich dem> ganzen Zweck von Shared Libaries zuwiderliefe.
Alternativ wird der Adressraum einer bestimmten DLL/SharedLib bei ersten
Laden global vergeben. Dann ist Sharing problemlos, aber dieser Teil des
Adressraum ist dann auch bei Prozessen blockiert, die sie garnicht
verwenden.
Andreas Ferber schrieb:> BTW kamen Shared Libraries im a.out-Format damals unter Linux auch ohne> PIC aus.
Ja, aber das war ein ziemlicher Horror. SunOS 4.x hat mit a.out
vorgemacht, dass man das auch besser machen kann. ;-) Aber das
geht jetzt hier vom Thema weg.
Ich denke, dass das Ganze für einen AVR ohnehin etwas, naja,
oversized ist.
Joerg Wolfram schrieb:> Hallo,>> für das Nachfolgeprojekt von meinem ChipBasic wollte ich die Möglichkeit> schaffen, dass man auch Anwenderprogramme für den AVR in C schreiben> kann. Allerdings bin ich mir nicht sicher, ob das überhaupt möglich ist.
Was ist ein "Programm"? EIn fertig erzeugtes etwas? Routinen, gegeb die
gelinkt wird und die innerhalb eines bestimmten Kontextes ausführbar
sein sollen? ...?
> Denn dazu müsste man dem Compiler bestimmte Dinge vorgeben bzw.> vorgaukeln. Das (Betriebs-) System selbst ist in ASM geschrieben und> muss die Programme (mit derzeit maximal 9K Größe) an 8 verfügbare> Programmplätze laden können. Ich habe zwar einige Erfahrung in C (GCC),> aber halt nur auf dem PC.>> 1. Der Code muss relozierbar sein. Dazu habe ich bereits einen> Relocator, der beim Speichern des Programmes in das Flash die Befehle> CALL,JMP und LPM der entsprechenden Startadresse anpasst.
Das ist zu kurz gedacht, weil du so niemals alle Informationen hast, um
die Ersetzungen zu machen. Bei einem LDI/SUBI/SBCI zB musst du wissen,
ob er eine Zahl lädt/subtrahiert oder eine Adresse, und ob die Adresse
eine RAM-, eine Flash- oder eine EEPROM-Adresse ist. Diese Informationen
stehen im Objekt. Du kannst sie zB mit
1
> foo-objdump -rR file
anzeigen lassen. Im Endeffekt musst du die Aufgabe des Lokators
übernehmen, und je nach RELOC (R_AVR_16, R_AVR_LO8_LDI, R_AVR_HI8_LDI,
R_AVR_LO8_LDI_NEG, R_AVR_HI8_LDI_NEG, R_AVR_CALL, R_AVR_16_PM, ...) die
richtige Ersetzung vornehmen. Weiters ist zu beachten, daß das Symbol,
auf den sich der RELOC bezieht, zu unterschiedlichen Sections gehören
kann, zb .text, .data, .progmem.data etc. Falls das Programm explizite
Casts enthält, etwa weil es auf fest codierte EEPROM oder Flash-Adressen
zugreift, ist das nicht erkennbar, weil dafür keine RELOCs erzeugt
werden.
> Bei> Tabellenzugriffen über das Z Register sehe ich dabei aber keine Chance.> Es gibt zwar eine API-Funktion, die die Startadresse des Programmes> ermittelt und im Z-Register zurückgibt aber ich weiss nicht ob sich die> so nutzen lässt.> 2. Der nutzbare RAM-Bereich muss begrenzbar sein, außerdem darf das> Programm keinen eigenen Stack benutzen. Letzteres könne ich vielleicht> realisieren, indem ich vom Relocator Direktzugriffe auf den Stackpointer> z.B. in NOP umwandle.
Das Programm wird Stack benutzen; einfach schon deshalb, weil es Befehle
wie CALL unr RET enthält. Falls es darum geht, daß keine Daten auf dem
Stack angelegt werden bzw. über den Stack übergeben werden wird's
schwierig. Weil du unter die stdio erwähnst: Funktionen mit variabler
Argumentzahl übergeben ihre Parameter immer auf dem Stack. Zumindest
im avr-gcc. Sobald die Adresse einer lokalen Variablen genommen wird, zu
viele lokale Variablen gebraucht werden, deren Größe ungünstig ist (zB
char[3]), solche "ungünstigen" Objekte als Funktions- oder
Rückgabeparameter auftauchen oder Argumente bzw. Rückgabepparameter eine
gewisse Anzahl übersteigen, wird ebenfalls Stack fällig. Stack wird auf
jeden Fall gebraucht, wenn eine Funktion call-saved GPRs verwendet.
Zwar lassen sich auch recht komplexe Anwendungen "ohne Stack" schreiben
(d.h. ohne daß ein Framepointer gebraucht wird) schreiben, aber dazu ist
einiges an Erfahrung bzw. Arbeitsweise von avr-gcc nötig.
> 3. Es gibt mind. 6 Register (im Bereich R0-R15), die nicht genutzt> werden dürfen da die Video-Engine sie benötigt.
Das kommt sich u.U mit dem avr-gcc ABI quer. R0 und R1 können bzw.
dürfen nicht ausgeschlossen werden. Für Rn, n>=2 gilt: je kleiner die
Register-Nummer, desto besser. Beim Übersetzen kann das GPR zwar mit
-ffixed-n ausgeschlossen werden, allerdings ist das nicht ABI-kompatibel
mit den Bibliotheken bzw. die Libs müssen mit ebendiesen Schaltern
übersetzt werden. Zudem ist zu bedenken, daß, gcc um so mehr Stack
verwenden wird, je mehr GPRs im entzogen werden.
Vielen Dank für die Antworten und die interessante Diskussion.
Ich versuche mal, ein bisschen Resümee für mich zu ziehen:
Kurz noch zum Ziel. Das Ganze soll eine recht universelle mobile
Plattform werden und einige meiner nicht veröffentlichten Projekte zu
einem Open Source Projekt zusammenführen. Ausgabe ist ein 320x240 LCD
(mit Graustufen) aber auch VGA und TV (RGB/FBAS). Controller ist ein
ATMega1284P, der auch später durch einen S12X ersetzt werden kann.
Als "Programme" bezeichne ich das, was man neuerdings "Apps" nennt. Dazu
gibt es 8 Programmplätze im System, die jeweils 8K oder 9K (steht noch
nicht ganz fest) Flash belegen. Wenn dann jemand dich eine "App" in C
programmieren will, sollte er es zumindest theoretisch können.
Zuallererst, das "Stack-Problem" sollte wahrscheinlich keines
darstellen, solange nicht über andere Dinge als den Stackzeiger darauf
zugegriffen werden muss. Mir geht es darum, dass beim Start des
Programms ja schon ein Stack existiert und der auch am Programmende noch
so existieren muss.
Für die Reloziererei ist es wahrscheinlich der einfachste Weg, das
Programm genau für den Adressbereich zu kompilieren in dem es später
laufen soll. Im Programmkopf der bei der HEX->BIN Konvertierung noch
dazugefügt wird steht dann in einem Byte, an welche der 8 Programmplätze
das Programm geladen werden kann.
Bei der Register-Thematik werde isch schauen, dass ich die reservierten
Register (R2,R3 und R8-R11) zusammenlege.
Gruß Jörg
Da meine ursprüngliche Idee wohl nicht funktioniert, habe ich mir noch
ein anderes Konzept überlegt. Da ich ja einen Mega1284P verwende, habe
jetzt ich Dinge wie z.B. den Bildspeicher an das Ende des RAM gerückt
und nutze die Vektortabelle im Bootloaderbereich.
- Das Programm kann an Adresse 0 beginnen
- Das Programm darf R2 - R5 nicht verwenden
- Port A und C dürfen nicht verwendet werden
- Speicherbereich wie ATMega 328, bei Bedarf auch 4K RAM
- Interrupts können nicht genutzt und dürfen nicht abgeschalten werden
- Das Programm kann einen eigenen Stack nutzen, die ISR braucht aber 10
Bytes auf dem Stack
Der Zugriff auf das System müsste dann über entsprechende Bibliotheken
erfolgen, die im Wesentlichen nur aus CALL-Aufrufen in den oberen
Bereich bestehen. Auf die gleiche Weise kann ich dann auch den
Rücksprung ins System realisieren.
Da ich mir nicht mehr sicher bin, ob ich das Ganze als Open Source
veröffentlichen will, sollte es auch möglich sein, das System als "Black
Box" mit definierter Schnittstelle zu betrachten.
Jörg
So, es gibt einen neuen "Zwischenstand". Ich werde das Ganze so halten,
dass man die Einstellungen für den Mega644 verwenden kann:
- Die unteren 64K Flash sind frei, ebenso die unteren 2K EEPROM
- Die ersten 4K RAM sind frei
- Keyboard habe ich jetzt auf USART1 gelegt, USART0 ist frei
Es sind aber noch ein paar Fragen offen
1. macht der Assembler es mit, dass Unterprogrammaufrufe in den für den
Mega644 nicht vorhandenenen Flash-Bereich erfolgen?
2. kann ich mit static FILE mystdout ... die Ausgabe einfach auf den
Bildschirm umlenken?
3. gibt es eine ausführliche Doku, wie beim GCC die Parameterübergabe
auf Register-/Stackebene erfolgt?
4. Besteht überhaupt Interesse an einem derartigen Projekt?
Jörg
Joerg Wolfram schrieb:> 1. macht der Assembler es mit, dass Unterprogrammaufrufe in den für den> Mega644 nicht vorhandenenen Flash-Bereich erfolgen?
Sollte dem Assembler egal sein. Ist halt nur die Frage, wie du ihm
die Aufrufe dahin vermittelst.
> 2. kann ich mit static FILE mystdout ... die Ausgabe einfach auf den> Bildschirm umlenken?
Ich kenne keinen AVR mit eingebautem Bildschirm. ;-)
stdio existiert, aber du musst dir schon die Doku dazu mal
durchlesen, und dann musst du dich drum kümmern, eine Backend-
Funktion zu schreiben, die die Zeichen aus dem AVR irgendwie zu
deinem Bildschirm bekommt. Es existiert ein Beispielprogramm,
das für UART und HD44780-LCD zeigt, wie's geht.
> 3. gibt es eine ausführliche Doku, wie beim GCC die Parameterübergabe> auf Register-/Stackebene erfolgt?
Ja, in der avr-libc-FAQ.
> 4. Besteht überhaupt Interesse an einem derartigen Projekt?
Keine Ahnung.
Auch Jörg. ;-)
Hallo Jörg,
danke für die Info. Wichtig ist erstmal, dass es prinzipiell gehen
sollte. Die Aufrufe müssten dann wohl als CALL in Inline-Assembler
erfolgen, ebenso ließe sich dort auch die Parameter- und
Resultatübergabe unterbringen, also eine Art "Wrapper" um die
eigentlichen ASM-API Funktionen.
Ob das jemand jemals realisiert, ist noch eine ganz andere Frage da ich
selbst nur in ASM programmiere.
Jörg (TO)
Gut, dann müsste halt der Wrapper im Assemblerbereich stehen. Da die
"Schnittstellen" des compilierten C-Codes und die meiner ASM-Funktionen
nicht miteinander kompatibel sind, wird so etwas auf jeden Fall
notwendig sein. Um zum Beispiel einen Punkt zu setzen (PLOT), liegen bei
mir die X-Koordinate im X-Register und die Y-Koordinate im Y Register
und die Farbe in R20. Diese ganzen "internen Konventionen" will und muss
ich auch weiter beibehalten, damit meine ASM-Module miteinander
weitestgehend kompatibel bleiben.
Jörg