Forum: Mikrocontroller und Digitale Elektronik minimales kooperatives Multitasking


von Neuling (Gast)


Lesenswert?

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:
1
while((uint16_t wartezeit=Unterfunktion())!=TASK_FINISH)
2
  scrReturn(wartezeit);
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

von (prx) A. K. (prx)


Lesenswert?

Vielleicht hilft dir auch das weiter: http://www.sics.se/~adam/pt/

von Karl H. (kbuchegg)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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

von Neuling (Gast)


Lesenswert?

@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?

von Purzel H. (hacky)


Lesenswert?

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.

von Falk B. (falk)


Lesenswert?

Siehe Multitasking

von Peter D. (peda)


Lesenswert?

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:
1
void long_func()
2
{
3
  static uint8_t state = 0;
4
5
  switch( ++state ){
6
    case 1: part1(); break;
7
    case 2: part2(); break;
8
    // ...
9
    default: state = 0;
10
  }
11
}

Peter

von Neuling (Gast)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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

von Chris (Gast)


Lesenswert?

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"

von hacker-tobi (Gast)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.