Hallo zusammen,
nachdem ich länger (hier und anderswo) nach einer minimalen und schön
verständlichen Implementierung für kooperatives Multitasking auf nem
Atmega gesucht habe, wollte ich mal kurz meine eigenen Ideen vorstellen
und fragen, ob das so funktionieren könnte, oder wo die Fallstricke
sind. Es ist wahrscheinlich nichts besonders neues oder innovatives,
aber ich wahr ehrlichgesagt zu faul, mich ernsthaft in diese riesigen
super komfortablen OSe wie FreeRTOS und Konsorten einzulesen und wollte
lieber was eigenes einfaches haben.
Was ich möchte, ist möglichst Wartezeiten, zum Beispiel beim
Displayzugriff oder so, nicht einfach vertrödeln, sondern von anderen
Tasks nutzen lassen. Außerdem ist meine State-Machine in der Main-loop
einfach zu unübersichtlich geworden. Das ganze sollte so einfach wie
möglich sein und am besten ganz ohne komplizierten Kontextwechsel etc.
auskommen.
Was ich mir deshalb überlegt hab ist folgendes:
Jede Funktion (bzw. Task) der irgendwo warten muss, wird als sogenannte
Coroutine implementiert. Das bedeutet, dass mit ein bisschen
Präprozessor-Vodoo automatisch eine State-Machine generiert wird und man
an jeder beliebigen Stelle die Funktion verlassen kann, um beim nächsten
Aufruf an gleicher Stelle weiterzumachen. Sehr schön hier erklärt (und
auch dort geklaut):
http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
Jeder Task, der vorher in der Main-loop lief hat jetzt eine Struktur
bestehend aus Funktionspointer und einem Timer. Der Timer wird in einem
"TimerTick", der in einem Hardware-Timer läuft, regelmäßig
heruntergezählt. Ist er Null, führt der Minischeduler (läuft in der Main
als Endlosschleife) diesen aus. Will ein Task warten, gibt er einfach
mit Hilfe des Coroutines Mechanismus die Wartezeit beim return zurück
und wird dann vom Scheduler wieder nach entsprechenden Ticks aufgerufen.
Damit das gewarte auch über mehrere Aufrufebenen funktioniert, müssen
Functionen, die ebenfalls warten könnten, also Coroutines sind,
folgendermaßen aufgerufen werden:
Damit wird die Unterfunktion so oft aufgerufen, bis sie TASK_FINISH
zurückgibt und zwischendurch wird die entsprechende Zeit gewartet.
Soweit mein Konzept, jetzt meine Frage: Ist das so machbar und worauf
muss man noch achten, damit einem die ganze Sache nicht um die Ohren
fliegt?
Vielen Dank schonmal
Das läuft jetzt in eine ganz andere Kerbe
> Außerdem ist meine State-Machine in der Main-loop> einfach zu unübersichtlich geworden.
Da du ja mit State Machines offenbar schon Erfahrung hast:
Kannst du die deine State Machine nicht in mehrere aufteilen?
Wir haben vor Jahren eine Maschinensteuerung gemacht, in der wir einfach
für jedes Subsystem eine eigene State-Machine implementiert haben. Aus
der Hauptschleife sind die einfach reihum alle nacheinander aufgerufen
worden. Dadurch dass sich jede SM nur auf einen Teilaspekt der
kompletten Maschine konzentriert hat, ist jede einzelne auch schön klein
und überschaubar geblieben.
Natürlich gab es so Zugaben wie Timer, mit denen jede Statemaschine
einen zeitlich definierten Wartezustand abwarten konnte. Die
Statemaschinen hatten einen Gesamtzustand "running" bzw. "waiting", war
eine Maschine im Zustand waiting wurde sie vom Round Robin Scheduler
einfach nicht bedient bis der Timer die zugehörige Wartezeit
runtergezählt hat.
Aber im Grunde war das System sehr einfach und simpel und kam ohne
irgendwelche "Spracherweiterungen" aus.
Neuling schrieb:> Was ich möchte, ist möglichst Wartezeiten, zum Beispiel beim> Displayzugriff oder so, nicht einfach vertrödeln, sondern von anderen> Tasks nutzen lassen.
Geht z.B. hiermit:
Beitrag "Wartezeiten effektiv (Scheduler)"
Peter
@Karl Heinz
Im Grunde mache ich ja genau das; jeder Task ist ja nichts anderes als
eine eigene State Machine, die nacheinander aufgerufen werden.
Allerdings wird innerhalb dieser obersten Funktionsebene häufig auch mal
eine Funktion aufgerufen, die länger braucht und diese ruft dann
vielleicht noch eine "API-Funktion" eines "Treibers" auf, der aber
wiederum auf sein angespochenes Gerät (In meinem Fall LCD oder RFM12
Funkchip) warten muss. Das würde bedeuten, ich müsste für ALLE
Funktionen, die selber warten müssen, oder eine wartende Funktion
aufrufen, eine neue State Machine per Hand bauen und mir neue
individuelle State Label ausdenken. Im Prinzip habe ich ja genau das
jetzt nur organisiert und mit Makros ein bisschen automatisiert.
@Peter
Vielen Dank für den Hinweis, deinen Beitrag habe ich natürlich auch
schon gesehen, aber da kam wieder meine Faulheit ins Spiel, besonders,
nachdem ich den Satz "Das Programm ist relativ komplex und schwer zu
erklären." in deiner eigenen Doku gelesen hab. Außerdem kann man damit,
wenn ich das richtig verstanden habe, einen Task immer nur komplett
ablaufen lassen und nicht in der Mitte unterbrechen. Das heißt bei einem
langen Task ist die CPU auch entsprechend lange blockiert, richtig?
Kooperatives Multitasking besteht darauf, dass der Programmierer den
verschiedenen Segmenten passend Zeit einraeumt, dh zu lange dauernde
Teile verden eingekuerzt. Anstelle von einem 32bit-to-ASCII am Stueck
kann man vielleicht nur ein Digit pro durchgang rechnen. Wenn's fuer
den Display ist, braucht man auch keine 1000 updates pro sekunde,
sondern vielleicht 10, oder nur 5.
Neuling schrieb:> Außerdem kann man damit,> wenn ich das richtig verstanden habe, einen Task immer nur komplett> ablaufen lassen und nicht in der Mitte unterbrechen. Das heißt bei einem> langen Task ist die CPU auch entsprechend lange blockiert, richtig?
Kooperatives Multitasking heißt ja, Du entscheidest, wann Du zum Main
zurückkehrst.
Du kannst eine lange Funktion unterteilen und dann nach jedem Teil zum
Main zurückkehren:
Peter Dannegger schrieb:
> Kooperatives Multitasking heißt ja, Du entscheidest, wann Du zum Main> zurückkehrst.> Du kannst eine lange Funktion unterteilen und dann nach jedem Teil zum> Main zurückkehren:
Ja, genau das machen ja die Coroutinen, nur eben etwas praktischer, da
formalisiert und automatisiert und wenn man das für 100 Funktionen
machen muss, will man ja ungern hundert switch statements mit jeweils
einigen Dutzend cases schreiben.
Meine Frage war ja eigentlich: Ist mein Konzept abwegig weil es
ineffizient ist, oder funktioniert es nicht weil... ? Oder kann ich das
so umsetzten. Teilweise ist es auch schon umgesetzt und funktioniert bis
jetzt ganz prächtig mit wenigen Tasks und wenig verschachtelten
Funktionsaufrufen.
Neuling schrieb:> Ja, genau das machen ja die Coroutinen, nur eben etwas praktischer, da> formalisiert und automatisiert und wenn man das für 100 Funktionen> machen muss, will man ja ungern hundert switch statements mit jeweils> einigen Dutzend cases schreiben.
Wenn Du 100 lange Funktionen hast, dann mußt Du sie unterteilen, daran
führt kein Weg vorbei. Wie Du das machst, da gibt es mehrere
Möglichkeiten.
Das mit dem Switch ist sehr effektiv, jede Funktion merkt sich selber,
wo es weiter geht. Auch ist durch die Statemachine die Reihenfolge
eindeutig festgelegt, Teilfunktionen können sich nicht überholen.
Man kann aber auch die 100 Funktionen in z.B. 10 Unterfunktionen
unterteilen, dann muß man aber irgendwie diese 1000 Aufrufe schedulen.
Peter
Für gewisse Sachen geht es gut, ist aber eine andere Denkweise und alles
passiert in einer Funktion. Du kannst dir das so vorstellen,
TASK_SERIAL ist ein label und alles spielt sich in der Main ab.
wenn du sowas willst, könnte ich task.h neu schreiben, ist schneller
als das suchen.
#include "task.h"
TASK(SERIAL)
VAR char buff[10];
VAR char crc;
INIT
setupRs232();
ALWAY
if(!inchar()) END
BEGIN
... yield();
....
yield();
......
.....
#define SERIAL_CMD1 50
#define SERIAL_CMD2 70
#define SERIAL_CMD3 90
#define SERIAL_CMD4 90
if(c<4) STATE=SERIAL_CMD1+c*20;
REPEAT
STATE = SERIAL_CMD1
... yield();
... yield();
if (!~crc) yield();
END
STATE = SERIAL_CMD2
...
END
STATE = SERIAL_CMD3
...
TASK(foo)
TASK(bar)
#include "task.h"
Ich schlag mal mein nanoos vor. Der Scheduler ist zwar hauptsächlich für
preemptive Multitasking ausgelegt, kann aber auch für cooperative
Multitasking genutzt werden (dazu gibt es die Makros
TASK_ENABLE_MULTITASKING und TASK_DISABLE_MULTITASKING).
www.sourceforge.net/projects/nanoos
gruß
tobi
Man muß auch unterscheiden, hat man eine Funktion, die wirklich viel,
d.h. lange rechnen muß oder enthält die Funktion einfach nur lange
Wartezeiten.
Lange Berechnungen kann man unterteilen.
Lange Wartezeiten macht man mit dem Scheduler. Eine Funktion stellt die
nächste mit der Wartezeit in den Scheduler.
Natürlich kann eine Funktion auch sich selber in den Scheduler stellen.
Ich mache das z.B. mit der Display-Routine so. Sie stellt sich alle
200ms rein, um eine ergomische Anzeigerate von 5Messungen/s zu erzielen.
Um sie zu starten, wird sie einmalig vor der Mainloop aufgerufen.
Peter