Multithreading Reduzierter Rechenaufwand durch Preemption-Threshold Scheduling (PTS)

Autor / Redakteur: John A. Carbone* / Franz Graser

Der Verarbeitungsaufwand eines voll präemptiven Schedulers in einem Echzeitbetriebssystem kann zu Lasten der Systemeffizienz gehen. Das PTS-Verfahren ermöglicht die Voraussetzungen für mehr Leistung.

Anbieter zum Thema

Schwellenwerte: Normalerweise wird ein Thread mit der Priorität 20 durch jeden Anwendungsstrang mit einer höheren Priorität unterbrochen. PTS erlaubt es aber, Prioritätsschwellen festzulegen. In diesem Fall kann der Thread mit der Priorität 20 erst ab dem Wert 14 und höher unterbrochen werden.
Schwellenwerte: Normalerweise wird ein Thread mit der Priorität 20 durch jeden Anwendungsstrang mit einer höheren Priorität unterbrochen. PTS erlaubt es aber, Prioritätsschwellen festzulegen. In diesem Fall kann der Thread mit der Priorität 20 erst ab dem Wert 14 und höher unterbrochen werden.
(Grafik: Express Logic)

Echtzeitfähige Embedded-Systeme nutzen in der Regel mehrere Applikations-Tasks oder Threads, die ihr Arbeitsaufkommen jeweils im Rahmen der Echtzeitvorgaben bewältigen müssen. Um zu garantieren, dass kritische Threads umgehend berücksichtigt werden und ihre zeitlichen Restriktionen einhalten können, verwendet man in Echtzeit-Systemen meist das so genannte „präemptive Scheduling“. Nachteilig an diesem Verfahren ist, dass es unter bestimmten Umständen eine erhebliche Anzahl an Kontextwechseln erzeugt.

Abhilfe kann hier eine als „Preemption-Threshold Scheduling“ (PTS) bezeichnete Technik schaffen, die einerseits den Verarbeitungsaufwand durch die vorzeitige Unterbrechung von Tasks reduziert, andererseits aber dafür sorgt, dass die Applikationen die gestellten Echtzeit-Anforderungen erfüllen.

Prioritäten der Threads einer Applikation

Den Threads einer Applikation werden Prioritäten zugeordnet. Diese geben ihre relative Wichtigkeit an und entscheiden über die Reihenfolge, in der sie die CPU nutzen dürfen, wenn alle Threads zur Ausführung bereit sind. Die Prioritäten werden in der Regel mit ganzen Zahlen von 0 bis n angegeben, wobei 0 je nach Anwendung für die höchste oder die niedrigste Priorität stehen kann. Im Betriebssystem ThreadX etwa ist 0 die höchste Priorität, die mit zunehmendem Zahlenwert geringer wird. Jedem Thread wird eine Priorität zugewiesen, die jedoch während des Betriebs dynamisch geändert werden kann. Mehrere Threads können die gleiche Priorität haben, oder es kann jedem Thread eine eigene Priorität zugewiesen werden.

Wenn ein Thread verarbeitet wird und während dieser Zeit ein anderer Thread mit höherer Priorität in den READY-Status wechselt, d. h. verarbeitungsbereit wird, unterbricht das RTOS den laufenden Thread (Präemption) und startet stattdessen die Verarbeitung des Threads mit höherer Priorität. Dieser Vorgang wird als Kontextwechsel bezeichnet. Bei einem Kontextwechsel legt das RTOS den Kontext des laufenden Threads auf den Stack und holt aus dem Stack den Kontext des neuen Threads, um ihn in die Register und den Programmzähler der CPU zu laden. Der Kontext des bisher laufenden Threads wird also durch den des neuen Threads ersetzt.

Ein solcher Kontextwechsel ist ein recht komplexer Vorgang, der je nach RTOS und Prozessor zwischen 50 und 500 Zyklen beanspruchen kann. Dieser große Aufwand ist auch der Grund für die große Sorgfalt, die bei Echtzeit-Betriebssystemen auf die Optimierung der Kontextwechsel sowie darauf verwendet wird, die Häufigkeit dieses Vorgangs zu minimieren. Diesem Ziel dient auch das Preemption Threshold Scheduling (PTS).

Anwendungen, die aus mehreren Funktionen (d. h. mehreren Threads oder Tasks) zusammengesetzt sind, benötigen einen Mechanismus, der für die Ausführung der jeweils erforderlichen Funktion sorgt. Eine Möglichkeit ist es, die Threads der Reihe nach zyklisch zu starten. Verfeinern lässt sich dieses ‚Big-Loop‘-Verfahren durch eine Statusprüfung, sodass nur die Funktionen gestartet werden, die tatsächlich etwas zu tun haben, während die anderen übersprungen werden. Derartige Schleifen sind bereits eine rudimentäre Form eines Schedulers, obwohl sie ineffizient sind und es ihnen an Reaktionsschnelligkeit mangelt, speziell wenn die Zahl der Threads und Funktionen zunimmt. Ein RTOS-Scheduler hat dagegen stets den Überblick oder kann schnell entscheiden, welche Aktivität zum jeweiligen Zeitpunkt verarbeitet werden muss.

Echtzeit-Scheduler arbeiten in aller Regel präemptiv. Sie sorgen also stets dafür, dass der Thread mit der höchsten Priorität ausgeführt wird, während die übrigen Threads warten müssen. Auch RTOS-Scheduler können das zyklische Round-Robin-Scheduling nutzen, das große Ähnlichkeit mit dem erwähnten Big-Loop-Verfahren hat. Ebenso kommt eine etwas ausgefeiltere Variante des Round-Robin-Schedulings in Frage, bei dem den einzelnen Threads jeweils ein bestimmter Prozentsatz der CPU-Zeit zugewiesen wird, anstatt die einzelnen Threads so lange laufen zu lassen, bis sie beendet sind oder ihre Arbeit aus eigenem Antrieb unterbrechen.

Der RTOS-Scheduler führt Kontextwechsel nach Bedarf aus und gibt den Threads die Möglichkeit, in den Sleep-Status zu wechseln, die CPU-Nutzung abzutreten oder ihre Ausführung zu beenden und den Pool der auf die CPU wartenden Threads zu verlassen. Der maximalen Reaktionsgeschwindigkeit steht allerdings ein großer Verarbeitungsaufwand gegenüber, da es niemals ohne Kontextwechsel geht.

Dem „Verhungern“ von Threads entgegenwirken

Das Präemptions-Konzept ist mit einer Reihe von Problemen behaftet, die von den Entwicklern entweder umgangen oder bei der Programmierung einer Applikation berücksichtigt werden müssen.

Das erste Problem wird als Thread Starvation, also das Verhungern eines Threads, bezeichnet. In diesem Fall gelangt ein Thread niemals zur Ausführung, weil die Verarbeitung eines Threads mit höherer Priorität nicht endet. Entwickler sollten deshalb die Situation vermeiden, dass ein Thread hoher Priorität in eine Endlosschleife gerät oder übermäßig viel CPU-Zeit beansprucht, wodurch anderen Threads die Nutzung des Prozessors verwehrt wird.

Das zweite Problem ist der hohe kumulative Verarbeitungsaufwand, wenn es zu vielen Kontextwechseln kommt. Weiter unten wird dieser Fall an einem Beispiel beschrieben und es werden Tools vorgestellt, mit denen sich der Verarbeitungsaufwand sichtbar machen und messen lässt.

Das dritte Problem ist die Prioritätsinversion. Hier wartet ein Thread mit hoher Priorität auf eine gemeinsam genutzt Ressource. Diese aber wird von einem Thread niedriger Priorität beansprucht, der die Ressource aber nicht freigeben kann, weil er von einem Thread mit dazwischen liegender Priorität unterbrochen wird.

Weniger Kontextwechsel – weniger Verwaltungsaufwand

An dieser Stelle kommt das Konzept des Preemption-Threshold Scheduling (PTS) zum Tragen. Hierbei wird eine bestimmte Prioritätsgrenze festgelegt, die ein Thread überschreiten muss, um einen Thread zu unterbrechen. Da das PTS-Verfahren einige Präemptionen verhindert, werden einige Kontextwechsel unterbunden, sodass sich der Verarbeitungsaufwand entsprechend verringert.

Normalerweise kann ein Thread von jedem anderen Verarbeitungsstrang unterbrochen werden, dessen Priorität höher ist. Beim PTS-Verfahren ist die Präemption eines Threads dagegen nur möglich, wenn die Priorität des unterbrechenden Threads höher ist als die Präemptions-Schwelle (Preemption Threshold) des gerade laufenden Threads. In einem vollständig präemptiven System ist der Preemption-Threshold-Wert identisch mit der Priorität des Threads. Setzt man den Preemption-Threshold-Wert dagegen höher an als die eigentliche Priorität des Threads, werden Präemptionen durch Threads, deren Priorität zwischen beiden Werten liegt, unterbunden.

(ID:37563520)