ARM-basierendes Systeme Echtzeitbetriebssysteme: Weniger Unterbrechungen, höhere Effizienz

Damit ein ARM-basierendes System die Echtzeitanforderungen erfüllt, kommen meist Echtzeitbetriebssysteme mit präemptivem Scheduler zum Einsatz. Allerdings können sich die vielen Kontextwechsel und Interrupt-Verarbeitungsroutinen negativ auf die Speichergröße für Stacks und auf die Leistungsaufnahme des Mikrocontrollers auswirken. Mit einer Technik namens »Preemption-Threshold Scheduling« lässt sich dies auf ein Mindestmaß begrenzen, ohne die Echtzeitfähigkeit des Systems zu gefährden.

ARM-basierte Echtzeit-Embedded-Applikationen nutzen in der Regel ein Echtzeitbetriebssystem, damit sich das System als eine Zusammenstellung einzelner Module entwickeln lässt und dennoch eine korrekte Reaktion auf zeitkritische Ereignisse gewährleistet ist. Ein System besteht stets aus mehreren Tasks oder Threads, die entweder periodisch oder als Reaktion auf bestimmte Ereignisse ausgeführt werden und deren Verarbeitung bis zu einem bestimmten Zeitpunkt (Deadline) beendet sein muss.

Mithilfe von Echtzeit-Analysetechniken lässt sich auf mathematischem Weg festzustellen, ob ein Satz Threads Scheduling-fähig ist, also ob alle Threads des Systems in allen denkbaren Scheduling-Situationen ihre Deadlines einhalten. Dies ist eine unabdingbare Voraussetzung für viele Embedded Systeme. Die meisten Echtzeitbetriebssysteme verwenden vollständig unterbrechende, also präemptive Scheduler.

Ein Thread kann demnach praktisch jederzeit einen Thread von niedrigerer Priorität unterbrechen. Diese Preemption-Technik sorgt dafür, dass kritische Verarbeitungsaufgaben schneller erledigt werden als weniger kritische. Die Reaktionsschnelligkeit und Echtzeit-Performance eines Mikroprozessorsystems wird durch den Verarbeitungsaufwand bestimmt, der durch Kontextwechsel und Interrupt-Verarbeitungsroutinen (Interrupt Service Routines, ISRs) entsteht.

Das System muss zunächst den Status des gerade laufenden Threads abspeichern, bevor es mithilfe des Schedulers festlegt, welcher Thread als Nächstes verarbeitet werden soll. Abschließend wird der Status des nächsten Threads geladen. Diese Abläufe nehmen selbst mit schnellen ARM-Prozessoren recht viel Zeit in Anspruch, was der Reaktionsgeschwindigkeit gewisse Grenzen setzt.

Die mit der Zeit ständig wachsenden Anforderungen an die Reaktionsschnelligkeit haben die Entwickler gezwungen, immer schnellere und teurere Prozessoren mit immer höherem Stromverbrauch einzusetzen, um die Kontextwechsel zu beschleunigen und die Zeiten für die Interrupt-Verarbeitung zu reduzieren. Da die gegenseitigen Unterbrechungen der Threads vollkommen asynchron erfolgen können, muss das System mit ausreichend RAM-Kapazität ausgestattet sein, um allen Worst-Case-Stacks gleichzeitig gerecht werden zu können.

In Systemen mit wenig RAM oder vielen Threads kann dies übermäßig hohe Kosten verursachen. In jedem Fall muss der Stack-Bereich so groß bemessen sein, dass die ungünstigste Kombination von geschachtelten Funktionsaufrufen, lokalen Variablenzuweisungen, ISR-Aktivitäten sowie möglicherweise auch Thread-Control-Block und Kontext unterstützt wird.

Mit Zusatz-Features wie einer GUI, einem integrierten Webserver oder TCP/IP-Stacks kommen weitere Threads hinzu, die jeweils ihren eigenen Stack benötigen, sodass es selbst in Systemen mit großzügiger RAM-Kapazität nicht unüblich ist, dass die Speicherkapazität erschöpft ist. Bei Systemen mit Cache-Speicher kann das Problem der Cache-»Verunreinigung« durch andere Threads (cache-related preemption delay) auftreten, solange keine besonderen Gegenmaßnahmen getroffen werden.

Diese sind aber wiederum der Performance abträglich. Der unterbrechende Thread kann, während er verarbeitet wird, die Daten und Befehle des unterbrochenen Threads aus dem Cache entfernen. Wenn der unterbrochene Thread anschließend seine Arbeit wieder aufnimmt, wird er dadurch langsamer, weil mehr Cache-Zugriffe fehlschlagen. Mit der Zeit hat ARM seine Prozessorarchitekturen weiterentwickelt, um den Verarbeitungsaufwand im Zusammenhang mit Kontextwechseln und ISRs zu verringern.

Die Verarbeitungszeit, die bei den Prozessoren dafür aufzuwenden ist, hat die Performance-Hürde für reaktionsschnelle und echtzeitfähige Systeme kleiner gemacht. Die modernen »Cortex-M«-, »Cortex-R«- und »Cortex-A«-Architekturen bieten zwar mehr Performance, doch jede Präemption bringt zusätzlichen Zeitaufwand mit sich. Unabhängig vom jeweils verwendeten ARM-Prozessor ließe sich die Systemperformance steigern, wenn man die Präemptionen vermeiden könnte. Gleichzeitig ließe sich Strom sparen, weil das System länger im Sleep-Modus verbleiben könnte, oder die Taktfrequenz ließe sich insgesamt verringern.

Präemption einschränken

In Sachen Präemption gibt es keineswegs nur schwarz oder weiß. Viele Systeme bleiben voll echtzeitfähig, auch wenn die Systementwickler keine uneingeschränkte Präemption der Threads untereinander zulassen. Tatsächlich gibt es zwischen »nicht unterbrechbar« und »vollständig unterbrechbar« ein ganzes Kontinuum von Zwischenstufen.

Eine vielversprechende Technik, um Präemptionen einzuschränken, ist das »Preemption-Threshold Scheduling« (PTS). Es wurde 1997 von Bill Lamie entwickelt und ist im Echtzeitbetriebssystem »ThreadX« von Express Logic implementiert. Mit PTS versucht man, die Zahl der Präemptionen auf ein Minimum zu reduzieren, ohne die Scheduling-Fähigkeit des Systems aufzugeben.

Wang und Saksena untersuchten in die Echtzeiteigenschaften der PTS-Technik, wie sie in ThreadX implementiert ist. Das normale, vollständig präemptive Scheduling weist jedem Thread eine bestimmte Priorität zu, die damit über zwei Aspekte des Verhaltens dieses Threads bestimmt. Sie entscheidet erstens darüber, ob der Thread einen anderen Thread unterbrechen kann, und bestimmt zweitens, ob er durch einen anderen Thread unterbrochen werden kann.

PTS weist beiden Aspekten jeweils eine eigene Priorität zu. Die nominelle Priorität entscheidet darüber, ob der Thread andere Threads unterbrechen kann. Dagegen ist die so genannte »Preemption-Threshold« die effektive Priorität während der Ausführung des Threads.

Bild 1 illustriert das Prinzip dieses Verfahrens. Angenommen, die Priorität eines Threads sei 20 und seine Preemption-Threshold sei auf 15 eingestellt. Wenn also die Verarbeitung des Threads beginnt, hebt das System dessen Priorität automatisch von 20 auf die Preemption-Threshold von 15 an. Dadurch können Threads mit einer Priorität von 19 bis 15 - obwohl sie gegenüber dem laufenden Thread höher priorisiert sind - diesen nicht unterbrechen.

Mit PTS kann man folglich Gruppen von Threads schaffen, die sich gegenseitig nicht unterbrechen dürfen. Voll präemptive und vollständig nicht-präemptive Scheduling-Strategien lassen sich als Grenzfälle der PTS-Technik ansehen. Indem der Entwickler die Preemption-Thresh-old eines Threads gleich seiner eigentlichen Priorität setzt, vereinfacht sich das PTS-Verfahren zu einem voll präemptiven Scheduling-Konzept.

Setzt er die Preemption-Threshold dagegen auf die höchste im System vorkommende Priorität, so wird das PTS-Verfahren zu einer vollständig nicht-präemptiven Scheduling-Strategie. Präemptionen zwischen verschiedenen Threads lassen sich so einschränken, dass sie nur vorkommen, wenn es für die Aufrechterhaltung der Scheduling-Fähigkeit eines Systems nötig ist.

Wang und Saksena entwickelten eine Methode, um eine machbare Preemption-Threshold-Zuweisung zu ermitteln, falls eine solche existiert. Die Methode beginnt mit einem vollständig präemptiven System, die Preemption-Threshold eines jeden Threads ist also identisch mit seiner eigentlichen Priorität. Ausgehend vom Thread mit der niedrigsten Priorität, wird die Preemption-Threshold der einzelnen Threads so lange angehoben, bis das System durch jegliche weitere Anhebung seine Scheduling-Fähigkeit verlieren würde. Dies lässt sich mithilfe standardmäßiger Echtzeitanalyse-Techniken ermitteln.

Zahl der Präemptionen reduziert

Wang und Saksena untersuchten in außerdem, wie effektiv das PTS-Verfahren die Zahl der zur Einhaltung der Deadlines erforderlichen Präemptionen reduzieren kann. Wie sie herausfanden, senkt PTS die Zahl der Präemptionen gegenüber dem voll präemptiven Scheduling um 5 Prozent bis 32 Prozent. Sie untersuchten dabei zufällig erzeugte, periodische Thread-Gruppen mit einer festen Anzahl Threads, aber mit gleichmäßig verteilten zufälligen Perioden und Rechenzeiten.

Durch dieses Verfahren lässt sich das Verhalten verschiedener Scheduling-Strategien über ein umfangreiches Auslastungsspektrum beobachten und die Abhängigkeit von verschiedenen Faktoren untersuchen. In Bild 2 ist klar zu erkennen, dass PTS die Zahl der Kontextwechsel signifikant verringert - abhängig von der Thread-Anzahl und dem Parameter »MaxPeriod«. Jejurikar und Gupta untersuchten in den Einsatz von PTS in einem Cache-basierten System, in dem zusätzlich das Dynamic-Voltage-Scaling eingesetzt wurde, um den Energiebedarf zu senken.

Dabei stellten sie fest, dass sich die Zahl der erforderlichen Kontextwechsel mit PTS entscheidend verringerte. Die Autoren zählten zwei Arten von Kontextwechseln (tatsächliche und effektive) und verglichen sie mit der Anzahl, die sich mit einem voll präemptiven Scheduling-Konzept ergeben. Effektive Kontextwechsel treten auf, wenn ein Thread seine Verarbeitung beginnt oder beendet.

Ihre Bedeutung resultiert aus ihrem direkten Einfluss auf die Cache-Performance, weil sie die Lokalität der Speicherzugriffe aufbrechen. Jejurikar und Gupta wiesen nach, dass die Zahl der Kontextwechsel mit PTS im Durchschnitt um 90 Prozent geringer war als mit dem voll präemptiven Scheduling, und zwar weitgehend unabhängig von der Prozessorauslastung (Bild 3). Die Zahl der effektiven Kontextwechsel verringerte sich um 19 Prozent bis 29 Prozent, wodurch die Leistungsfähigkeit des Speichersystems ungefähr um denselben Prozentsatz zunahm.