YANOS - Yet Another Narrow Operating System
Hallo Zusammen,
die erste Frage ist vielleicht - och nee, warum denn schon wieder eins?
Das habe ich mich auch gefragt. Vorher bin ich immer mit meinen timer
tasks und einer mehr oder weniger grossen Statusmaschine in der mainloop
ausgekommen. Dann wurde ich bei einem Projekt gezwungen, zwei nicht
kooperative Tasks gemeinsam laufen zu lassen. Dazu habe ich einen
simplen Taskswitcher geschrieben. Dann kam der Wunsch auf, die
Rechenzeit nicht mehr 50:50, sondern zur Laufzeit je nach Bedarf
aufzuteilen. Das ging auch noch recht einfach.
Das Projekt wuchs und wuchs. Mailboxen und Messages wurden auf
Applikationsebene implementiert. Ein dritter Task, der nur ab und an mal
laufen sollte, wurde angedacht. Die Lockingmechanismen fuer
konkurierende Hardwarezugriffe wurden auch immer komplizierter. Da jetzt
noch weiter irgendwie dran rumzufrickeln machte wenig Sinn. Die Zeit war
also reif fuer ein OS.
Kein Problem, gibt ja genug. Aber die kleinen koennen meist nicht mit
nicht kooperativen Tasks umgehen. Die die es koennen arbeiten oft
prioritaetsbasiert. Damit unsere beiden Tasks damit laufen, muessten sie
die gleiche Prioritaet bekommen. Dann laufen sie aber wieder 50:50. Die
naechst Besseren, die anhand der Prioritaet Laufzeitverteilungen
vornehmen, sind dann allerdings schon ziemlich fett und behaebig.
So entstand das Xte OS. Da ich so einen Ansatz bislang noch nicht
gesehen habe, stelle ich das mal ins Forum. Das ist zwar sehr auf eine
bestimmte Anwendung optimiert, aber vielleicht kann das ja trotzdem
jemand von Euch gebrauchen.
Gruss Udo
Für welche Prozessor ist das Ganze denn gedacht?
Habe diesbezüglich im Quelltext nichts gefunden. Und mit dem
Assemblerschnipseln wirds wohl nicht plattformunabhängig sein.
Hallo Hans,
vor ca. 2 Wochen habe ich den Code auf das grosse Projekt losgelassen.
Hat auch sehr gut funktioniert. Diesen Source darf/moechte ich aber
nicht posten (waere auch viel zu viel).
Ich habe aber noch meinen Testcode, den ich fuer die Entwicklung von
Yanos verwendet habe. Das kannst Du Dir gerne mal anschauen. Aber
Achtung - das ist lediglich zum Testen und anschliessend fuer die
Muelltonne geschrieben worden.
Hallo Thomas,
das laeuft auf einem M32. Ist wohl eher selten im Forum. Sollte aber
egal sein, da es bis auf den Scheduler reines C ist. Portierungen eines
Schedulers auf die hier im Forum hauptsaechlich verwendeten Prozessoren
habe ich schon mehrere gesehen.
In einer aelteren Version hatte ich den Scheduler auf die klassische
Weise implementiert. Da kann man den Assemblercode ja nahezu 1:1 von
Prozessor zu Prozessor uebernehmen. Hier habe ich diese halbwegs
vorhandene plattformunabhaengigkeit bewusst verlassen, um auf
Geschwindigkeit zu optimieren. Mit jetzt knapp ueber 5us (ohne
OS-overhead) habe ich die Threadwechselzeit nahezu halbiert.
Gruss Udo
BTW: Gibt hier noch andere M16/M32 Programmierer die stctx/ldctx
verwenden? Es kommt mir so vor, als haette ich diese Befehle irgendwie
vergewaltigt.....
uCler wrote:
> Die Lockingmechanismen fuer> konkurierende Hardwarezugriffe wurden auch immer komplizierter.
Wie hast Du denn das gelöst ?
Bei einer Mainloop sind solche Probleme ja sehr selten, da ja alles
immer der Reihe nach abläuft.
Aber mit Deinem OS müssen ja solche Probleme alle Nase lang auftreten,
da ja alles virtuell parallel ist.
Insbesondere auf 8-Bittern ist ja auch Datenmurx quasi vorprogrammiert,
bei allen globalen Variablen, die mehrbytig sind (Zeitscheibe haut genau
dazwischen, macht nen neuen Wert und man hat dann ein neues Byte + ein
altes Byte).
Das ist es auch, was mich vor einem OS zurückschrecken läßt.
Die möglichen Deadlock und Datenmurx-Probleme sind mit einem OS ja kaum
beherrschbar.
Die Datenzugriffe zwischen Main und Interrupts zu sichern
(Interruptsperre), geht ja noch.
Aber wenn plötzlich jede Maintask die andere unterbrechen kann, sieht ja
keiner mehr durch.
Ich hab z.B. mehrere Statemachines, wo zu Anfang alle Eingänge per SPI
eingelesen werden, dann verarbeiten das die Statemachines und dann wird
wieder alles per SPI ausgegeben. Das kann aber nur funktionieren, wenn
die Daten während eines kompletten Zyklus konstant bleiben.
Bzw. es können auch zwischenzeitlich Fehlerstates auftreten, die
keinesfalls ausgegeben werden dürfen.
Erst wenn der abschließende Fehlerchecker die Daten nochmal geprüft hat,
das kein gefährlicher Zustand entstehen kann, dann dürfen sie angelegt
werden.
Peter
Peter Dannegger wrote:
> Das ist es auch, was mich vor einem OS zurückschrecken läßt.>> Die möglichen Deadlock und Datenmurx-Probleme sind mit einem OS ja kaum> beherrschbar.>>> Die Datenzugriffe zwischen Main und Interrupts zu sichern> (Interruptsperre), geht ja noch.> Aber wenn plötzlich jede Maintask die andere unterbrechen kann, sieht ja> keiner mehr durch.
Dafür gibt es Synchronisationsmechanismen wie Semaphoren und Queues.
Richtig angewendet ist damit der "Murx" gut beherrschbar.
> Ich hab z.B. mehrere Statemachines, wo zu Anfang alle Eingänge per SPI> eingelesen werden, dann verarbeiten das die Statemachines und dann wird> wieder alles per SPI ausgegeben. Das kann aber nur funktionieren, wenn> die Daten während eines kompletten Zyklus konstant bleiben.
Wenn kein anderer Task in diesen Daten herumpfuscht (warum sollte er
auch?), dann gibt es da keine Probleme.
Andreas Schwarz wrote:
> Dafür gibt es Synchronisationsmechanismen wie Semaphoren und Queues.> Richtig angewendet ist damit der "Murx" gut beherrschbar.
Das sind dann Notlösungen, die wieder zusätzliche Ressourcen
verbrauchen, statt das Problem zu vermeiden.
Besonders heimtückisch sind die Queues.
Wenn man da nicht 101%-ig garantieren kann, daß der empfangende Prozess
immer schneller als der sendende ist, läuft die Queue über.
Oder sie ist sehr groß und läuft sehr voll und dann arbeitet der
empfangende Prozess noch völlig veraltete Daten ab und z.B. Dein
Regelkreis schwingt.
Ich habe daher bessere Erfahrungen mit einem Datenpuffer für genau einen
Datensatz gemacht.
Wenn dann mal der Empfänger nicht nachkommt, überschreibt einfach der
Sender den Puffer mit den aktuelleren Daten, also z.B. ein alter
AD-Wandler Wert geht verloren und alles läuft problemlos weiter.
Peter
Peter Dannegger wrote:
> Andreas Schwarz wrote:>>> Dafür gibt es Synchronisationsmechanismen wie Semaphoren und Queues.>> Richtig angewendet ist damit der "Murx" gut beherrschbar.>>> Das sind dann Notlösungen, die wieder zusätzliche Ressourcen> verbrauchen, statt das Problem zu vermeiden.
Das sind "Lösungen", keine Notlösungen.
> Besonders heimtückisch sind die Queues.> Wenn man da nicht 101%-ig garantieren kann, daß der empfangende Prozess> immer schneller als der sendende ist, läuft die Queue über.> Oder sie ist sehr groß und läuft sehr voll und dann arbeitet der> empfangende Prozess noch völlig veraltete Daten ab und z.B. Dein> Regelkreis schwingt.>> Ich habe daher bessere Erfahrungen mit einem Datenpuffer für genau einen> Datensatz gemacht.
Und was hindert dich daran das mit einem RTOS genauso zu machen? Für
sowas einen Queue zu verwenden ist natürlich sinnlos.
> Wenn dann mal der Empfänger nicht nachkommt, überschreibt einfach der> Sender den Puffer mit den aktuelleren Daten, also z.B. ein alter> AD-Wandler Wert geht verloren und alles läuft problemlos weiter.
Wieso nicht gleich den Messwert im Regelungs-Task einlesen? Nur weil man
einen Hammer (RTOS) hat darf nicht alles wie ein Nagel (Task) aussehen.
Andreas Schwarz wrote:
> Das sind "Lösungen", keine Notlösungen.
Für mich setzen Lösungen immer bei der Ursache an und Notlösungen
bekämpfen aber nur die Wirkung.
> Wieso nicht gleich den Messwert im Regelungs-Task einlesen?
Weil ich keine Zeit verschwenden will.
Der ADC-Task ist freilaufend und kann mehrere Eingänge einlesen, glätten
und in Puffern ablegen.
Und jede Task, die einen Wert braucht, liest ihn sofort aus dem Puffer.
Ich habe sehr gute Erfahrungen damit gemacht, daß ich alle Tasks in der
Mainloop in einer eindeutig definierten Reihenfolge ohne gegenseitige
Unterbrechung aufrufe.
Damit ergibt sich eine sehr geringe Fehleranfälligkeit und sehr stabiles
Arbeiten.
Natürlich dürfen die Tasks keine Zeit vergeuden, jede Task bricht sofort
ab, wenn sie grad nichts zu tun hat.
Und Prozesse, die erst nach einer bestimmten Wartezeit ausgeführt werden
dürfen, übergebe ich dem Scheduler. Der ist auch eine Task in der
Mainloop, d.h. wird auch nur zu ganz eindeutigen Zeiten ausgeführt.
Es ist halt nur ne klitzekleine Umstellung in der Programmierweise
erforderlich, Bedingungen werden nicht wartend, sondern abweisend
programmiert.
Also nicht: warte bis Bedingung wahr, sondern: breche ab solange
Bedingung nicht wahr.
Peter
Peter Dannegger wrote:
> Andreas Schwarz wrote:>>> Wieso nicht gleich den Messwert im Regelungs-Task einlesen?>> Weil ich keine Zeit verschwenden will.> Der ADC-Task ist freilaufend und kann mehrere Eingänge einlesen, glätten> und in Puffern ablegen.> Und jede Task, die einen Wert braucht, liest ihn sofort aus dem Puffer.
Wenn du kooperatives (also kein preemtives Scheduling verwendest), dann
gibt es damit grundsätzlich kein Problem, weil der Kontextwechsel erst
stattfindet wenn der ADC-Task fertig ist und sich schlafen legt.
Wenn du preemtives Scheduling verwendest (was in deinem Fall wenig Sinn
macht), dann kannst du eine critical section verwenden um
sicherzustellen dass der ADC-Task beim Schreiben der globalen Variablen
nicht preemtiv unterbrochen werden kann:
taskENTER_CRITICAL();
globaler_adcwert = adcwert;
taskEXIT_CRITICAL();
> Natürlich dürfen die Tasks keine Zeit vergeuden, jede Task bricht sofort> ab, wenn sie grad nichts zu tun hat.> Und Prozesse, die erst nach einer bestimmten Wartezeit ausgeführt werden> dürfen, übergebe ich dem Scheduler. Der ist auch eine Task in der> Mainloop, d.h. wird auch nur zu ganz eindeutigen Zeiten ausgeführt.>>> Es ist halt nur ne klitzekleine Umstellung in der Programmierweise> erforderlich, Bedingungen werden nicht wartend, sondern abweisend> programmiert.> Also nicht: warte bis Bedingung wahr, sondern: breche ab solange> Bedingung nicht wahr.
Damit machst du ja all das von Hand was dir ein kooperatives OS abnehmen
kann. Statt dem Scheduler zu sagen wann die Funktion das nächste Mal
aufgerufen werden möchte und zu returnen, hast du bei einem OS für jeden
Task eine eigene Endlosschleife, und wenn es nichts zu tun gibt weil du
z.B. auf den nächsten Abtastzeitpunkt für den ADC oder die Regelung
wartest, rufst du sleep_until(t + 100) auf. Und zusätzlich hast du z.B.
eine komfortable Möglichkeit über Queues Nachrichten zu verschicken,
ohne dass du in jedem Empfängertask if(!neue_daten_vorhanden) return;
machen musst, usw.
Meine Meinung ist, dass ein multitaskingfähiges OS in einem ROM ein
totaler Overhead ist. Es sind ja alle Prozesse und Funktionen bekannt,
ein OS zu verwenden ist eigentlich nur ein Grund, ineffizient zu
programmieren. Anders sieht es aus, wenn zum Erstellungszeitpunkt
"unbekannte" Prozesse später auch geladen und entladen werden sollen.
Dann ist ein OS eine saubere Schnittstelle, gegen die auch nichts
spricht. Zum Probieren kann man das auch im ROM machen...
Jörg
> Die Lockingmechanismen fuer> konkurierende Hardwarezugriffe wurden auch immer komplizierter.
Wie hast Du denn das gelöst ?
Hallo Peter,
kleines Vorher/Nachher Beispiel aus der Praxis. Als NVRam setzen wir
FRam ein. Das haengt ebenso am IIC wie die RTC und ein
Temperaturfuehler. Zur Vermeidung von Konflikten musste der IIC-Code an
eine gemeinsame Stelle.
Um das System besser zu verstehen - Neben den OS-Tasks laufen noch 3
harte Realtime-Tasks (Timer Interrupts). Im 1ms Task laufen nur
SW-Timer - Laufzeit vernachlaessigbar. Im 10ms Task laeuft im
Wesentlichen das PID - in diesem Fall ein Touchscreen. Wenn man mit 8
Fingern drauf rumtrommelt steigt die Rechenzeit auf ca. 1ms, also ca.
10%. Im 100ms Task laeuft neben einer SW-Uhr im Wesentlichen die IIC
Kommunikation. Die Gehaeuseinnentemperatur brauchen wir seit der
Umstellung von STN auf TFT eigentlich nicht mehr. Sie wurde ohnehin nur
alle paar Minuten gelesen. Auf die RTC wird nur beim Uhr stellen
schreibend zugegriffen. Um die IIC Zugriffe zu minimieren wird im
Betrieb aus der SW-Uhr gelesen. Bleibt das FRam.
Grosse Datenmengen werden auf CF oder MMC gehalten. Das ist aber relativ
lahm und unsicher bei power fail. Fuer Statusbytes und Parameterbloecke
ist das NVRam da. Das habe ich segmentiert in Bytes, Worte, Longs und
unterschiedlich grosse Bloecke. Frage war, wie gross darf ich den
groessten Block machen, um Echtzeit zu garantieren.
Aus dem Bauch raus habe ich mal maximal 50% fuer die harte Echtzeit
angenommen. Datentransfer auf den beiden CAN-Bussen und der andere Kram,
den ich oben nicht genannt habe, machen etwa 10% aus. Bleiben also grobe
Kante 30ms fuer die groessten Bloecke. Schreibt also eine Applikation
einen grossen Block ins Shadow-RAM ist kurz darauf der 100ms Task fuer
maximal 30ms beschaeftigt - und 30 mal kommt der OS_Timer_Tick nicht
durch.
Das fuehrte dann zu folgenden, lustigen Fehlern. Normalerweise antwortet
ein Modul am CAN-Bus innerhalb von 5ms. Der timeout wurde auf 20ms
gesetzt. Das geht auch 100000 Mal gut. Aber wir hatte alle paar Tage mal
einen timeout. Immer dann, wenn ein Task auf eine Antwort wartete,
waerend der andere vorsichtshalber mal den kompletten Geraetestatus
abspeicherte, um im Fall des Falles nach einem Powerfail evtl. an der
gleichen Stelle weitermachen zu koennen (schreibt sich jetzt schnell -
die Suche war es nicht).
So - was tun? Timeout verlaengern? Bloecke verkuerzen? Retry
Mechanismus? Anderswie zurechtfrickeln? Oder eben ein OS anstelle des
Taskswitchers einsetzen (siehe meinen Ausgangspost).
Jetzt ist alles ganz einfach. Einzelne Bytes oder Worte werden direkt
aus der Applikation heraus geschrieben. Bloecke gehen nach wie vor
uebers Shadow-Ram. Der ganze IIC-Code ist in einem OS Task gekapselt.
Ueber Signale bekommt der mitgeteilt, ob er die Uhr stellen, einen Block
ins FRam schreiben oder sonstwas auf dem IIC-Bus anstellen soll. Liegt
nichts an, ist dieser Task tot.
An dieser Stelle sieht man dann auch, warum ich den OS_Timer_Tick
unbedingt bei 1ms lassen wollte. Der 100ms Task ist jetzt praktisch leer
- die SW-Uhr braucht so gut wie keine Rechenzeit. Ueber den CAN-Bus hat
das Board als Master volle Kontrolle, und der Touch ist im Normalbetrieb
in Ruhe. So stehen fuer die OS-Tasks knapp 98% an Rechenleistung zur
Verfuegung, und so im 5ms-Bereich kann man jetzt sogar bei den OS-Tasks
von weicher Echtzeit reden.
Puuh - viel Text. Ein weiteres Beispiel nur noch in Stichworten. Es ist
jetzt relativ einfach, gleichzeitig mehrere Files zu oeffnen, und quasi
gleichzeitig zu lesen oder zu schreiben.
Gruss Udo
Joerg Wolfram wrote:
> Meine Meinung ist, dass ein multitaskingfähiges OS in einem ROM ein> totaler Overhead ist. Es sind ja alle Prozesse und Funktionen bekannt,> ein OS zu verwenden ist eigentlich nur ein Grund, ineffizient zu> programmieren. Anders sieht es aus, wenn zum Erstellungszeitpunkt> "unbekannte" Prozesse später auch geladen und entladen werden sollen.
Ich verstehe nicht wieso das ein Kriterium sein soll - die Vorteile
eines RTOS, nämlich dass es einem abnimmt was Peter und viele andere von
Hand machen, haben nichts damit zu tun ob Prozesse zur Laufzeit
hinzugefügt/entfernt werden sollen. Das würde ja auch die
Echtzeitfähigkeit und damit das "RT" in RTOS kaputtmachen.
Hallo Joerg,
ich habe hier 'nur' zwei Tasks. Und die sind mir auch seit Monaten
bekannt. Aber die sind nicht kooperativ. Wie soll ich die denn laufen
lassen? Wenn Du etwas effizienteres kennst wie diese Minimalversion
eines OS, dann wuerde ich mich freuen, wenn Du mir das mitteilst.
Gruss Udo
@uCler
>ich habe hier 'nur' zwei Tasks. Und die sind mir auch seit Monaten>bekannt. Aber die sind nicht kooperativ. Wie soll ich die denn laufen>lassen?
Wieso nicht kooperativ? Schlecht programmiert?
MfG
Falk
uCler wrote:
...
> Der ganze IIC-Code ist in einem OS Task gekapselt.
...
Ich verstehe ehrlich gesagt überhaupt nicht, wo da das OS hilft.
Was bedeutet "I2C ist ein OS-Task" praktisch gesehen ?
Ich hätte I2C in seinen Interrupthandler gepackt und gut is.
Dann noch ein Struct definieren, in dem die Slaveadresse, Datenpointer
und Datenlänge abgelegt werden und los geht der Interrupt.
Die Tasks, die dann I2C machen wollen, gucken nach, ob dieser Struct
frei ist und starten dann I2C.
Oder man legt ein Array an (für jede Task ein Eintrag), das der
I2C-Interrupt abklappert.
> Puuh - viel Text. Ein weiteres Beispiel nur noch in Stichworten. Es ist> jetzt relativ einfach, gleichzeitig mehrere Files zu oeffnen, und quasi> gleichzeitig zu lesen oder zu schreiben.
Hmm, Files öffnet man doch per Filehandler.
Auch in nem Single-Task-DOS kann ich mehrere Files öffnen (soviel, wie
Handler frei sind).
Peter
P.S.:
Ein Kollege von mir, der den CAN-Bus programmiert, wollte auch gerne
eine maximale Latenzzeit von 2ms haben.
Ich wollte aber einen 1-Wire Temperatursensor benutzen, was länger
gedauert hätte.
Ich hab ihm dann einfach nen Timerinterrupt geschrieben, der dann
bitweise das 1-Wire gemacht hat und dann gings wieder.
In nem Struct wurde abgelegt, wieviel Bytes nach dem 1-Wire-Reset zu
senden sind, wieviel zu empfangen und wenn beide Zähler 0 waren, hat
sich der Interrupt selber disabled.
Ich arbeite sehr gerne mit Interrupts.
Auch wenn ich jetzt alle Zusammenhänge nicht kenne, versuche ich mal zu
schreiben, wie ich an die Sache herangehen würde.
Das erste Problem sind die nichtkooperativen Tasks. Sowas sollte es
eigentlich nicht geben, ausser sie wurden von einem anderen System mehr
oder weniger übernommen. Da es jetzt ja funktioniert, scheinen sie ja
vorher jede Menge Zeit nutzlos verbraten zu haben. Da muß ich Falk voll
recht geben, so etwas kann nicht besonders gut programmiert sein oder
stammt bereits aus einem Kontext wo das nichts ausmachte.
Manchmal werden auch Dinge in Interruptroutinen verfrachtet, die dort
nichts zu suchen haben. Muß zum Beispiel die Routine für das Touchpanel
unbedingt im Interrupt laufen oder werden bloß über AD-Wandler die
Koordinaten abgefragt und dann berechnet. In dem Fall könnte man
vielleicht den ADC im 1ms-Interrupt abfragen und die Ergebnisse in den
Speicher schreiben. Die eigentliche Berechnung der Koordinaten kann dann
im Hauptprogramm erfolgen. Meiner Meinung nach ist es besser, über ein
Zählregister kleinere Aufgaben im schnelleren Interrupt mit zu machen
und dafür eine Interruptquelle weniger zu haben.
Ich programmiere viel in C, aber auf den 8-Bit Controllern bin ich damit
überhaupt nicht warm geworden. Hier halte ich einen guten Makroassembler
und ein paar passende Bibliotheken für die bessere Wahl. Ich will damit
jetzt keinen Glaubenskrieg auslösen wegen Portabilität und so weiter,
aber bis jetzt ist bei mir eigentlich bei jedem notwendigen
Systemwechsel (warum auch immer) nur die grundsätzliche Funktionalität
des Programms geblieben, weil man viele Dinge schon lange anders machen
wollte...
Gruß Jörg
Peter Dannegger wrote:
> Ein Kollege von mir, der den CAN-Bus programmiert, wollte auch gerne> eine maximale Latenzzeit von 2ms haben.> Ich wollte aber einen 1-Wire Temperatursensor benutzen, was länger> gedauert hätte.> Ich hab ihm dann einfach nen Timerinterrupt geschrieben, der dann> bitweise das 1-Wire gemacht hat und dann gings wieder.
Es gibt immer eine Möglichkeit etwas in kleine Zeitscheibchen zur
Ausführung in Hauptschleife/Interrupt zu zerteilen, aber MANCHMAL ist
der Aufwand einfach größer als wenn man dafür gleich ein RTOS verwendet.
Wenn man allerdings eine ideologische Abneigung gegen RTOS mit
Vorurteilen wie "langsam/großer Overhead/Notlösung" hat...
Andreas Schwarz wrote:
> Es gibt immer eine Möglichkeit etwas in kleine Zeitscheibchen zur> Ausführung in Hauptschleife/Interrupt zu zerteilen, aber MANCHMAL ist> der Aufwand einfach größer als wenn man dafür gleich ein RTOS verwendet.> Wenn man allerdings eine ideologische Abneigung gegen RTOS mit> Vorurteilen wie "langsam/großer Overhead/Notlösung" hat...
Ich habe leider bisher nirgends ein Beispiel gefunden, woran man die
Vorteile eine RTOS aufm MC sehen konnte.
Das 1-Wire ist ja gerade nicht für ein RTOS geeignet.
Eine Bitzeit muß ja zwischen 60..120µs lang sein, d.h. man muß
garantieren, daß eine zusätzliche Verzögerung höchstens 60µs betragen
darf.
Auf dem 8051 war das ganz einfach, der T0 wurde so eingestellt, daß nach
60µs der nächste Interrupt auftritt und ihm wurde Priorität 3
zugewiesen, d.h. er konnte sämtliche anderen Interrupts unterbrechen.
Zusätzliche Latenzen enstehen nur dort, wo atomare Zugriffe auf
Mehrbytevariablen erfolgen und die sind ja nur wenige µs lang.
Ein RTOS mit nur max 60µs Latenz wäre dagegen auf dem 8051 unmöglich.
Anbei mal der komplette 1-Wire Code.
Peter
Frage an die RTOS-Experten:
Könnt ihr einige Bücher / Fachartikel (vorzugsweise in deutscher
Sprache, aber auch gerne in englisch) nennen, in denen die prinzipiellen
Funktionsweisen eines RTOS und auch die Vorteile/Nachteile der
verschiedenen Systeme (kooperativ, preemtiv usw.) erläutert werden?
(sowohl für Einsteiger als auch für Fortgeschrittene)
Vielen Dank.
Andreas Schwarz wrote:
> Z.B. zu den Nachteilen von preemtiven Systemen:> http://www.embedded.com/showArticle.jhtml?articleID=192701173
Da steht leider nichts neues für mich drin.
Hast Du denn keinen Artikel pro RTOS ?
Ich geb natürlich zu, daß ich mit Assembler angefangen hab (8051, AVR).
Dadurch kann ich ziemlich gut einschätzen, wie teuer eine Task ist und
wo ich sie dann einordnen muß (Interrupt, Mainloop, Scheduler).
Peter
Erstmal musst du lernen zwischen preemtiven und kooperativen OS zu
unterscheiden. Das was du von Hand machst ist exakt das was ein
kooperatives OS macht.
Andreas Schwarz wrote:
> Das was du von Hand machst ist exakt das was ein> kooperatives OS macht.
Bloß mit dem großen Unterschied, daß ich genau bestimmen kann, in
welcher Reihenfolge sie ausgeführt werden.
Nur bei den Tasks, die ich dem Scheduler übergebe, weiß ich die
Reihenfolge nicht mehr, aber bei denen ist es in der Regel auch nicht
kritisch.
Und nur diese Tasks haben dann auch den Scheduler-Overhead.
Peter
Vielleicht reden wir auch aneinander vorbei, weil jeder eine andere
Definition von "OS" hat. Eine Kombination aus Taskmanager, Scheduler
etc. die ich an meine Tasks anpassen muß, ist für mich kein OS. Beim uC
wäre das (für mich) ein monolithischer Kernel, dem ich die zu
bearbeitenden Tasks als Tabelle vorgebe (Einsprungadresse, Intervall,
Priorität). Die Schnittstellen sind definiert und alle Tasks (auch noch
zu schreibende ;-) müssen sich daran halten. Der Overhead entsteht bei
der Prozessumschaltung und im Task selbst, da zumindest beim AVR zur
Laufzeit nur Resourcen im RAM (über Pointer) zugewiesen werden können.
Richtig preemptives Multitasking geht meiner Meinung nach sowieso nur,
wenn ein unkooperativer Task es nicht verhindern kann. Wenn aber jeder
Task einfach die Interrupts abschalten kann, dann wird da wohl eher nix
daraus. Oder es ist eben doch kein OS (nach meinem Verständnis)
Gruß Jörg
Joerg Wolfram wrote:
> Vielleicht reden wir auch aneinander vorbei, weil jeder eine andere> Definition von "OS" hat.
Ein OS ist etwas, das Hardwareressourcen (=Rechenzeit) verwaltet und
zuteilt.
> Eine Kombination aus Taskmanager, Scheduler> etc. die ich an meine Tasks anpassen muß, ist für mich kein OS.
Dass man den Scheduler an seine Tasks anpasst wäre so ungefähr Peters
von-Hand-Modell.
> Richtig preemptives Multitasking geht meiner Meinung nach sowieso nur,> wenn ein unkooperativer Task es nicht verhindern kann. Wenn aber jeder> Task einfach die Interrupts abschalten kann, dann wird da wohl eher nix> daraus.
Anders geht es nicht, oder wie willst du sonst Hardwarezugriffe oder IPC
realisieren, wenn überall der Scheduler dazwischenfunken kann?
Andreas Schwarz wrote:
> Ein OS ist etwas, das Hardwareressourcen (=Rechenzeit) verwaltet und> zuteilt.
Zu den Resourcen die verwaltet und verteilt werden müssen, gehören aber
auch Speicher und Zugriff auf die Peripherie. Bei einem Controller ohne
umschaltbare Registerbänke kann man den Tasks viele Register zur
Verfügung stellen, die dann natürlich auch während der Taskumschaltung
gerettet und restauriert werden müssen (=>braucht Zeit und Speicher).
Oder man beschränkt sich auf wenige Register und der Task muss häufiger
auf den Speicher zugreifen.
> Anders geht es nicht, oder wie willst du sonst Hardwarezugriffe oder IPC> realisieren, wenn überall der Scheduler dazwischenfunken kann?
Für Hardwarezugriffe gibt es OS-Routinen (z.B. BIOS). Und ein Task
sollte die auch nutzen, anstelle selbst auf die Hardware zuzugreifen.
Ich will auch nicht den Sinn eines OS in Frage stellen, aber um das
Timing von quasiparallel ablaufenden Funktionen in einem Mikrocontroller
zu verbessern, halte ich es für eine schlechte Wahl.
Gruß Jörg
Joerg Wolfram wrote:
> Andreas Schwarz wrote:>>> Ein OS ist etwas, das Hardwareressourcen (=Rechenzeit) verwaltet und>> zuteilt.>> Zu den Resourcen die verwaltet und verteilt werden müssen, gehören aber> auch Speicher und Zugriff auf die Peripherie.
...
> Für Hardwarezugriffe gibt es OS-Routinen (z.B. BIOS). Und ein Task> sollte die auch nutzen, anstelle selbst auf die Hardware zuzugreifen.
Die Unterscheidung zwischen Hardwaretreibern und normalen Tasks, wie man
sie bei größeren Systemen (Linux, Windows) kennt, gibt es bei den
kleinen Mikrocontroller-OSen nicht. Sie bestehen aus Scheduler, Timer,
Synchronisationsmechanismen. Es geht nicht darum Hardware zu
abstrahieren, sondern nur darum Rechenzeit zuzuteilen.