PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [ERLEDIGT] Verlassen einer Funktion um später zurück zu springen.



Iqon
16.09.2015, 19:55
Ist es möglich eine Funktion an einem vorher definierten Punkt zu verlassen um dann später die Ausführung nach diesem Punkt wieder fortzuführen?

Mein Problem ist folgendes: ich habe einen JobStack auf den ich Funktionen lege, damit diese nicht im aktuellen Kontext ausgeführt werden (z.B. um den Kontext aus einer ISR zu verschieben). Der JobStack wird dann im Main-Loop abgearbeitet. Um den Main-Loop nicht zu lange zu blockieren suche ich eine Möglichkeit, Punkte in der Funktion festlegen, an denen die Funktion verlassen wird.

Wird ein solcher Punkt erreicht, soll der JobStack die Kontrolle zurück an den Main-Loop geben. Nach dem der nächsten Iteration des Main-Loops soll der JobStack dann wieder an den vorher verlassenen Punkt springen und die Funktion fertig (oder bis zum nächsten "Break"-Point) ausführen.

Ist sowas (mit vertretbarem Aufwand) überhaupt möglich und wenn ja wie Sinnvoll?

MfG Iqon

i_make_it
17.09.2015, 06:24
Eine Hochsprachen Funktion (z.B. C++) hat genau einen Einsprungspunkt und mindestens einen Aussprungspunkt. Return kann man machen, wenn z.B. eine Funktion in einem Prüfzweig feststellt das sie vorzeitig verlassen werden kann. Nehmen wir C++ wo es Überladungen gibt, dann stellen wir fest, das ein Einsprung in eine Funktion an einer anderen Stelle als dem Funktionsaufruf nicht funktionieren kann, da der Compiler bei einer überladenen Funktion anhand der Argumente entscheidet welche der Funktionen nun aufzurufen ist. diese Bedingung ist so nicht mehr gegeben. unabhängig davon ob es Comilerfehler gibt, ist das Spagetticode den man spätestens 12 Monate nach dem man sich nicht mehr damit befasst hat, selbst nicht mehr versteht. Eventuell überlegen, ob man da wirklich eine Funktion nimmt, oder mehrere bzw. ob man die Funktion überläd und so beim Aufruf mit vorverarbeiteten Argumenten weiterlaufen lassen kann.

Von der Beschreibung her, soll ja die Funktion bei jedem Durchlauf der Hauptschleife genau einmal aufgerufen werden. Wie stellst Du dir da die Weiterverarbeitung vor? Ändern sich da nicht bei jedem Aufruf die Werte der Argumente?
Bzw. Was hast Du in der Hauptschleife untergebracht was Zeitkritisch ist. wäre es da nicht sinnvoll das mit Interrupts zu händlen. Wenn kein Event Interrupt, dann einen Timerinterrupt der zyklich dafür sorgt das man nichts wichtiges verpasst. Das Zielsystem auf dem der Code läuft, könnte auch eine Lösung liefern. Bei einem Multicoresystem, könnte man die betreffende Funktion exklusiv einen Core zuweisen, dann läuft sie parallel zum Rest des Programms.

witkatz
17.09.2015, 07:54
Ich weiss nicht, ob ich dein Problem richtig verstehe, aber wäre das nicht ein Fall für eine State Machine (http://www.mikrocontroller.net/articles/Statemachine)? Im Unterprogramm können die Arbeitsabschnitte in einer switch-case Anweisung gegliedert werden. Wenn der jeweilige Arbeitsabschnitt abgearbeitet oder ein Ereignis eingetreten, setzt du die Switch-Variable auf den nächsten Wert und verlässt die Funktion.
Gruß witkatz

Iqon
17.09.2015, 08:19
Kein Multicore System. soll alles auf einem AVR laufen.
Hier mal ein Pseudo-Code Beispiel wie ich mir das Vorstelle:



void longrunning(void) {
...
}


void main(void) {

jobstack_put(&longrunnnig);

while(true) {

... //Do something else
jobstack_execute(); // this is the point where the jobstack functions are executed.
}
}

Also führt jobstack_execute den aktuellen Job aus. Ich suche jetzt nach einer einfachen Möglichkeit, "Longrunning Jobs" auszuführen ohne die Main zulange zu blockieren. Normalerweise würde so eine Funktion in einem eigenen Thread laufen und der Scheduler würde mein Problem lösen.

Aber auf ein OS mit Threads will ich hier verzichten. Für meine Zwecke würde es reichen, wenn innerhalb des "Longrunning Jobs" Punkte angegeben werden, an denen die Ausführung pausiert werden darf.


void longrunning(void) {

... // do something
CUSTOM_BREAK;
... // do something more
}

}

Sobald CUSTOM_BREAK erreicht wird, soll jobstack_execute verlassen werden um noch einmal die Main-Schleife durch zu laufen. Wird jobstack_execute erneut aufgerufen, soll die Funktion longrunning direkt nach CUSTOM_BREAK wieder fortgesetzt werden.

Im Moment sind das nur Gedankenspielereien ob das Überhaupt möglich ist. Wie aufwendig die implementierung wäre und ob ein solches Konzept überhaupt einen mehrwert bringen würde.

- - - Aktualisiert - - -

@i_make_it:
Es geht hier auch nicht darum, andere Parameter zu übergeben, oder irgendetwas im Funktionsstack zu ändern. Ich will eine Funktionen während der ausführung unterbrechen und später "fertig ausführen".

Im Moment ist auch nichts im Main-Loop. Ich will eine "flexible" Middleware für ein Projekt und bin am Überlegen ob so etwas Überhaupt möglich ist und wenn ja ob es den Aufwand auch Wert wäre.

@witkatz:
Glaube nicht das ne State Machine das Problem löst. Man könnte die einzelnen Abschnitte (vor und nach dem CUSTOM_BREAK zwar in einzelne States packen und die nacheinander abarbeiten. Aber das geht etwas an meinem Ziel vorbei ;). Ich will Funktionen mit minimalen Aufwand (=ein Macro) aufteilen können.

oberallgeier
17.09.2015, 08:28
Eine Hochsprachen Funktion (z.B. C++) hat genau einen Einsprungspunkt und mindestens einen Aussprungspunkt ... C++ wo es Überladungen gibt, dann stellen wir fest, das ein Einsprung in eine Funktion an einer anderen Stelle als dem Funktionsaufruf nicht funktionieren kann ...Meine Kenntnisse in C sind sehr begrenzt aber ich hab mich (vielleicht deshalb?) mit der Frage auch schon beschäftigt. Wegen mangelnden Zutrauens zu so einem Konstrukt hatte ich das nie getestet - auch nie geschrieben.

Frage: KÖNNTE es nicht so gehen, dass man ein Flag mitschleppt? Beim Aufruf der Funktion informiert das Flag über die verschiedenen Zustände: Erster Aufruf, Aufruf nach Return am Ende des ersten Abschnittes, Aufruf nach Return am Ende des zweiten Abschnittes usf, Aufruf nach Abarbeiten der vollständigen Funktion. Ein switch nutzt nun diese Information zum Ansprung der verschiedenen Pseudoeingänge. Am Beginn jedes Cases wird dann das Flag erstmal passend gesetzt (?) und vor dem Break bzw. Return des aktuellen Cases (klingt aber jetzt irgendwie doppelt gemoppelt) wird das Flag entsprechend angepasst.

Ich habs nie realisiert weil es mir irgendwie sehr seltsam vorkommt; wie sollte ich das testen??

Nachtrag: Sorry, witzkatz ist schneller wach gewesen.

witkatz
17.09.2015, 08:35
Ich will Funktionen mit minimalen Aufwand (=ein Macro) aufteilen können.
So meinte ich das
void longrunning(void) {
static step = 0;
switch(step){
case 0:
/*...*/ // do something
step++;
break; //CUSTOM_BREAK;
case 1:
/*...*/ // do something more
step = 0;
}
}
Ist doch aufgeteilt, oder nicht?

Iqon
17.09.2015, 08:39
Das mit dem "kaum pflegbar" würde ich nicht behaupten. Da die Funktionalität ja gut entkoppelt ist und auch sehr überschaubar. Mir schwebt da eine ähnliche Lösung (nur etwas leichter als deine) vor:

1. Beim erreichen des Punktes wird die rücksprung Adresse in einem globalen Pointer gespeichert
2. Zurück in die jobstack_execute und von da wieder in die Main
3. Wird jobstack_execute erneut ausgeführt an den in Schritt 1 gesetzten Pointer springen

Mein Problem hier ist, dass ich nicht weiß inwieweit ich damit mein Programm zerstöre. Muss ich vorher alle Register sichern, könnte ich Probleme mit den Funktions-Stack bekommen, ... .
Welche Auswirkungen hat es, wenn ich eine Funktion einfach verlasse.

- - - Aktualisiert - - -


Ist doch aufgeteilt, oder nicht?

Ja ist aufgeteilt ist, bedeutet aber viel Aufwand in der Funktion und die Funktion wird dadurch sehr schwer lesbar.
Der größte Nachteil dieser Lösung: Da die Funktion wirklich verlassen wird, verliert sie ihren Zustand. Will man zwischen den States zustände teilen muss das über globalen oder anderweitig geteilten Speicher geschehenm, was die Sache um einiges komplizierter macht.

witkatz
17.09.2015, 08:51
bedeutet aber viel Aufwand in der Funktion
Nö, sehe ich nicht so.

und die Funktion wird dadurch sehr schwer lesbar.
Im Gegenteil, es ist lesbarer als irgendein magisches Makro und gut debugbar mit Breakpoints in einzelnen Schritten. Aber wie meine Mutter immer zu sagen pflegte "de gustibus non est disputandum" ;)

Da die Funktion wirklich verlassen wird, verliert sie ihren Zustand. Will man zwischen den States zustände teilen muss das über globalen oder anderweitig geteilten Speicher geschehenm, was die Sache um einiges komplizierter macht.
static ?

oberallgeier
17.09.2015, 08:54
Das mit dem "kaum pflegbar" würde ich nicht behaupten. Da die Funktionalität ja gut entkoppelt ist und auch sehr überschaubar ...Wäre also doch ne kleine Übung wert . . . wobei ich wohl die Version mit dem Flag brauche. Denn: Das Ergebnis der Funktion wird doch wohl (hoffentlich :.-.) ) von irgend einem Abschnitt benötigt. Und dieses Ergebnis ist nur dann gültig bzw. aktuell und brauchbar, wenn das Flag "Funktion mindestens einmal vollständig durchlaufen" gesetzt ist.

Gute Mutter: "de gustibus non est disputandum" - meine verstand kein Latein, da musste ich selbst durch.

Iqon
17.09.2015, 09:12
Ja das Ergebnis wird benötitg. Der JobStack den ich nutze ruft Funktionen mit folgender Signatur auf:

void (*jobstack_job)(void *dataPtr)

Die Ergebnisse werden also in eine beliebige Struktur geschrieben, die beim anlegen des Jobs übergeben wird.


struct mystruct shared_data;
jobstack_put(&func_ptr, (void *) &shared_data);


static ?
Wäre möglich, in meinen Augen aber unsauber da lokale Variablen dadurch Datei-Global sind.

@witkatz: Deine Lösung ist wahrscheinlich die einfachste. Ich hab mir nur überlegt, ob man das ganze Switch-Case gedöns irgendwie Sinnvoll verstecken könnte.

witkatz
17.09.2015, 09:43
ob man das ganze Switch-Case gedöns irgendwie Sinnvoll verstecken könnte.
Verstecken kann man in C fast alles. Ob es sinnvoll ist, ist eine andere Frage. Wäre zwar nicht meine Art, aber bitte schön, ein Q&D Vorschlag zum Verstecken von "Switch-Case-Gedöns":


#include "StateMachine.h"
void longrunning(void) {
STATEMACHINE_INIT
;/*...*/ // do something
step++;

STATEMACHINE_STEP(1)
;/*...*/ // do something more
step++;

STATEMACHINE_STEP(2)
; // do even more
if(error)
step = -1;
else
step = 0;

STATEMACHINE_STEP(-1)
; // do some error handling
step = 0;

STATEMACHINE_END
}

Nachtrag:
Ich hab die quick'n'dirty Macros etwas überarbeitet und mittlerweile gefällt mir die Lösung sogar :cool:

Iqon
17.09.2015, 09:54
Verstecken kann man in C fast alles.
Das stimmt natürlich, ob das immer sinnvoll ist, ist die andere Frage ;) Danke für eure Hilfe.

Ich denke die Switch-Case (ohne Macros) Variante ist die einfachste und damit beste. Also getreu dem KISS Motto, bleibt es bei dieser Variante.

i_make_it
17.09.2015, 12:00
Wenn ich das richtig verstehe, willst Du eigentlich ein Multitasking realisieren.
Task 1 ist dein Longrunning Prozess Task 2 alles andere.

Man müsste mal prüfen ob sich das mit einer Timer IRQ Routine realisieren lässt.
Das wäre die einzige Möglichkeit sicherzustellen, das der Longrunning Task sicher nach einer gewissen Zeit unterbrochen wird und in den andern Task zurückgekehrt wird.

Da ja normalerweise die IRQ Routine komplett abgearbeitet wird, bevor in das unterbrochene Programm zurückgekehrt wird, sollte entweder die Hauptschleife die IRQ Routine sein und nach einem Durchlauf in den Longrunning Prozess zurückkehren oder du befasst dich mal damit was Linus Torvalds als ersten Teil von Linux realisiert hat (Ein präemptives Multitasking das zwichen zwei Tasks hin und her springt und dabei die Rettung aller Register, Pointer und des Stacks übernimmt). Was du versuchst ist ein kooperatives Multitaksing, damit ist Microsoft schon bei Windows 1 bis 3 gescheitert. Bleibt ein Task hängen ohne das ein Reset verursacht wird, fällt das ganze wie ein Kartenhaus in sich zusammen.

Wie so was geht steht z.B. hier:
http://www.gbv.de/dms/ilmenau/toc/592544346.PDF
oder hier:
Multitasking mit AVR RISC-Controllern
Prof. Dr. Ernst Forgber
Franzis Verlag
Leseprobe:
http://www.ciando.com/img/books/extract/3645270558_lp.pdf

Multitasking für AVR:
http://www.controllersandpcs.de/pdfs/vmavr.pdf

https://xivilization.net/~marek/binaries/multitasking.pdf

http://www.wseas.us/e-library/conferences/2015/Dubai/CEA/CEA-24.pdf
(http://www.wseas.us/e-library/conferences/2015/Dubai/CEA/CEA-24.pdf)
http://www.ripublication.com/irph/ijict_spl/ijictv4n17spl_16.pdf

http://www.ijert.org/view-pdf/7712/real-time-operating-system-based-on-avr-microcontroller

http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.86.5418&rep=rep1&type=pdf

damfino
18.09.2015, 10:16
Der TE wollte kein OS...

Mit so einer State Machine kann man so einiges erreichen, denke auch das es die ideale Lösung ist. Hatte auch mal so eine Aufgabe, eine leere State Machine, ca 5000 ? wie das gehen soll, dann die Erleuchtung und Erkenntnis das man damit einem Mikrokontroller super auslasten kann ohne das eine einzelne lange Berechnung alles blockiert.
Sozusagen Multitasking light.

LG!

i_make_it
18.09.2015, 12:03
Multitasking und Betriebssysteme (OS) sind auch zwei verschiedene Sachen. Ein Multitasking bedingt noch kein Betriebssystem und Betriebsysteme müssen nicht zwingend Multitasking tauglich sein.
Eine State Machine (endlicher Automat) ist eine Form kooperatives Multitasking zu realisieren und kein "Multitasking light".
Der Nachteil ist halt wenn ein Task nicht kooperiert (Fehlerfall) dann gibt es einen Systemabsturz.
Präemptives Multitasking hat halt n+1 Tasks (der eine Task ist der Multitasking Task)
Der Multitasking Task steuert stur seine Zeitscheibe, unabhängig davon ob ein Task abgestürzt ist oder nicht.
Man könnte sogar über Status Informationen eine Überwachung des Laufverhalten der anderen Tasks realisieren und so einen abgestürzten Task nach maximal 3 Durchläufen der Zeitscheibe terminieren und ggf. neu starten.
Kooperatives Multitasking funktioniert solange alles fehlerfrei läuft.
Präemptives Multitasking kapselt die einzelnen Tasks und verhindert so ein Versagen des gesamten Systems wenn ein Einzeltask versagt.
Eine Selbstheilung des Gesamtsystems erfordert neben dem präemptiven Multitasking zusätzlich eine FST die die Zustände jeden Tasks überwacht und beim Verharren in einem Zustand entsprechende Masnahmen veranlasst.

Es kommt halt darauf an wie wichtig einem das korrekte Weiterlaufen bstimmter Tasks ist, bzw. welche Folgen das nicht korrekte arbeiten derselben hat, um zu entscheiden welche Form des Multitaskings man implementieren will.

Bei Servoreglern, geht man z.B. so weit das Motorstrom Regelung und Motorstrom Überwachung sogar in echtzeit parallel auf verschiedener Hardware laufen, da die Folge einer zu langsamen Motorstrom Überwachung, bei zu langem Überstrom, die Zerstörung des Reglers und des Motors zur Folge haben kann. Und in Folge die Beschädigung oder Zerstörung der Hardware die an dem Motor hängt (inklusive Personenschäden).

Mxt
19.09.2015, 08:12
Eine State Machine (endlicher Automat) ist eine Form kooperatives Multitasking zu realisieren und kein "Multitasking light".
Der Nachteil ist halt wenn ein Task nicht kooperiert (Fehlerfall) dann gibt es einen Systemabsturz.

In einigen Sprachen werden solche State Machines auch automatisch vom Compiler erzeugt, z.B. bei Verwendung von async und await in C#. Das erspart einiges an Schreibarbeit und umgeht einige Fehlerfälle. Für einen zukünftigen C++ Standard ist das zumindest vorgeschlagen, einige Compiler (z.B. Visual C++ 2015) bieten schon experimentelle Unterstützung (mit __yield, __async und __await).

Holomino
19.09.2015, 10:37
Hier (http://www.mikrocontroller.net/articles/Multitasking) steht einiges über kooperatives und präemptives Multitasking.
Allerdings steht auch drin, dass beim präemptivem Multitasking neben den Registern auch der Stack für jede Task umgebogen werden muss (das ist eigentlich auch logisch, wenn man weiß, dass bei einem Funktionsaufruf in einer Task Übergabeparameter und Rückgabewerte über den Stack transferiert werden). Das wird bei Controllern mit wenig RAM relativ schnell eng, insbesondere, wenn weitere ISRs auch noch ihre Register auf den Stack der aktuellen Task poppen wollen).

Alternativ zur Switch-Anweisung: Mehrere kleine Funktionen schreiben, statt der Step-Variable und dem Switch den Funktionszeiger für die "Task" in der Jobliste jeweils aus einer Funktion heraus auf die nächste Funktion schieben.

RedBaron
20.09.2015, 16:43
Moin,

manche C-Versionen (z.B. c#) bieten die Funktion yield, die so etwas kann. Für c habe ich dies gefunden: http://stackoverflow.com/questions/17478264/implementing-yield-in-c

Wenn ich das Problem richtig verstehe liegt folgende Situation vor:

1) Es gibt eine Liste von Aufgaben, die in einer bestimmten Reihenfolge abgearbeitet werden sollen (Jobstack).
2) Hin und wieder sollen bestimmte Aufgaben erledigt werden, dies aber auch während ein Job aus 1) ausgeführt wird.

Lösen kann man das Problem, indem man 1) und 2) nicht in einer gemeinsamen Funktion implementiert (z.B. in main()):

Preemptives Verhalten: Man packt 2) in eine Timer-ISR. Dann geschieht die Ausführung regelmäßig. Führt man die ISR als non-blocking Version aus, werden andere Interrupts nicht behindert. Dann wird 2) regelmäßig ausgeführt, ohne dass sich die Jobs darum kümmern müssten. main() würde sich dann nur noch darum kümmern, dass nach Beendigung eines Jobs der nächste gestartet würde. Ist die Aufgabe länger als die Timer-Periode, muss man den Timer zu Beginn der ISR stoppen und am Schluss wieder starten.

Kooperatives Verhalten: Man packt 2) in eine Routine und ruft diese in den gewünschten Stellen in den Jobs aus. Auch hier übernimmt main() die Verwaltung der Jobs. Diese sähe etwa so aus (Pseudo-Code):

main()
{ while(true)
StarteNaechstenJob();
}

TueWasManNichtLassenKann()
{ // Der Name sagt alles ..
}

Job1()
{ ... // erster Teil der Arbeit von Job1
TueWasManNichtLassenKann();
... nächster Teil der Arbeit von Job1
TueWasManNichtLassenKann();
... nächster Teil der Arbeit von Job1
}


Gibt es mehrere Dinge, die man in TueWasManNichtLassenKann() erledigen muss, dies aber nicht auf einmal machen will, wäre dafür die Lösung in einer kleinen State-Machine. Alternative Implementierung als Array von Funktionszeigern: Der Array-Index wird bei jedem Aufruf erhöht und die entsprechende Funktion aufgerufen. Komplexeres Verhalten lässt sich durch Variation der Index-Bestimmung ermöglichen.

Viele Grüße
RedBaron

HaWe
21.09.2015, 15:29
Herausspringen zur aufrufenden Funktion ist ja einfach mit return, entweder mit oder ohne weitere Parameter.

Zum Zurückspringen:
wie wäre es, wenn man der Funktion z.B. zusätzlich zur eigentlichen 1. Variable (usw.) einen weiteren Parameter übergibt, der den Sprungpunkt definiert?


int function_foo(int var1, int jaddr){
// (Deklarationsteil)

if(jaddr==0) goto LABEL0;
else
if(jaddr==1) goto LABEL1;
else
if(jaddr==2) goto LABEL2;
// usw.

//...
LABEL0:
//...(Anweisungen)

LABEL1:
//...(Anweisungen)

LABEL2:
//...(Anweisungen)

}

Iqon
29.09.2015, 17:08
Hat sich seit meinem letzten Besuch ja einiges getan ;D. Ja im Prinzip hab ich nach einem Multitasking-Light gesucht. Und zumindest für meinen UseCase tuts das Kooperative Multitasking via State-Machine.
Sollte eine Task nicht kooperieren, also hängen bleiben, ist der Kontroller in einem Fehlerzustand, aus dem er sich nicht mehr retten kann. Ein Fall der nicht auftreten darf/soll.

Abgesehen von dem Aufwand der für das Multitasking getrieben werden muss, kommt dann noch die globale Fehlerbehandlung dazu: wie wird reagiert, wenn ein Task nicht beendet werden kann.

Das sind für ein kleines Projekt zu viele unbekannte Zustände und führt zu unvorhersehbaren Problemen.

schorsch_76
29.09.2015, 18:49
Ist es möglich eine Funktion an einem vorher definierten Punkt zu verlassen um dann später die Ausführung nach diesem Punkt wieder fortzuführen?
Ja das gibt es. Nennt sich Funktionsaufruf. :rolleyes:

Peter(TOO)
30.09.2015, 12:49
Ja im Prinzip hab ich nach einem Multitasking-Light gesucht. Und zumindest für meinen UseCase tuts das Kooperative Multitasking via State-Machine.
Sollte eine Task nicht kooperieren, also hängen bleiben, ist der Kontroller in einem Fehlerzustand, aus dem er sich nicht mehr retten kann. Ein Fall der nicht auftreten darf/soll.

Das sind für ein kleines Projekt zu viele unbekannte Zustände und führt zu unvorhersehbaren Problemen.

Dazu gibt es dann den Watch-Dog, den heute eigentlich die meisten Controller haben.

MfG Peter(TOO)