Hi Leute,
ich will grad folgendes auf meinem Controller implementieren:
Es geht wieder einmal um einen Befehls-Interpreter.
Der Controller ist über RS232 mit einem PC verbunden, dort kann man im
Terminal befehle eingeben.
jetzt soll der Controller die eingegebenen Befehle erkennen, und ggf.
eine entsprechende Funktion ausführen.
ABER: der Befehlsinterpreter soll auch Argumente, die man eingibt,
ebenfalls erkennen. Also wenn ich eingebe
test 123 [ENTER]
Dann soll der Controller die entsprechende Funktion aufrufen (die halt,
die für den Befehl "test" definiert wurde) und ihr den Parameter 123
übergeben.
Kann mir einer verraten, wie man das elegant lösen könnte? Schön wäre es
natürlich, wenn es mit Funktionspointern irgendwie ginge. Soweit bin ich
schon, dass ich eine Tabelle von Funktionspointern habe, die der
Controller dann durch pflügt, wenn man was eingibt.
Weiter möchte ich noch folgendes machen, was sicher ein bisschen
exotisch ist (wie ich finde, aber durchaus sinnvoll sein kann). Und zwar
möchte ich, dass die Liste der Befehle dynamisch ist. Das heisst: sie
soll zur Laufzeit erweitert werden können.
Welchen Sinn hat dies?
Nun: Auf den Controller wird ein einzelnes "Base Package" runter
geladen. Dort sind grundlegende Systemfunktionen drin, sowie halt ein
Startup-Code. Jetzt kann man das Base Package einfach auf den Controller
runterladen, und dieses sucht dann nach einem "Application Package".
Dieses enthält ann das eigentliche Programm, und dieses wiederum soll
natürlich eigene Funktionen im Terminal anbieten können, wenn es das
will.
Wie könnte man eine solche dynamische Befehlsliste realisieren? Ohne
malloc wird das ja schwer, aber malloc auf Controllern ist ja 'böse'.
Oder? ;-)
Weiter habe ich noch eine letzte Frage, die mich beisst:
Ich definiere im "Base Package" gewisse Hilfs-Funktionen, z.B. printf
sitzt im Base Package. Jetzt will ich aber nicht bei jedem neuen
Programm, das ich mache, den Code des Base Package includen! (Genau das
ist ja der Sinn der Sache. Wenn man so will: Das Base Package stellt
eine 'Library' zur Verfügung, u.a. mit Sachen wie printf, scanf, usw.).
Wie kriege ich jetzt einen Pointer auf die Funktion printf, wenn ich
doch im Application Package gar nicht wissen kann, wo die liegt?
Ich weiss, das ist ein seltsames Vorhaben. Ich weiss aber auch, dass es
sowas gibt, weil ich schon mal damit gearbeitet habe. Da konnte man zur
Laufzeit neue Software runterladen, und die hat ohne Reset des
Controllers funktioniert. Wie macht man das bloss?
Also, wenn ihr mir ein paar Denkanstösse geben könntet, wär das ganz
toll. Ich danke schon im Voraus!
Viele Grüsse
Zu deinem ersten Problem.
Eine Befehlsliste mit zugehörigen Funktionen zur Laufzeit zu erweitern,
ist nicht das grosse Problem. Der Weg wird über Funktionspointer führen.
Soweit hast du das erkennt. Das eigentliche Problem besteht woanders.
Die Argumentübergabe.
Du musst dir einen Mechanismus ausdenken, mit dem es dir möglich ist,
eine beliebige Anzahl von beliebigen Datentypen an eine Funktion zu
übergeben, und zwar möglichst so, dass die aufzurufenden Funktionen
immer eine gleiche Argumentliste haben.
Wie könnte man das machen?
Ich werd jetzt einfach mal das Problem 'beliebige Datentypen' ignorieren
und mich auf 'beliebige Anzahl' konzentrieren.
Man könnte zb. der Funkion ein Array übergeben, in dem alle Parameter
enthalten sind. Dazu noch einen int, der die Anzahl der Argumente
angibt.
Sagen wir einfach mal, dass eine Funktion beliebig viele int bekommen
kann. Dann könnte die vorgeschrieben Signatur für eine Funktion zb so
aussehen:
1
typedefvoid(*FktPtr)(intcount,int*args);
Ein FktPtr ist also ein Pointer auf eine Funktion, die keinen Returnwert
liefert und 2 Argument kriegt: einen int als ArgumentCounter und einen
Pointer auf ein Array von ints, welche die Argument darstellen.
Soweit, so schlecht.
Was brauchst du noch?
Du brauchst logischerweise eine Tabelle, in der alle Kommandowörter
aufgeführt sind und die dazu zugehörigen Funktionen, die aufzurufen
sind.
Da machen wir uns gleich mal eine Struktur, die einen solchen Eintrag
beschreibt.
1
structCommand
2
{
3
constchar*Keyword;
4
FktPtrFunction;
5
};
und eine Tabelle, die die Einträge aufnimmt. Jetzt musst du dich
entscheiden: Willst du das voll dynamisch machen (also mit malloc
arbeiten) oder reicht es dir, wenn dein Interpreter max. 20 Kommandos
halten kann.
Auch hier wieder: pragmatisch geh ich mal den ersten Weg
1
#define MAX_COMMANDS 20
2
structCommandCommands[MAX_COMMANDS];
3
intNrCommands=0;
Jetzt noch eine Funktion, die ein neuese Kommando einfügt. Beachte: Der
Keyword Pointer in struct Command soll immer auf einen String zeigen,
der sicher existiert. D.h. man sollte der gleich folgenden Add Funktion
immer einen konstanten String übergeben und kein char Array. Das ist
jetzt natürlich eine Konvention, die ich hier akzeptieren würde (vor
allem in Hinblick darauf, dass es sich hier um einen MC handelt mit
traditionell knappen Resourcen)
... aber irgendwann kommt die Stunde der Wahrheit und vom Benutzer kommt
eine Eingabezeile. Was ist zu tun?
Das erste Wort aus der Einagbezeile ist zu extrahieren. Damit wird in
die Kommandotabelle gegangen und der entsprechende Eintrag gesucht. Wird
einer gefunden, wird die zugehörie Funktion aufgerufen (Die Argumente
ignorieren wir erst mal).
Dazu schreiben wir gleich mal eine Funktion. Wieder: zur Vereinfachung
nehm ich einfach mal an, dass Kommandos eine maximale Länge haben. Ich
will ja hier keine Stringverarbeitung zeigen, sondern Kommandoauswertung
:-) (Ich nehm für die Stringverarbeitung auch Array Syntax und keine
Pointersyntax. Einfach nur, weil man dann leichter verfolgen kann, was
passiert.)
Soweit so gut. Wenn ich jetzt keinen Fehler eingebaut habe, dann müsste
schon mal die korrekte Funktion aufgerufen werden.
Wie gehts weiter?
Argumente: Momentan haben wir ja vereinbart: nur int Argumente.
Mann müsste also beim zerlegen der EIngabezeile weiter machen, die
jeweils nächsten Wörter extrahieren, das jeweilge Wort in einen int mit
gleichen Wert wandeln, alle diese int in einem Array sammeln und beim
Aufruf der Funktion mitgeben.
Das überlass ich mal dir, das zu machen.
Schlussendlich, wollen wir noch die Einschränkung aufheben, das nur int
Argumente möglich sind. Wie könnte man das machen?
Nun, dafür gibt es ein Vorbild! Sieh dir einfach mal an, wie du in
main() die Argumente von deinem Betriebssystem kommt. Die vollständige
Signatur sieht so aus
1
intmain(intargc,char*argc[])
2
{
3
...
4
}
main bekommt also einen Counter (argc), der die Anzahl der Argumente
angibt, und ein Array von char-Pointern, wobei jeder Pointer auf einen
String zeigt, der das jeweilige Argument in Stringform enthält. Na das
ist doch was! Das kann man kopieren.
Hallo G. ast, Du beschreibst gerade die Funktionalität der
Programmiersprache FORTH. Die ist zwar schon uralt (um 1970), stellt
aber - vom PC aus betrachtet - genau dieses Verhalten zur Verfügung.
Allerdings ist sie durch die umgekehrt polnische Notation (UPN) seeehr
gewöhnungsbedürftig. Wenn man die Tricks erst mal drauf hat, ist man
jedoch ganz schnell mit der Anwendungsentwicklung fertig. Es ergibt sich
kompakter schneller Code, da die Vorteile von Interpreter UND Compiler
zum Tragen kommen.
Allerdings muss der Programmierer stets wissen was er tut: es gibt keine
Typkonventionen oder gar -prüfungen.
Ausführliche Informationen zu den verschiedensten
FORTH-Implementierungen findes Du im Netz.
Hi Karl heinz,
Danke für deine ausführlichen Erläuterungen. Dein Code sieht plausibel
aus, ich versuch das dann gleich mal!
Jetzt, hast du auch noch einen Lösungsansatz für mein letzteres
Problem:
Ich will ja nicht bei jedem Programm, das ich schreibe, die ANSI-C
library einbinden. Schön wäre es, wenn man die 1x ins Flash rutnerladen
könnte, und die ist dann für immer und ewig dort. Funktionen wir printf
oder so liegen dann irgendwo in diesem Flash-Bereich, wo ich das
runtergeladen habe.
Wie könnte ich nun herausfinden, WO GENAU printf liegt, damit ich es in
meinem Awnendungsprogramm aufrufen kann?
Der Gedanke dabei ist nämlich, dass man dann ähnlich, wie bei einem
Betriebssystem, gewisse oft verwendete Grundfunktionen zur Verfügung
stellt, die dann von einer nwendung benutzt werden können.
Viele Grüsse & Danke
@ Karl heinz Buchegger:
Hi Karl heinz,
ich hab noch ne Frage!
Und zwar, kannst du mir vielleicht einen Tipp geben, wie ich das ganze
lösen könnte, wenn es Interruptgesteuert ablaufen soll? Also über Rx und
Tx Interrupt des UART?
Im Moment polle ich die entsprechenden Bits nämlich. Aber das ist
hässlich, umständlich und unzuverlässig, denn wenn der MC nebenbei auch
noch andere Sachen machen soll, kann es ja passieren dass er einige
empfangene Bytes 'verpasst', weil er das Rx-Flag nicht rechtzeitig
abfegragt hat.
Wie könnte man dem abhelfen?
Vielen Dank & Gruss
G. Ast wrote:
> Wie könnte ich nun herausfinden, WO GENAU printf liegt, damit ich es in> meinem Awnendungsprogramm aufrufen kann?
Das wird so nicht gehen.
Du wirst dir eine Tabelle bauen müssen, in der Funktionspointer auf
deine Librry Funktionen liegen. Diese Tabelle muss dann im Flash (oder
im EEPROM) an immer der gleichen Stelle liegen.
Die tatsächlichen Funktionsaufrufe nehmen dann den Umweg über diese
Funktionspointer.
Wenn du die Tabelle zb. im EEPROM platzierst, könnte ich mir vorstellen,
das das Ganze einfacher wird. Dein Usercode lädt als erstes die Tabelle
vom EEPROM ins SRAM und stellt somit den Umleitungsfunktionen die
Funktionspointer zur Verfügung.
G. Ast wrote:
> @ Karl heinz Buchegger:> Hi Karl heinz,> ich hab noch ne Frage!> Und zwar, kannst du mir vielleicht einen Tipp geben, wie ich das ganze> lösen könnte, wenn es Interruptgesteuert ablaufen soll? Also über Rx und> Tx Interrupt des UART?
Ganz einfach.
In der ISR werden die Zeichen in einer globalen String Variablen
gesammelt, bis die ISR zb. den Return erkennt. Danach setzt sie ein
Jobflag, der aussagt, dass eine komplette Eingabezeile fertig vorliegt
und beim nächsten Durchlauf der mainloop wird dieses Jobflag ausgwertet
und die Eingabezeile der Verarbeitung zugeführt.
> Kann mir einer verraten, wie man das elegant lösen könnte?
Klar, kein Problem. :-)
Da dein Problem ein echtes Standardproblem ist gibt es dafuer eigene
Programme namens flex,yacc,bison. Die sind auf jedem Unix installiert,
und man darf vermuten das es sie auch fuer andere weniger wichtige
Betriebssystem gibt.
http://flex.sourceforge.net/http://www4.tu-ilmenau.de/ate/TET/nlnet/flex_bison.html
Du beschreibst dann dein Problem in einer Art Metasprache und diese
Programme erzeugen dir daraus einen Parser in C-Code den du nur noch
in dein Programm einbinden musst.
Olaf
Olaf wrote:
>> Kann mir einer verraten, wie man das elegant lösen könnte?>> Klar, kein Problem. :-)>> Da dein Problem ein echtes Standardproblem ist gibt es dafuer eigene> Programme namens flex,yacc,bison. Die sind auf jedem Unix installiert,> und man darf vermuten das es sie auch fuer andere weniger wichtige> Betriebssystem gibt.
Das möcht ich sehen, wie du mit flex & bison einen Parser für eine
Sprache baust, die zur Compilezeit nicht feststeht, weil der Benutzer
neue Module hinzufügen kann. Und um einen String in Wörter aufzudröseln,
ist flex dann ja wohl ein klein wenig Overkill :-)
Hallo zusammen,
also lex&yacc einzusetzen für so einen Trivial-Parser, ist gelinde
gesagt, mit Kanonen auf Spatzen geschossen. Ich habe mal eine Shell, a
la bash, damit gemacht (mit &-operator, usw), selbst dafür war das
Vorgehen noch fast zu klein. Außerdem ist der Einarbeitungsaufwand in
yacc (bzw. bison) für das Ziel zu hoch, vor allem, wenn man die nötigen
theoretischen Grundlagen nicht parat hat (was war nochmal ein
shift-reduce-Konflikt?).
Mein Tip: Mach sowas wie den DOS-Kommandinterpreter, aber noch mit
Einschränkungen: Du brauchst wahrscheinlich keine Pipes und auch nicht
mehrere Befehle pro Zeile. Deine "Kommandos" sind keine separat ladbaren
Progrämmchen, sondern Funktionen mit entsprechenden Argumenten
(argc,argv), die in einer Tabelle gehalten werden. Eleganter ist es
natürlich, wenn die konkreten Kommandos Ableitungen einer abstrakten
Klasse Befehl sind.
Gruß
Stephan
Hi Jungens,
vielen Dank für eure Hilfe!
Wie könnte ich das Problem mit der Funktionspointer-Tabelle lösen?
Einfach fix an eine beliebige Adresse speichern?
Zu dem Befehlsinterpreter:
Das mit dem Rx-Interrupt ist klar, das klappt nun soweit auch. Nun
möchte ich aber Ausgabestrings auch Interruptgesteuert senden, mit Hilfe
des Tx-Interrupts. Also ohne Polling! Wie könnte ich das lösen?
Nun, noch das aufsplitten in die Teilstrings.
Beispiel, ich gebe ein:
test 123 abc
nun soll die Funktion test aufgerufen werden, die sieht doch so aus:
void test(int argc, char* argv)
und argc ist jetzt 2, argv ist ein String-Array mit "123" und "abc"
drin.
Wie nur kann ich "123" und "abc" aus dem Eingabestring elegant raus
pfriemeln? Irgendwie krieg ich das nicht gebacken, es funktioniert nicht
richtig.
Könnt ihr mir da noch eine Hilfestellung geben?
Vielen Dank, und Grüsse
G. Ast wrote:
> Zu dem Befehlsinterpreter:> Das mit dem Rx-Interrupt ist klar, das klappt nun soweit auch. Nun> möchte ich aber Ausgabestrings auch Interruptgesteuert senden, mit Hilfe> des Tx-Interrupts. Also ohne Polling! Wie könnte ich das lösen?
Da müsste sich in der Codesammlung auch was dazu finden lassen.
Im Prinzip: Die UART kann dich mit einem Interrupt benachrichtigen, wenn
sie ein Zeichen fertig abgeschickt hat und wieder frei ist.
Dein Ausgabestring kommt in einen Buffer, das erste Zeichen geht gleich
zur UART. Wenn der Interrupt kommt, wird nachgesehen ob noch was im
Buffer ist, wenn ja: nächstes Zeichen in die UART stellen.
> Nun, noch das aufsplitten in die Teilstrings.> Beispiel, ich gebe ein:>> test 123 abc>> nun soll die Funktion test aufgerufen werden, die sieht doch so aus:>> void test(int argc, char* argv)
Nein. So sieht sie nicht aus (schau dir nochmal main() genau an!)
Die sieht sie aus:
void test(int argc, char* argv[])
> und argc ist jetzt 2, argv ist ein String-Array mit "123" und "abc"> drin.
Schau dir main an!
argv ist ein Array von character Pointern!
> Wie nur kann ich "123" und "abc" aus dem Eingabestring elegant raus> pfriemeln? Irgendwie krieg ich das nicht gebacken, es funktioniert nicht> richtig.> Könnt ihr mir da noch eine Hilfestellung geben?
Sicher.
Du kannst zb, ein Array bauen, welches Pointer auf den Originalstring
enthält. Im Originalstring fügst du an strategischen Stellen '\0' ein.
So vermeidest du, dass du Strings allokieren musst bzw. umkopieren
musst.
Von der UART kriegst du den String hier
"TEST 123 abc"
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| T | E | S | T | | 1 | 2 | 3 | | a | b | c | \0| |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
Du überschreibst die Spaces mit \0 und setzt gleichzeitig ein Pointer
Array auf, so dass du erhältst
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| T | E | S | T | \0| 1 | 2 | 3 | \0| a | b | c | \0| |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
^ ^ ^
| | |
+----------+ | |
| | |
argv | | |
+-------+ | | |
| o--------+ | |
+-------+ | |
| o----------------+ |
+-------+ |
| o---------------------------------+
+-------+
| NULL |
+-------+
| |
+-------+
| |
Wenn du jetzt deine Funktion mit den Argumenten 3 und argv aufrufst, hat
die Funktion alles was sie braucht.
Sie kann die Argumente ganz einfach durchgehen
void test( int argc, char* argv[] )
{
for( int i = 0; i < argc; ++i )
printf( "%d: %s\n", i, argv[i] );
}
Hi Karl heinz,
vielen Dank. Ich werde das dann testen, aber ich denke wenn du das so
beschreibst wird das sicher funktionieren :-)
Noch eine Frage zum Senden mit Interrupt:
Nehmen wir an, ich habe in meinem UART-Modul einen Buffer von 64 Bytes
reserviert für Sendedaten.
Was jetzt, wenn ich einen String senden muss, der länger als 64 Bytes
ist?
Da hakt es bei mir irgendwie noch.
Vielen Dank,
Gruss
G. Ast wrote:
> Hi Karl heinz,> vielen Dank. Ich werde das dann testen, aber ich denke wenn du das so> beschreibst wird das sicher funktionieren :-)>> Noch eine Frage zum Senden mit Interrupt:>> Nehmen wir an, ich habe in meinem UART-Modul einen Buffer von 64 Bytes> reserviert für Sendedaten.> Was jetzt, wenn ich einen String senden muss, der länger als 64 Bytes> ist?
Du hast eine Funktion, die den String in den Buffer schreibt.
Diese Funktion muss natürlich warten, bis tatsächlich Platz im Buffer
ist um wieder ein Zeichen in den Buffer stellen zu können.
Wie kann Platz entstehen?
Der Interrupt sorgt dafür, dass wieder ein Zeichen aus dem Buffer per
UART auf die Reise geschickt wird. Dadurch wird dann wieder Platz für
ein neues Zeichen.
Irgendwann hat dann die Sendefunktion (möglicherweise auch dadurch, dass
sie warten musste) alle auszugebenden Zeichen in den Buffer gepfriemelt
und kann retournieren. Das heist nicht, das deswegen schon alles
gesendet wurde. Es heist lediglich, dass die Sendefunktion alle Bytes
loswerden konnte. Einige wurden schon übertragen, ein anderes ist gerade
im Übertragen, andere warten im Sendebuffer.
Studier mal die UART Library vom Peter Fleury (zu finden mit Google).
Hi Karl Heinz,
das heisst: Beim Senden von Daten muss ich IMMER warten, egal ob ich
Interrupts verwende oder Polling. Richtig?
Die Sendefunktion wartet dann halt einfach, bis der Buffer wieder leer
ist.
Ich habe mir das irgendwie so vorgestellt, dass ich einfach eine
Sendefunktion habe, der übergebe ich die Daten, die ich senden will, und
kann nachher gleich weiterarbeiten. Der Rest wird im UART erledigt. Aber
ich hab leider grade keine Idee, wie sich sowas realisieren liesse,
deshalb meine Fragerei.
Grüsse
G. Ast wrote:
> Hi Karl Heinz,> das heisst: Beim Senden von Daten muss ich IMMER warten, egal ob ich> Interrupts verwende oder Polling. Richtig?
Nein.
Wenn du Interrupts zum Senden und einen Buffer benutzt, musst du nur
dann warten, wenn kein Platz im Buffer ist. Wenn der Buffer Platz zum
aufnehmen der Zeichen hat, muss überhaupt niemand warten
> Ich habe mir das irgendwie so vorgestellt, dass ich einfach eine> Sendefunktion habe, der übergebe ich die Daten, die ich senden will, und> kann nachher gleich weiterarbeiten. Der Rest wird im UART erledigt.
Dann müsste der Buffer im UART sein.
Irgendwo muss ein Buffer sein, der das zu Sendene zwischenzeitlich
aufnimmt. Und wenn dieser voll ist, dann muss gewartet werden, bis er
wieder was aufnehmen kann. Ob der Buffer jetzt in der Hardware-UART
integriert ist, oder ob du den Softwaremässig machen musst, ändert
nichts am Prinzip.
Beispiel aus dem realen Leben:
Dein kannst deinem Kumpel Nachrichten schicken.
Die Nachrichten schreibst du auf ein Papier, welches du von einem
Vorratsstapel Papier nimmst und legst sie deinem Kumpel in den
Eingangskorb. Wenn dein Kumpel die Nachricht gelesen hat, radiert er sie
aus und legt das Papier wieder auf den Stapel Vorratspapier.
Solange genug Vorratspapier da ist: kein Problem. Du schreibst und
irgendwann liest dein Kumpel.
Was aber tust du, wenn kein Vorratspapier mehr da ist? Du wartest, bis
dein Kumpel mit dem Lesen wieder nachgekommen ist und wieder ein paar
Blätter freiradiert hat.
G. Ast wrote:
> Nun: Auf den Controller wird ein einzelnes "Base Package" runter> geladen. Dort sind grundlegende Systemfunktionen drin, sowie halt ein> Startup-Code. Jetzt kann man das Base Package einfach auf den Controller> runterladen, und dieses sucht dann nach einem "Application Package".> Dieses enthält ann das eigentliche Programm, und dieses wiederum soll> natürlich eigene Funktionen im Terminal anbieten können, wenn es das> will.
Dann brauchst Du ein Betriebssystem, welches es gestattet verschiedene
Programme zu starten und ihnen einen jeweils eigenen Speicher
zuzuweisen.
Und einen Compiler, der dazu passende Programme erzeugen kann.
Welcher MC soll es denn überhaupt sein?
Auf nem AVR kannste es vergessen, der hat keine Speicherverwaltung.
Jedes Programm verfügt über den gesamten SRAM und weiß nicht, ob noch
andere Programme im Flash sind.
Ein Programm kann zwar ein anderes starten, dieses wird aber sofort den
Speicher des Aufrufers zerstören. Eine Rückkehr oder gar Funktionsaufruf
ist daher unmöglich.
Außerdem kann nur ein Programm Interrupts benutzen, da diese nicht
verschieblich sind.
Peter
Peter Dannegger wrote:
> G. Ast wrote:>> Nun: Auf den Controller wird ein einzelnes "Base Package" runter>> geladen. Dort sind grundlegende Systemfunktionen drin, sowie halt ein>> Startup-Code. Jetzt kann man das Base Package einfach auf den Controller>> runterladen, und dieses sucht dann nach einem "Application Package".>> Dieses enthält ann das eigentliche Programm, und dieses wiederum soll>> natürlich eigene Funktionen im Terminal anbieten können, wenn es das>> will.>>> Dann brauchst Du ein Betriebssystem, welches es gestattet verschiedene> Programme zu starten und ihnen einen jeweils eigenen Speicher> zuzuweisen.
An sowas hab ich auch zunächst gedacht.
Ich glaube aber, er möchte sich die Funktionalität seines Programmes zur
Laufzeit zusammenstellen, in dem er 'Packages' runterlädt, die sich dann
ins Programm einbinden können. Es gibt also eigentlich nur ein einziges
Program, welches zur Laufzeit aus Komponenten zusammengestellt wird. Ich
denke mit einem spezialisiertem Bootloader müsste sowas möglich sein. In
dem Fall wäre dann der Bootloader sowas wie ein Mini-Betriebssystem,
welches dafür sorgt, dass die Einzelteile im Speicher sauber angeordnet
werden und nach dem Download eine Art 'init'-Funktion in jedem Paket
aufgerufen wird, damit sich die Komponente beim Programmkern anmelden
kann. Irgendsowas in der Art.
Ich find die Idee an sich interessant, wenn auch sicherlich nicht
trivial zu implementieren. Ganz und gar nicht trivial.
> Und einen Compiler, der dazu passende Programme erzeugen kann.
Das wird wohl eines der Hauptprobleme an der ganzen Sache sein.
Compiler/Linker/Librarian dazu zu überreden, prinzipiell ausführbare
Module zu erzeugen, die in sich bereits gelinkt wurden, aber trotzdem
keine fertigen EXE darstellen.
Moin,
hab auch schon die ganze zeit beim durchlesen gedacht wann das
schwierigste aller probleme hier angesprochen wird.
eine möglichkeit währe relativ gelinkter code. sprich im modul werden
nur refferenzielle sprünge durchgeführt, keine absoluten. somit könnte
der Code überall liegen.
Speicher müsste über malloc verwaltet werden. Der Pointer selber muss
wie z.B. die Funktionstabelle für die Lib Funktionen an einer zentralle
stelle liegen.
Alternative dazu eine CPU mit MMU
gruss
¨Hey Karl Heinz,
du hast es genau erfasst! Genau das will ich machen. Es gibt ein
Mini-"Betriebssystem", das nach dem Reset gestartet wird. Danach wird
der Speicher (also das angeschlossene Flash) nach 'Packages' durchsucht.
Die liegen immer an bestimmten Adressen, nämlich genau an den
Sektorgrenzen des Flash. Und dort schaut jetzt das System nach dem Reset
nach, ob ein Package vorhanden ist. Wenn ja, dann führt es dessen
Init-Funktion aus. Die kann z.B. weitere Befehle zum Terminal
hinzufügen, gewisse Peripherie-Bausteine initialisieren usw.
Genau so soll das gehen!
Dass es nicht einfach ist, habe ich schon bemerkt :-)
Aber es wäre recht praktisch, wenn man sich das gewünschte Programm
nachher aus einzelnen Modulen 'zusammenbasteln' kann, fast nach einem
Baukasten-System. Man setzt dann einfach die gewünschten Komponenten
zusammen, lädt das Zeuch runter - fertig ist das Programm!
Ausserdem, das mit der Funktionspointer-Tabelle hat noch einen anderen
Hintergrund. Beispiel: Ich verwende oft Funktionen wie strcmp oder
printf. Die müssten dann ja jedes mal mit compiliert werden und
runtergeladen werden. Wenn die schon fix im Flash wären (da also eine
'Library' mit bereits vordefinierten Funktionen läge) dann wäre das
natürlich praktisch. In Windows-Programmen z.B. muss man sich ja auch
nicht mehr um Dinge wie "Erstelle eine Datei" oder sowas kümmern, man
macht einfach einen bestimmten Funktionsaufruf an das Betriebssystem und
das erledigt alles für einen.
@Peter Dannegger
Ich würde das gerne auf einem ARM7-Controller realisieren. Es stehen 256
MB SDRAM zur Verfügung, Flash gibts auch ausreichend (1 MB).
Grüsse
@Michael Graf
> sprich im modul werden> nur refferenzielle sprünge durchgeführt, keine absoluten. somit könnte> der Code überall liegen.
Dafür dient glaube ich die Compiler-Option "generate position
independent code", oder nicht? Ich meine, irgendwo sowas mal gesehen zu
haben.
@Karl heinz:
> Das wird wohl eines der Hauptprobleme an der ganzen Sache sein.> Compiler/Linker/Librarian dazu zu überreden, prinzipiell ausführbare> Module zu erzeugen, die in sich bereits gelinkt wurden, aber trotzdem> keine fertigen EXE darstellen.
Das Problem habe ich nun, denke ich zumindest, einigermassen angehen
können. Ein Ansatz wäre doch der:
Und zwar besitzt das 'Betriebssystem' (ich schreib das in Gänsefüsschen,
weils ja gar kein BS ist) einen 'Control Block', der nichts anderes ist
als ein grosses struct:
typedef struct __TestControlBlock
{
void (*Funktion1)(void);
int (*Funktion2)(char test, int blubb);
...
} TestControlBlock;
In dem Struct sind also Pointer auf alle Funktionen hinterlegt, die das
Betriebssystem veröffentlichen will. Beispielsweise könnte man hier ja
auch einen Funktionspointer für printf definieren oder die Funktion, die
auf einer SD/MMC-Karte Dateien einliest oder dergleichen.
Und jetzt wird bei jedem Package beim Start die Funktion InitPackage
aufgerufen und ein Pointer auf diesen struct übergeben! Das Package
'kennt' nun alle Betriebssystem-Funktionen, denn es kann ja über den
struct zugreifen. Wo die Funktionen liegen, ist wurscht, das BS gibt die
Pointer ja vor. Somit muss man bei einem neuen Package nur noch das
Header-File includen, wo definiert ist, wie der struct aussieht. Den
Rest erledigt das BS. Mit Makros könnte man dann noch solche Konstrukte
machen, wenn man will:
#define Funktion1() TestControlBlock->Funktion1
#define Funktion2(a, b) TestControlBlock->Funktion2(a, b)
So sieht der C-Quellcode dann aus, wie wenn das alles 'normale'
funktionen wären.
Im Mainloop des BS wird dann für jedes (mittlerweile dem System
bekannten) Package eine 'PackageMain'-Funktion aufgerufen, so kann man
irgendwelchen Code in den Packages platzieren. Also eine Art
kooperatives Multitasking.
Die Frage ist jetzt nur noch: Wie findet das BS heraus, wo die
InitPackage-Funktionen sind?
Darüber bin ich mir noch nicht im klaren. Ein Ansatz wäre, dass man
festlegt: ein Package kann nur immer an einer Sektorgrenze des
Flash-Speichers liegen. Dann bräuchte das BS nur noch an den jeweiligen
Adressen zu schauen, ob da was sinnvolles steht oder nicht, und wenn ja,
dann wird das ausgeführt. Das schöne wäre daran auch, dass man dann
einzelne Sektoren des Flashs löschen kann, und so immer einzelne
Packages entfernen kann (oder hinzufügen, wenn man in einen Sektor was
neues rein programmiert).
Was ist von meiner Idee zu halten?
Das mit dem UART scheint jetzt übrigens zu funktionieren. Vielen Dank!
Hi
jedes paket hat am anfan einen kleinen info header, in dem z.B. der
Pointer der Startfunktion hinterlegt ist. wie gross das Paket selber
ist, ...
weiter noch eine Magic number, das ist eine zahl, so konstruiert, das
sie nicht zufällig zustande kommen kann. Grub verwendet z.B. sowas.
Diese zahl sollte so im Programm nicht vorkommen. man kann dann nach so
einer zahl suchen, um z.B. so einen Headerblock zu identifizieren.
Natürlich sollte so ein block noch zusätzlich durch z.B. eine prüfsumme
gesichert werden, im falle das doch das eintritt, was nicht eintreten
sollte.
gruss
Hi Michael,
das mit der MAgic Number und dem Info-Header klingt interessant. Hast du
eine Idee, wie man die MAgic Number generieren könnte?
Ich stelle mir das nicht so einfach vor. Denn die Magic Number muss ja
so kinzipiert sein, dass sie garantiert anders aussieht, als alle Daten,
die in dem Paket vorkommen können. Wenn die Magic Number z.B. 0x12345678
ist, brauchst du ja in dem Programmcode nur irgendwo eine Variable mit
dem Wert 0x12345678 zu verwenden - und schon hast du ein Problem, weil
der Loader dies dann auch als Magic Number interpretiert.....
Kann man den Compiler irgendwie dazu bringen, dass er den Info-Header
IMMER ganz am Anfang des Programmcodes platziert? Irgendwo muss der
Loader ja mit der Suche nach dem Header beginnen. Oder soll er einfach
Byte für Byte vom Speicher durchgehen, bis er eine Magix Number findet?
Gruss
@Michael Graf
ich hab mir jetzt das nochmal ganz genau überlegt, wie das mit den
Paketen funktionieren könnte. Und ich habe immer noch keine gute Lösung
gefunden!
Das einzig schlaue, was mir eingefallen ist, wäre folgendes:
ein struct wird definiert, mit folgenden Inhalt:
typedef struct _paketinfo
{
long magicnumber;
void (*init)(void);
long segmentsize;
} paketinfo;
magicnumber ist die Magic Number, 32 Bits breit. init ist ein Zeiger auf
die Init-Funtkeion des Pakets.
Der Loader geht nun nach dem Start den Speicher durch, bis er die erste
Magic Number findet. Anhand der wird dann eine paketinfo-Struktur
generiert, und init aufgerufen. Nun wird der Speicher weiter durchsucht,
aber erst wieder ab der Adresse (aktuelle adresse + segmentsize).
Dadurch wird verhindert, dass eine im Programm vorkommende Konstante,
die zufälligerweise den selben Wert hat wie die Magic Number, als Paket
identifiziert wird.
Die Frage ist jetzt nur:
1. Wie könnte so eine Magic Number aussehen?
2. Wie kann ich das Feld segmentsize berechnen, um dort den richtigen
Wert reinzuschreiben?
Gruss