|
|
MultitaskingMultitasking bedeutet ein quasi paralleles Ausführen von mehreren Prozessen auf einem Prozessor.
[Bearbeiten] EinleitungDa eine echte parallele Ausführung von mehreren Prozessen (Programmen, Funktionen) auf einem einzelnen CPU-Kern nicht möglich ist, wird ein "Trick" verwendet. Dabei werden die einzelnen Prozesse jeweils nur für kurze Zeit (1..50 ms) bearbeitet und danach auf einen anderen Prozess umgeschaltet. Man spricht auch von einer verschachtelten Bearbeitung (engl. interleaving). Das Herz jedes Multitasking-Systems ist der Scheduler. Dieses Programm beinhaltet einen Algorithmus, der überprüft, welcher Prozess als nächstes die CPU (also Rechenzeit) zugeteilt bekommt. Es gibt verschiedene Schedulingalgorithmen:
Natürlich sind Scheduler in freier Wildbahn nicht immer so einfach zu charakterisieren, da sie oftmals komplizierte Hybriden der genannten Techniken implementieren. Die Scheduler der "echten" Betriebsysteme (Windows, Linux, MacOS, *BSD) sind im Prinzip prioritäten-basierende Round Robin Scheduler. Generell hat ein Betriebsystem 2 Möglichkeiten, Multitasking zu realisieren, kooperativ oder präemtiv. [Bearbeiten] Kooperatives MultitaskingBeim kooperativen Multitasking gibt der Scheduler die Kontrolle komplett an den Prozess ab. D.h., das Betriebsystem ist darauf angewiesen, dass der Prozess die Kontrolle wieder abgibt. Geschieht das nicht, wird der Scheduler nicht wieder aufgerufen und damit auch kein anderer Prozess mehr ausgeführt - das System "hängt". Das OS ist also auf die Kooperation der Prozesse angewiesen. Bekannte Beispiele für Betriebssysteme mit kooperativem Multitasking sind Windows 3.x und MacOS vor Version 10. Dennoch ist kooperatives Multitasking keineswegs überholt oder schlecht. Gerade im Bereich der Mikrocontroller und Echtzeitanwendungen gibt es viele Argumente, die für ein kooperatives Multitasking sprechen: Kooperatives Multitasking ist deterministischer (zeitlich und logisch vorhersagbar). Es ist besser simulierbar, d.h. für ein gegebenes System ist leichter nachweisbar, dass es funktioniert. Da es sich um geschlossene Systeme handelt, tritt das Problem, dass "irgendein" Prozess das System anhält, nicht auf. Es laufen ja im Gegensatz zum PC nicht "irgendwelche" Prozesse, sondern nur die, deren Korrektheit (hoffentlich) verifiziert & validiert wurde. [Bearbeiten] Ein einfaches Beispiel für den AVRHier soll ein einfaches Beispiel den Weg in die Programmierung von parallel bearbeiteten Aufgaben zeigen. Wichtigster Grundsatz ist die Herangehensweise! Viele Programmieranfänger haben damit Schwierigkeiten, was u.a. an den schlecht vermittelten Grundlagen liegt. Oft sieht man Funktionen zum Warten in Form von
um beispielsweise eine LED blinken zu lassen. Will man dann noch andere Dinge erledigen, wundert sich der Programmierer, warum der Mikrocontroller so langsam reagiert, trotz 16 MHz Taktfrequenz. [Bearbeiten] Einfacher AnsatzStellen wir uns vor, wir wollen drei Dinge gleichzeitig tun.
Ein einfacher Ansatz für die drei Dinge sieht etwa so aus. Die Beispiele wurden mit WinAVR Version 20081006 in der Optimierungsstufe -Os kompiliert.
Wenn man das Programm nun laufen lässt, wird man feststellen dass
Dieser Ansatz ist also untauglich. Egal wie schnell unser AVR auch ist, er reagiert sehr langsam. [Bearbeiten] Verbesserter AnsatzWill man mehrere Dinge gleichzeitig bearbeiten, muss man die Aufgaben in kleinste Häppchen zerteilen. Diese kleinsten Häppchen werden dann verschachtelt abgearbeitet, also ein Häppchen von Aufgabe A, ein Häppchen von Aufgabe B, ein Häppchen von Aufgabe C. Das Auslesen der Taste geht immer sehr schnell, kein Ansatz zum optimieren. Das Blinken der LED dauer entweder 1s oder 100ms, eine Ewigkeit für einen Mikrocontroller! Hier muss man was ändern. Am schlimmsten ist die UART-Nutzung. Der AVR wartet solange, bis ein Zeichen empfangen wurde! Das kann ewig dauern! Unser Programm steht! Das darf nicht sein!
Dieses Programm reagiert ganz anders! Schnell wie der Wind und vollkommen unabhängig von anderen, parallel laufenden Prozessen. Warum ist das so ? Die einzelnen kleinen Häppchen sind verdaulicher als die grossen. Die maximale Durchlaufzeit der einzelnen Funktionen ist drastisch reduziert. Anstatt in der LED-Ausgabe einmal 1000 ms zu warten wird nun 1000x1ms gewartet. Zwischendurch werden aber 1000 mal die anderen Prozesse bearbeitet. Echte Demokratie sozusagen. Noch viel besser ist die Handhabung des UARTs. Anstatt eine Ewigkeit auf ein ankommendes Zeichen zu warten, wird nur dann etwas bearbeitet, wenn auch wirklich etwas zur Bearbeitung vorliegt. Klingt eigentlich logisch. Also nur dann, wenn schon ein Zeichen empfangen wurde wird es auch bearbeitet, ansonsten geht es zurück zur Hauptschleife. Das ist eigentlich der ganze "Trick" eines kooperativen Multitaskings. Auch wenn die Verwendung von _delay_ms(1) noch ein kleiner Schönheitsfehler ist, den die Profis lieber mit einem Timer erledigen, so wird das Prinzip klar.
Damit ähneln die Prozesse einem Interrupt, auch wenn sie als ganz normale Funktionen ausserhalb eines Interrupts ausgeführt werden. An diesem Beispiel erkennt man die Vor- und Nachteile des kooperativen Multitaskings Vorteile
Nachteile
[Bearbeiten] Verbesserter Ansatz mit TimerZum Abschluss noch einmal die verbesserte Version mit Timer. Diese hat mehrere Vorteile.
Diese Überprüfung kann an zwei Stellen durchgeführt werden.
[Bearbeiten] Message passing FrameworkIm vorangegangenen Abschnitt wird erklärt, wie man die einzelnen "Tasks" in kleine Häppchen Zerlegen kann und diese alle innerhalb der Main Loop aufruft. Dieses kooperative System hat aber noch einige Nachteile:
Wenn man diese beiden Nachteile auch noch lösen möchte, wird das ganze noch ein klein wenig komplizierter. Da man das Grundprinzip aber für viele Mikrocontroller Projekte immer wieder verwenden kann, lohnt es sich und man kann die Entwicklung für diese Art des Multitasking in einem Framework zusammenfassen. Ein Framework, das sind einige Dateien, die den Rahmen (Frame=Rahmen) für ein Programm bilden und den Teil enthalten, den man immer wieder braucht. [Bearbeiten] MessageDie Basis des Frameworks bildet die "Message". Immer wenn wir für einen unserer "Tasks" etwas zu tun haben, schicken wir eine "Message". Die "Messages" werden in eine Warteschlange einsortiert und der Reihe nach abgearbeitet. Dadurch kommt ein Task der viel zu tun hat (viele Messages bekommt) öfter dran, als ein "Task" der nicht so viel zu tun hat. Außerdem kann eine Message noch Daten enthalten (z.B. ein empfangenes Zeichen). So können die einzelnen Tasks sogar Daten austauschen. [Bearbeiten] Message ReceiverUnsere "Tasks" werden immer dann aufgerufen, wenn Arbeit für sie da ist. Das wissen wir, weil sie eine Message empfangen sollen. Deshalb heißen die "Tasks" ab jetzt "Message Receiver" [Bearbeiten] TimeoutIn diesem System wird jeder Message Receiver aufgerufen, wenn jemand Arbeit für ihn hat und er deshalb eine Message bekommt. Was aber, wenn keine Message kommt, oder ein Message Receiver selbst aktiv werden soll? Aus diesem Grund braucht es die Timeouts. Mit Hilfe eines Hardware Timers wird eine "Systemzeit" programmiert. Jeder Message Receiver kann die Zeit angeben, wann er wieder aufgerufen werden muss. Das Framework verwaltet alle Timer und sendet den Message Receivern eine "Timeout" message, wenn ihre Zeit gekommen ist. [Bearbeiten] BeispielHier der Sourcecode eines solchen Framework Datei:ACF.zip Das Framework implementiert die Message Warteschlange und die Timer Warteschlange. Die Prozessor spezifischen Dinge sind in der Datei "ACF_Hal.c" zusammengefasst und für Linux Desktop und atMega128 implementiert. In dieser Datei kann man das ganze auch auf andere Prozessoren anpassen. Ein "main" sieht dann z.B. so aus:
[Bearbeiten] TracingEs gibt noch einen weiteren Grund, sich ein "Framework" zu erarbeiten, oder ein fertiges Framework zu verwenden. Da der Ablauf der Software und der Aufruf aller Teile vom Framework bestimmt wird, kann das Framework auch einen sehr detaillierten Trace über das Verhalten des Codes anfertigen. Das Framework aus vorstehendem Beispiel enthält bereits entsprechenden Code. Solche Traces von laufendem Code können gerade dann sehr hilfreich sein, wenn viele Dinge gleichzeitig ablaufen (und das war schließlich der Sinn des ganzen). Nachstehendes Bild zeigt den Trace eines Reglers, der mit dem Framework realisiert wurde. Sequenzdiagramm [Bearbeiten] Präemptives MultitaskingBeim präemptiven Multitasking gibt das OS die Kontrolle zu keinem Zeitpunkt auf. Ein Prozess, der gerade die CPU nutzt, kann jederzeit wieder vom Betriebssystem unterbrochen werden. Daher muss bei der Entwicklung für ein präemptives System immer damit gerechnet werden, dass ein Prozess jederzeit unterbrochen werden kann. Das kann z. B. zu Problemen beim Zugriff auf limitierte Betriebsmittel führen. Beispiel:
Jetzt haben also beide Prozesse den gleichen Speicherblock reserviert. Entweder arbeiten jetzt beide Prozesse mit dem gleichen Speicher, und überschreiben daher gegenseitig die Daten, oder das Betriebsystem hat etwas gemerkt und zieht die Notbremse. In jedem Fall passieren schreckliche Dinge. Sowas nennt man eine Race-Condition. Die Lösung nennt sich Semaphore: Dieser Mechanismus wird vom Betriebsystem bereitgestellt und erlaubt es einem Prozess eine bestimmte Ressource zu sperren. Wenn also Prozess A aus obigem Beispiel Speicher haben möchte, setzt er vor Beginn der sogenannten "Kritischen Sektion" einen Semaphor für "Speicher reservieren". Dieser Semaphor wird erst wieder aufgehoben, sobald Prozess A den Speicher für sich reserviert hat. Wenn der Prozess B zwischendurch gestartet wird und ebenfalls versucht den Semaphor zu setzen, wird er solange warten müssen, bis Prozess A den Semaphor wieder freigibt. Speziell für derartige Locking Mechanismen bieten die meisten Prozessoren sogenannte TAS-Befehle (Test And Set), die in einem Prozessorbefehl eine Variable testen und je nach Ergebnis setzen können. Das ist nötig um das Setzen von Semaphoren unteilbar (atomar) zu machen. Könnte der Scheduler das Setzen eines Semaphors unterbrechen, wäre ja der ganze Aufwand umsonst. Präemptive Multitasking Systeme sind sehr flexibel und kommen mit einer Vielzahl an Tasks klar. Amok laufende Prozesse können das System bei korrekter Implementierung nicht blockieren. Damit aber das System crash-sicher ist, muss es Systemresourcen geben, die nur der Scheduler verteilen kann (z. B. kein anderer Prozess darf in den Speicherbereich des Schedulers schreiben; kein anderer Prozess darf den Timerinterrupt des Schedulers ändern). Diese Möglichkeiten sind in Mikrocontrollern normalerweise gar nicht vorhanden, wodurch dieser Vorteil des Präemptiven MT weniger ins Gewicht fällt. Beispiele für Systeme mit präemptivem Multitasking sind Linux, *BSD und Windows XP. Vorteile
Nachteile
[Bearbeiten] MultithreadingMultithreading ist eine meist softwarebasierende Möglichkeit moderner Betriebssysteme, innerhalb eines Prozesses mehrere Tasks (threads) parallel auszuführen. Der Vorteil dieser weiteren Unterteilung ist, dass sich die Threads eines Tasks den Speicherbereich teilen können und eine Aufteilung in logische nebeneinander laufende Teile möglich ist. Je nach Betriebssystem kann der Übergang von Multithreading zu Multiprocessing fliessend bis starr sein. Das Hyperthreading eines Intel Pentium 4 folgt dem Konzept des Multithreadings auf Hardwarebasis und teilt den CPU-Kern zeitlich in zwei logische Prozessoren ein. [Bearbeiten] Umsetzung auf ProzessorenUnabhängig davon, ob Multitasking oder -threading auf einem Prozessor konkret unterstützt wird, lässt es sich immer in Form von Software realisieren. Dies wird in modernen Systemen durch das OS geleistet, das standardisierte Funktionen und Strukturen zur Verfügung stellt. Besonders C++ bietet ein stark abstrahiertes Programmiermodell und Methoden-Set an, um effektiv untereinander kompatible Programmmodule erstellen zu können. Nutzt man diese nicht, wie z.B. bei der Programmierung in C, müssen Strukturen manuell erzeugt und gehandhabt werden, was aufwändiger ist, aber auch geringeren overhead bewirkt. Das Programm ist dann fast immer erheblich kleiner, in den meisten Fällen strukturell einfacher, bezüglich komplizierter Änderungen jedoch auch unflexibler und träger. Bei Mikrocontrollern findet man je nach Komplexität und Struktur der Appliation praktisch alle denkbaren Kombinationen: [Bearbeiten] System mit real-time OS und Entwicklung in C++
[Bearbeiten] System mit real-time OS und Entwicklung in C
[Bearbeiten] System ohne real-time OS und Entwicklung in C
[Bearbeiten] System ohne real-time OS und Entwicklung in ASM
[Bearbeiten] Weblinks
|