Hallo zusammen,
ich würde gerne wissen wir ihr folgendes Problem lösen würdet.
Mir geht es nicht unbedingt um den unten dargestellten Code, sondern wie
man das im allgemeinen am besten löst...
Pseudo-Code:
1
voiddoX()
2
{
3
TransmitCanMessage(x,y,z);// Beginn der CAN-Übertragung
4
while(GetCanTransmitStatus()==Pending);// Warten bis CAN-Nachricht gesendet. Kann lange dauern, da nur 10 kBit/s...
5
6
doA();
7
}
8
9
intmain(void)
10
{
11
for(;;)
12
{
13
doX();
14
doY();
15
doZ();
16
}
17
18
return0;
19
}
Die Funktion doA soll erst ausgeführt werden, wenn die CAN-NAchricht
erfolgreich gesendet wurde. Gleichzeitig soll die Funktion doY und doZ
regelmäßig aufgerufen werden, d.h. es kann nicht jedes mal die lange
Zeit gewartet werde, bis die CAN-Nachricht gesendet wurde.
Folgende Lösung ist mir eingefallen, welche aber nicht besonders wartbar
finde und sich schwierig gestaltet, wenn doX() in irgendeiner
Unterfunktion auftritt und mehrfach verschachtelt ist:
Pseudo-Code:
1
voiddoX()
2
{
3
staticintsendFinished=1;
4
if(sendFinished==1)
5
{
6
TransmitCanMessage(x,y,z);
7
sendFinished=0;
8
}
9
10
if(sendFinished==0)
11
{
12
if(GetCanTransmitStatus()==Finished)
13
{
14
sendFinished=1;
15
}
16
}
17
18
if(sendFinished==1)
19
{
20
doA();
21
}
22
}
23
24
intmain(void)
25
{
26
for(;;)
27
{
28
doX();
29
doY();
30
doZ();
31
}
32
33
return0;
34
}
Welche Lösung fällt euch für diese Art von Problem ein?
Vom Prinzip her ist das schon genau richtig: Nicht auf Dinge warten,
sondern auf Ereignisse reagieren. Ereignisse können Interrupts sein oder
per Polling festgestellt werden. Pro Durchgang der Hauptschleife kann
man jeder Ereignisquelle einmal pollen.
Den konkreten Code könnte man vielleicht folgendermaßen vereinfachen,
das hängt aber natürlich von der genauen Semantik der Funktionen ab:
Danke Hans. Ja die Funktion hätte man deutlich einfacher gestalten
können. Mir geht es weniger um die spezielle Lösung, sondern alternative
Lösungsansätze...
>Mir geht es weniger um die spezielle Lösung, sondern alternative>Lösungsansätze.
Deine ankommende Nachricht kann sicherlich einen CAN Interrupt
auslösen. Dann machst du doA(); halt im Interrupt.
Oder per Callback. Was aber auf das gleiche hinausläuft.
>Deine ankommende Nachricht kann sicherlich einen CAN Interrupt
Schwachsinn, du willst ja wissen wann deine Nachricht gesendet
wurde. Löschen meinen letzten Post.
Mit Timer würde mir keine Lösung einfallen...
Danke Holger. Es geht nicht um die ankommende, sondern um die Abfrage,
ob die Nachricht gesendet wurde. Prinzipiell hast du Recht, danke für
die Alternative.
Schon etwas von thread gehört?
Eine Funktion wird gestartet und läuft dann im Hintergrund und tut was.
-> Ein Thread.
Zu geeigneten Zeitpunkten sendet diese dann die Ergebnisse an andere
Threads.
Die einzelnen Funktionen kommunizieren über diese Nachrichten.
Ich glaube in diese Richtung ging die Frage.
Gruß
Falls Du es nicht schon kennst, hilft Dir vielleicht das Konzept
"Zustandsautomat". So lassen sich komplexe Abläufe, die unterbrochen
werden, recht übersichtlich darstellen:
1
enum{
2
STATE_A,
3
STATE_B,
4
STATE_C,
5
STATE_D,
6
};
7
8
voidnextStep(void)
9
{
10
staticuint8_tstate=STATE_A;
11
switch(state)
12
{
13
caseSTATE_A:
14
doA();
15
state=STATE_B;
16
break;
17
caseSTATE_B:
18
doB();
19
state=STATE_C;
20
break;
21
caseSTATE_C:
22
doC();
23
state=STATE_D;
24
break;
25
caseSTATE_D:
26
doD();
27
state=STATE_A;
28
break;
29
}
30
}
Die Zustände müssen natürlich nicht linear aufeinander folgen. Sie
können z.B. von Bedingungen wie Nachricht fertig gesendet, neues Zeichen
empfangen, Timer abgelaufen etc. abhängen.
Wie du glaube ich schon geschrieben hast, habe ich schon ein
Zustandsautomat implentiert (nur halt mit if's).
Für mich ist die Frage, ob es Sinn macht oder einfachere Lösungen gibt,
anstatt immer einen Zustandsautomat mit einer Zustandsvariable (bei mir
sendFinished) zu bauen.
Wenn das Problem mehrfach in einem Programm auftaucht, kann das doch
sehr fehlerträchtig werden...
Naja, irgendwie muss sich das System den Zustand merken. Entweder indem
Du ihn explizit in einer Zustandsvariable speicherst oder implizit im
Kontext eines Prozesses/Tasks/Threads. Dort besteht der Zustand dann
eben aus Instructionpointer, Stackpointer, Registerinhalten etc..
Auf kleinen Mikrocontrollern ist es logischerweise wesentlich
effizienter, selber ein Byte für den Zustand zu verwalten, als ein RTOS
damit zu beschäftigen.
Besonders praktisch ist es, wenn der Zustand in den Peripherieregistern
des Controllers gespeichert ist. Z.B. in Form des
CAN-Controller-Statusregisters, in dem ein Bit gesetzt ist, nachdem die
Nachricht gesendet wurde. Dann braucht man gar keinen zusätzlichen
Speicher für den Zustand. Geht aber natürlich nicht immer.
Ich neige dazu, in ISR die zeitkritischen Aufgaben auszuführen, ein Flag
zu setzen und überlasse der verarbeitenden Schicht, welche von main
aufgerufen wird die Prüfung des Flags, genau wie du es machst. das geht
auch ohne RTOS, like OS.
Ich kann dir das Studium des 8 Betriebssystems von ATARI XL empfehlen
ist zwar angestaubt aber übersichtlich. Ausfühlich in "Mein Atari
Computer" "ATARI 800 XL intern" allerdings muss man ohne Listings
auskommen, da die Einsprungpunkte der Routinen sich von Version zu
Version verschoben.
Dafür sind die Funktionsweisen der Selben gut beschrieben. Wie auch die
Struktur des BIOS.
Mit dem Monitor habe ich mein XE ROM damals durchforscht und
modifiziert, was durch Spiegelung des ROM ins RAM (Dank vorhandenem
Mapping)leicht möglich war.
Miu schrieb:> TransmitCanMessage(x,y,z); // Beginn der CAN-Übertragung> while (GetCanTransmitStatus() == Pending); // Warten bis> CAN-Nachricht gesendet. Kann lange dauern, da nur 10 kBit/s...
Jaja, so macht man es eben lieber nicht, wenn man sich nicht selbst
blockieren will. Ich mach das (grob skizziert) so:
main(..)
{ int result;
.. init diverses;
immerzu:
if (!CAN_Pending)
{ if (SomethingToDoWithCAN)
DoWhatIsToBeDoneButDoNOTwaitForTheEnd();
}
else
{ result = LookOnStopwatch();
if (result==CANwasTooLazy) KillTheCANandReInitHim();
}
if (somethingelse) DoWhateverIsToBeDoneWithIt()
.. usw. z.B.
if (EventAvailable()
{ DispatchEvent(GetEvent());
}
goto immerzu;
Eben einfach so schreiben, daß man NICHT wartet.
W.S.
Bitte nicht den Schwachsinn vom Dr. Dunkels (der will dir alle lokale
Variablen wegnehmen .-)
> Falls es wie RTOS aussehen soll, aber in 4k passen muss...> http://dunkels.com/adam/pt/index.html
sondern:
Beitrag "Multithreading auf LPC800 - 86Byte(!) Code-Groesse"
Der Teil, der dich am ehesten interessiert ist die Umsetzung von
sleepMs() in main.c : Wartezeit mit sinnvollerem verbringen.
Wo hat man denn nur 4K Code?
Auf dem LPC810, LPC1110, ... hoffe ich doch mal?
Marc P. schrieb:> Bitte nicht den Schwachsinn vom Dr. Dunkels (der will dir alle lokale> Variablen wegnehmen .-)
Bin ein regelmaessiger Benutzer dieser von Dir als Schwachsinn
bezeichneten Technik. Und kann's jedem empfehlen, der nach RTOS
aehnlichen Möglichkeiten sucht, aber kein echtes RTOS aufsetzen möchte.
Für solche Sachen haben fast alle µCs interrupts. Oft hat auch schon das
CAN Interface einen interrupt, wenn er fertig ist.
Man muss sich nur trauen in der ISR mehr als nur ein Flag zu setzen -
das was bis zum nächsten Interrupt erledigt sein muss, darf in aller
Regel auch als Code in die ISR - wenn das zu viel ist, ist der µC
einfach überfordert, denn anders wird es nur noch langsamer.
Ulrich H. schrieb:> Man muss sich nur trauen in der ISR mehr als nur ein Flag zu setzen -> das was bis zum nächsten Interrupt erledigt sein muss, darf in aller> Regel auch als Code in die ISR - wenn das zu viel ist, ist der µC> einfach überfordert, denn anders wird es nur noch langsamer.
Kommt halt darauf an, wie deterministisch es werden soll/muss. Wenn man
einen relativ langen Interrupt hat und dieser auch noch Burst-Artig in
kurzen Abständen kommen kann, kann es passieren das man andere Tasks
nicht mehr in definierten Zeitabständen bedienen kann. Daher tendiere
ich eher dazu kritische Sache (Notaustaster, Endstellungsschalter am
Motor, Überlastabschaltungen, etc.) direkt im Interrupt zu machen und
den Rest lieber in ein Flag zu packen und in der Hauptschleife darauf zu
realisieren.
Einfache Dinge wie z.B. ein empfangenes Byte von der RS485 in einen
Puffer abzulegen macht man natürlich direkt in der ISR. Aber sobald man
auf irgendetwas warten muss (z.B. ein weiteres seriell übertragenes Byte
oder ein Write_Done Flag in einem externen Speicher oder ...) ist es
besser das nicht im ISR zu machen. Man muss halt vorher wissen, wie
lange die ISR im schlimmsten Fall brauchen kann und wie oft das Ereignis
auftritt.
Mehmet Kendi schrieb:> Bin ein regelmaessiger Benutzer dieser von Dir als Schwachsinn> bezeichneten Technik. Und kann's jedem empfehlen, der nach RTOS> aehnlichen Möglichkeiten sucht, aber kein echtes RTOS aufsetzen möchte.
... und der am liebsten wieder in die Zeit zurueckversetzt wuerde, in
der es noch keine 'Hoch'-Sprachen wie C gab.
0815 Fitzelprograemmchen, da kann man noch drueber reden vielleicht.
Aber empfehlen darf man das nicht! Du nennst es in einem Atemzug mit
'RTOS', aber auf die Einschraenkungen gehst Du ja (fahrlaessigerweise)
gar nicht ein. Ich hoffe Du kennst sie als regelmaessiger Nutzer
wenigstens!?
Man kann auch Kokain benutzen um das eine oder andere Leistungsproblem
eines Menschen zu beheben und das funktioniert vermutlich sogar. Bloss
muss man es deshalb - trotz seiner schwerwiegenden Seiteneffekte -
empfehlen ?
Kann mich dem oben nur anschliessen.
Testen ob der CAN-Controller bereit zum verschicken ist und nur was
machen wenn es denn so ist.
Extrem simpler aber funktionierender Test-Code für den ATMega16M1:
Beitrag "CAN beim ATMega16M1 anders als bei 90CANxx?"
Wobei das so sicherlich geschickter wäre:
1
while(1)
2
{
3
_delay_ms(1);
4
5
CANPAGE = (0<<4); // select MOB0
6
delay++;
7
if(delay > 49)
8
{
9
if(CANSTMOB & (1<<TXOK)) // fertig mit Senden?
10
{
11
delay = 0
12
CANSTMOB &= ~(1<<TXOK); // reset flag
13
14
CANCDMOB = (1<<DLC1); // 2 Byte
15
CANMSG = 0x55;
16
CANMSG = 0xaa;
17
CANCDMOB |= (1<<CONMOB0); // Transfer einleiten
18
}
19
}
20
}
Und ja, statt delay() im richtigen Code den µC schlafen legen am Ende
der Schleife und durch nen Timer wecken lassen.
Marc P. schrieb:> Bitte nicht den Schwachsinn vom Dr. Dunkels (der will dir alle lokale> Variablen wegnehmen .-)
Dieser "Schwachsinn" ersetzt komplexe von Hand programmierte und
unübersichtliche state machines durch jene state machine, die der
Prozessor von Haus auf mitbringt: den program counter. Nachteile hat es
natürlich auch, aber der entstehende Code ist wesentlich lesbarer als
die üblichen state machines.
Der Sinn davon liegt in kleinen Prozessoren mit einigen KB Speicher,
für die ein RTOS zu gross sein könnte.
Und der "Schwachsinn" verhindert, daß ich Stackframes anlegen "muß", für
Variablen, von denen es nur eine Instanz gibt, weil ich real keine 10
identischen Blink-Threads brauche. (ich muß natürlich nicht, aber wer
derentwegen heult, der macht das wohl)
Oder anders: static Variablen in der Thread-Funktion anlegen, dann stört
das Vergessen des Stack-Frames bei protothreads nicht wirklich.
Es geht da um einfach Abläufe, die man lieber sequenziell schreibt, als
als EventHandler-Grab. Wir befinden uns da oft auf 2Kb AVR's und nicht
auf Multicore-ARM's.
Badtler schrieb:> Oder anders: static Variablen in der Thread-Funktion anlegen, dann stört
Jede static Variable braucht ihren eigenen Platz fuer alle Zeit! Eine
Variable auf dem Stack hat gut Chancen denselben Speicherbereich zu
nutzen wie eine lokale Variable einer anderen Funktion des gleichen
Threads zu einer anderen Zeit. Und nicht jede lokale Variable ist nur 1
oder 2 Bytes gross.
Und genau da faengt das Problem an: dann wird an lokalen Variablen
gespart, oder 2 Funktionen zu einer zusammengefasst, oder auf
primitivere Typen zurueckgegriffen statt auf problemorientierte. Dann
werden ploetzlich uint8_t statt int benutzt, obwohl der uC mit einem
anderen Typ besser funktionieren wuerden, dto. mit Pointer vs.
8/16-bit-Index. Dann bastelt man ein PTLOCAL Makro das fur lokalen
Variablen in potentiellen Bibliotheksfunktionen verwendet wird (und
irgendwo eine mal vergessen;) , usw. usw.
Schwupdiwupp hat sich die Vereinfachung (Sequenzialisierung) des Ablaufs
zu einer 'Vermurksung' des Codes ueber das aktuelle Projekt hinaus
verwandelt. Ein bekannter Effekt von erstbesten Loesungen.
Darum bin ich so ein Gegner der Protothreads. Sie provozieren geradezu
schlechten Code. Okay ich will es nicht mehr 'Schwachsinn' nennen,
sondern 'Leichtsinn'.
Ich spiel ebenfalls mit kleinen Prozessoren: LPC810, LPC1110 - 4kiB ROM,
1kiB RAM. Ich spar lieber die paar Bytes fuer paar Stacks und den Code
KontextSwitcher indem ich keine Fliesskommas, printf und sonstigen
Standard-Rotz benutze. Aber meistens brauch ich den nicht mal, denn ich
design meine Interfaces praktisch immer als asynchrones IO.
Fuer Anregungen zum Sparen empfehle ich google mit 'Felix von Leitner',
der hat da paar interessante Vortraege gemacht.
A. K. schrieb:> natürlich auch, aber der entstehende Code ist wesentlich lesbarer als> die üblichen state machines.
ACK mit obigen Einschraenkungen.
Das eigentliche Problem ist, dass wir kein Werkzeug haben, das
sequentiell geschriebenen Code in eine Zustandsautomaten uebersetzt und
zurueck. Vielleicht sollte man sich da mal Gedanken machen, wie man
vielleicht mit magischen Kommentaren den Code so mit Information
anreichern kann, dass die Konvertierung automatisch erfolgen kann.
Zustandsautomaten fuer Textmuster werden ja auch generiert und nicht
handprogrammiert.
Marc P. schrieb:> Okay ich will es nicht mehr 'Schwachsinn' nennen, sondern 'Leichtsinn'.
Noch ein Hau-Ruck und ich bin mir sicher, Du findest eine noch bessere
Umschreibung als 'Leichtsinn'.
> Jede static Variable braucht ihren eigenen Platz fuer alle Zeit! Eine> Variable auf dem Stack hat gut Chancen denselben Speicherbereich zu> nutzen wie eine lokale Variable einer anderen Funktion des gleichen> Threads zu einer anderen Zeit. Und nicht jede lokale Variable ist nur 1> oder 2 Bytes gross.
Protothreads schenken dir Threads ohne lokale Variablen, *und ohne den
Stackframe-Overhead*. Du siehst nur den Nachteil.
> Und genau da faengt das Problem an: dann wird an lokalen Variablen> gespart, oder ... oder ....
Das Zauberwort heißt "Kompromiss".
Brauchst du viele lokale Variablen in deinen Threads, sind Protothreads
ungeeignet. Brauchst du dagegen möglichst viel Speicher für globale
Variablen (Buffer!), dann sind echte Threads ziemlich teuer.
Wenn du bei richtigen Threads und wirklich wenig Speicher nicht
aufpasst, handelst du dir auch gerne mal einen Stack-Overflow ein.
> Fuer Anregungen zum Sparen empfehle ich google mit 'Felix von Leitner',> der hat da paar interessante Vortraege gemacht.
Stimme ich zu, aber bei ihm geht es weniger um die ganz kleinen Systeme.
Mehmet Kendi schrieb:> Noch ein Hau-Ruck und ich bin mir sicher, Du findest eine noch bessere> Umschreibung als 'Leichtsinn'.
Du beziehst dich auf den schwedischen Adam, den dunklen Adam, ja?
Für dessen Proto-Threads sollte man diesen Präprozessor-Steptänzer
windelweich prügeln. Das ist der allergrößte Bockmist, den ich je in der
Szene gesehen habe.
Man sollte sich eines merken: Selbst durch allergrößte
Präprozessor-Akrobatik kann man aus einer kleinen Ressource keine große
Ressource machen. Da wäre es weitaus besser, direkt in biederem C ohne
Verrenkungen und ohne Mißbrauch des switch/case ein dem Problem wirklich
angepaßtes Stück Code zu schreiben.
W.S.
W.S. schrieb:> ...diesen Präprozessor-Steptänzer...> ...der allergrößte Bockmist...
:) Das ist Balsam auf meine Seele!
Kennst Du das Framework 'ACE' ?
http://www.cs.wustl.edu/~schmidt/ACE.html
Das hat damals meinen AMD K6-300 auf den ich damals so stolz war (lach
nicht!!) dank solcher Akrobatik soweit ausgelastet, dass ich (gefuehlt)
ewig auf Compiliern und Linken von 'hello world' (mit select()) warten
musste. ACE war Vorgabe des Chefs und in der folgenden Diskussion
darueber wurde ich als 'Spalter' (der Entwicklergruppe) bezeichnet.
Letzten Endes hab ich in dem Laden nicht angefangen und es wurde nichts
gespalten.
Interessant ist, dass Leute sehr gute Ideen haben, aber in der Umsetzung
haeufig so schlecht sind, dass unter'm Strich was negatives rauskommt.
Dunkels ist da ein Paradebeispiel. IP/TCP/UPD werden auf die
kleinstmoeglichen Grenzen geprueft und Minimalanforderungen ermitteln
-sehr gut! Aber die Ausfuehrung ..(wuerg).. na, ja,.. aehm nein! Ich hab
selbst uIP benutzt - kurze Zeit - und hab Dunkel's Sachen gelesen,
nachvollzogen. Aber die Umsetzung, da hat mich das blanke Entsetzen
gepackt und am Ende hab ich lieber selber einen UDP/IP Stack gebastelt.
Ich frag mit ob der weiss, dass es structs gibt, nicht nur char[]?
Heutzutage mach ich eine Datei in dem Stil nach spaetesten 100 Zeilen
anschauen wieder zu und loesch das Gesamtwerk vom Rechner. Das Leben ist
zu kurz, um es mit sowas zu vergeuden.
Gleiches gilt fuer ACE. Die Papers sind echt lesenswert, die Umsetzung
macht man besser selber. Sonst wird aus jeden feature ein Makro FEATURE,
das dann je nach Plattform per Praeprozessorakrobatik (bis zum Exzess)
was anderes gibt.
Kompromisse mach ich auch, bis ich bis zu den Knie in der Sch*isse steh,
aber nicht bis zum Hals!
Hallo,
eigentlich ist es ganz einfach auch selbst zu stricken: nichtblockierend
heisst, Start einer Aufgabe (z.B. Druck einer Seite) und deren Abschluss
werden getrennt bearbeitet. Die Startroutine schiebt das ganze an und
kehrt sofort zurück, der Druck läuft im Hintergrund und ruft die
Abschlussroutine auf, wenn er fertig ist. Die kann z.B. den Drucker für
andere Zwecke wieder freigeben, oder auch garnichts tun.
Georg
Ich würde, wenn ich das als loop Programm realisieren müste, folgender
Massen machen.
Das doX in 2 Funktionen zerlegen.
Eine sendCan, der alle Parameter und ggf die doA über gegeben wird. Die
Funktion speichert die Werte nur zwüschen. Ein return value sagt dir ob
die can Nachrichten angenommen wurde oder nicht. Die rufende Funktion
muss sich dann nicht blokierend um das wiederholen kümmern fals
notwendig. Ggf kann hier auch eine queue verwendet werden.
Und eine transmitCan. Die Funktion hängt in der hauptschleife,
entsprechend der oben gespeicherten Werte kümmert die sich um die
eigentliche Übertragung, warten und doA aufruf.
So ähnlich würde ich es auch mit threads machen. TransmitCan Wäre dann
der thread.
Gruss
Threads wurden erfunden, um gerade dieses handgefummelte,
fehlerträchtige Zerlegen in Ausführungsblöcke zu verhindern. Ändern sich
die Anforderungen ans Timing, muss man das alles neu bauen.
Sinnvoll ist, alles entweder von Beginn an in kleinste Einheiten zu
zerlegen und manuell zu schedulen (Zustandsautomat), oder alles am Stück
lassen und eine Runtime mit dem Zerlegen zu beauftragen (Threads).
Beides hat Vor- und Nachteile.
Multitasking / Threads sind auf dem PC oder einen großen System OK. Bei
einem kleinen µC ( 4 K Speicher) sind da aber eher Interrupts die Lösung
der Wahl. Das ist nicht so flexibel, wird aber gerade auf dem µC für
viele IO Funktionen direkt von der Hardware unterstützt. Es ist eine
etwas andere Art zu programmieren - halt ereignisorinetiert und nicht
mehr linear.
Das kann so weit gehen, das fast alles in ISRs erledigt wird, und das
Hauptprogramm nur noch den µC in den Schlafmodus schickt, wenn gerade
nichts zu tun ist.
S. R. schrieb:> Threads wurden erfunden, um gerade dieses handgefummelte,> fehlerträchtige Zerlegen in Ausführungsblöcke zu verhindern. Ändern sich> die Anforderungen ans Timing, muss man das alles neu bauen.
Moment: gerade beim Timing sind Threads keine grosse Hilfe, wenn's um
die Zeit zwischen 'Hardware-fertig' und 'passenden Thread aufrufen'
geht.
Wie macht ein Scheduler die Umsetzung von (Interrupt-)Flags ins
Aktivieren von schlafenden Threads? Pollen oder ISR! Nur dass man ihm
als goettliche Ueberinstanz dies zugesteht und dem Programmierer lieber
nicht ;-) Aber dann muss der Scheduler auf ueber alle moeglichen
Interrupt-Flags bescheid wissen und das kann doch auch schnell
unuebersichtlich werden, oder lieg ich da falsch?
Ulrich H. schrieb:> sind da aber eher Interrupts die Lösung
Sehe ich genauso. Vor allem in Kombination mir Priorisierung und
verschachtelten Interrupts. Ich finde das macht die Cortex-M-* so
interessant.
> Es ist eine etwas andere Art zu programmieren - halt ereignisorinetiert> und nicht mehr linear.
Und jetzt halt dich fest: genau dafuer hab ich meinen Kontext-Switcher
so geschrieben und nicht anders. Dass man naemlich aus einer ISR raus
(!!) eine zuvor angefangene, lineare Funktion fortsetzen kann.
Dafuer muss man aber einen vorgefertigten Scheduler entmachten
(eliminieren).
>> Threads wurden erfunden, um gerade dieses handgefummelte,>> fehlerträchtige Zerlegen in Ausführungsblöcke zu verhindern. Ändern sich>> die Anforderungen ans Timing, muss man das alles neu bauen.>> Moment: gerade beim Timing sind Threads keine grosse Hilfe, wenn's um> die Zeit zwischen 'Hardware-fertig' und 'passenden Thread aufrufen'> geht.
Wir haben verschiedene Probleme im Hinterkopf. Mir geht es nicht darum,
Interrupts durch pollende Threads zu ersetzen oder alle ISRs in Threads
zu stopfen.
Mehrere parallel ablaufende CPU-intensive Algorithmen sind ungeeignet
für eine ereignisorientierte Architektur. Ich denke da an Roboter, die
gleichzeitig Kamerabilder analysieren und Wegfindung betreiben. Man kann
die Algorithmen als nichtblockierende Zustandsautomaten entwerfen, oder
als blockierende Threads, die vom Scheduler gewechselt werden.
S. R. schrieb:> oder> als blockierende Threads, die vom Scheduler gewechselt werden.
Was preemptives Multitasking voraussetzt, kooperativ ist das zu
unsicher, siehe seliges 16bit-Windows.
Georg