Fig1-CPULoad

Fehler 1 CPU Starvation; Zum „Verhungern“ von Tasks kommt es, wenn sich die CPU-Auslastung der 100-Prozent-Marke nähert. (Bild: Percepio)

Bei der Verwendung eines Echtzeitbetriebssystems (Real-Time Operating System, RTOS) können Embedded-Entwickler auf einer höheren Abstraktionsebene arbeiten, ähnlich wie bei der Umstellung von der Programmierung in Assembler auf C. Das Arbeiten auf einer solch höheren Ebene kann es einfacher machen, komplexe Anwendungen zu entwickeln.

Schwierig wird die RTOS-basierte Entwicklung dadurch, dass die RTOS-Tasks nicht isoliert sind, wobei die zwischen den Tasks bestehenden Abhängigkeiten die Ausführung mitunter auf unerwartete Weise verzögern. Subtile Codierungs-Varianten können schwer greifbare Fehler oder Performance-Probleme im endgültigen Produkt verursachen. Eine Reihe scheinbar simpler RTOS-Tasks kann, wenn diese parallel als System ausgeführt werden, zu einem überraschend komplexen Laufzeitverhalten führen. Dabei sind unzählige Szenarien denkbar, die sich mit Tests oder Code-Reviews nicht vollständig abdecken lassen.

Die Herausforderung für die Entwickler besteht darin, dass die Debugging-Tools für die Entwicklung von Echtzeit-Applikationen nicht mithalten konnten, als die Entwicklung auf der Abstraktionsleiter – von Assembler und Superschleifen zu C und RTOS mit Scheduling – nach oben kletterte. Während normale Debugging-Tools nach wie vor auf Breakpoints und die Einzelschritt-Verarbeitung des Quellcodes fokussiert sind, erhalten die Entwickler mit RTOS-Trace und Visualisierung vollständigen Einblick in das Verhalten der Applikation auf der Systemebene.

Was ist die Aufgabe eines RTOS?

Die Hauptaufgabe eines RTOS besteht darin, Multitasking bereitzustellen, bei dem mehrere Programm-Module oder Tasks parallel ausgeführt werden können, um ein gemeinsames Ziel zu erreichen. Durch rasches Wechseln zwischen den verschiedenen Tasks erzeugt das RTOS die Illusion, dass alle Tasks parallel zueinander ausgeführt werden. Entwickler von RTOS-basierten Anwendungen können eine gewisse Kontrolle über Parameter wie Aufgabenprioritäten behalten, was wiederum ein deterministisches Echtzeitverhalten ermöglicht, aber sie verlieren die Kontrolle über die feineren Details. Zum Beispiel lässt sich der Programmablauf nicht mehr aus dem Quellcode ablesen, da das Betriebssystem entscheidet, welche Aufgabe zu einem bestimmten Zeitpunkt ausgeführt werden soll.

Das sind die 5 häufigsten Bugs in Echtzeitanwendungen

Mithilfe von RTOS-Trace und Visualisierung wird gezeigt, wie Entwickler auf einer höheren Abstraktionsebene arbeiten und auch solche Timing- und Synchronisationsaspekte ihrer Applikation erfassen können, die traditionellen Debuggern bisher verborgen blieben.

1: CPU Starvation

Symptome: Tasks werden langsam oder gar nicht ausgeführt

In Embedded-Systemen mit Multitasking kann es vorkommen, dass einigen Tasks zu wenig CPU-Zeit zugewiesen wird. Dies liegt häufig daran, dass die Prioritäten falsch gesetzt werden. Tasks höherer Priorität werden stets vor solchen niedrigerer Priorität ausgeführt, doch wenn letztere zu viel CPU-Zeit beanspruchen, bleibt zu wenig CPU-Zeit für erstere übrig, um ihre Aufgaben auszuführen. Diese Tasks „verhungern“ somit geradezu (Task Starvation). Als Abhilfe einfach die Prioritäten der betroffenen Tasks anzuheben, wäre zu kurz gegriffen, denn hierdurch würde die gesamte Priorisierung ad absurdum geführt. Stattdessen sollte eine hohe Priorität jenen Tasks vorbehalten bleiben, die vorhersagbar sind, nach einem wiederkehrenden Muster ausgeführt werden und – bezogen auf das Wiederholungsintervall – eine kurze Verarbeitungszeit aufweisen. Hoch priorisierte, zeitkritische Tasks, die viel CPU-Zeit beanspruchen, sollten in mehrere Tasks unterteilt werden. Hierdurch werden die zeitkritischen Elemente in einer kleinen Task hoher Priorität gebündelt, die dann eine weitere Task mittlerer bis niedriger Priorität aufruft, die den Großteil der Verarbeitung übernimmt.

2: Jitter

Symptome: Unzureichende Regulierungs-Performance: intermittierende Datenverluste oder Fehlfunktionen

Grafische Darstellung der Auswirkungen von Jitter.

Fehler 2 Jitter: Grafische Darstellung der Auswirkungen von Jitter. Percepio

Wenn eine Task in regelmäßigen Intervallen ausgeführt werden soll, beispielsweise um alle fünf Millisekunden die Drehzahl eines Motors anzupassen, wird das System anfällig gegen zufällige Verzögerungen (auch als Jitter bekannt). Kommt es bei einer Task zu Jitter, vergeht bis zur nächsten Aktivierung mehr Zeit als das vorgesehene Schlaf-Intervall. Geringfügiger Jitter lässt sich nur schwer komplett ausschließen und führt unter Umständen nicht zu Problemen. Bei exzessivem Jitter aber sieht die Sache anders aus.

Zu den sichtbaren Symptomen von übermäßigem Jitter gehören eine unzureichende Regelungs-Performance sowie intermittierende Datenverluste oder Fehlfunktionen. CPU Starvation kann die Ursache sein. Abhilfe lässt sich also schaffen, indem man die Task-Prioritäten richtig wählt und lang laufende Tasks hoher Priorität vermeidet. Es gibt jedoch noch weitere mögliche Ursachen, wie etwa eine suboptimale RTOS-Konfiguration. Außerdem sollten Entwickler einen Blick auf die Tick-Rate des RTOS werfen, also die Häufigkeit, mit der der RTOS Timer Interrupt auftritt. Im Idealfall sollte der Zeitabschnitt zwischen zwei Ticks deutlich kürzer sein als die periodische Dauer der am häufigsten ausgeführten Tasks im System.

3: Prioritätsinversion

Symptome: Das System reagiert kurzzeitig nicht oder stürzt unerwartet ab

Grafische Darstellung der Auswirkungen von Jitter.

Fehler 3 Prioritätsinversion: Darstellung der Prioritätsinversion. Percepio

Ein RTOS mit einem auf festgelegten Prioritäten basierenden Scheduler sollte hoch priorisierte Tasks vor solchen mit niedrigerer Priorität einplanen. Jedoch kann gelegentlich das Gegenteil passieren, indem niedrig priorisierte Tasks zuerst ausgeführt werden. Diese sogenannte Prioritätsinversion kann im Zusammenhang mit einem Synchronisationsobjekt wie einer Message Queue oder einem Mutex auftreten. Unterstützt das zugrundeliegende RTOS die sogenannte Prioritätsvererbung, dann lässt sich die Häufigkeit von Prioritätsinversionen durchaus eindämmen. Um diese aber vollständig zu eliminieren, ist sorgfältiges Applikationsdesign gefragt. Der Trick besteht darin, das Auftreten einer Prioritätsinversion überhaupt zu identifizieren.

4: Deadlock

Symptome: Tasks stoppen plötzlich, obwohl keine höher priorisierten Tasks in Ausführung sind

Grafische Darstellung eines Deadlocks.

Fehler 4 Deadlock: Grafische Darstellung eines Deadlocks. Percepio

Zu einem Deadlock kommt es, wenn zwei oder mehr Tasks blockiert werden, weil sie aufeinander warten, und keine der Tasks weiterlaufen kann. Ein solcher Deadlock stellt einen fatalen Zustand dar, dessen Identifikation von entscheidender Bedeutung ist.

5: Speicherlecks

Symptome: Speicherzuweisung schlägt während des Betriebs fehl

Darstellung der Speicherzuweisung.

Fehler 5 Speicherlecks: Darstellung der Speicherzuweisung. Percepio

Von der dynamischen Speicherzuweisung wird für Embedded-Software normalerweise abgeraten. Aus unterschiedlichen Gründen, ob richtig oder falsch, wird sie aber häufig dennoch angewandt. Möglicherweise wird sie außerdem – ohne Wissen des Primärentwicklers – von Third-Party-Softwarebibliotheken oder externen Entwicklerteams genutzt. Das Schwierige an der dynamischen Speicherzuweisung ist, dass jeder zugewiesene Block wieder freigegeben werden muss, wenn er nicht mehr benutzt wird. Unterbleibt diese Freigabe, geht der Anwendung irgendwann der Speicher aus.

Besonders gefährlich ist ein Speicherleck, wenn es nur gelegentlich auftritt. Es wird dann nämlich bei Funktionstests nur selten entdeckt, kann aber im Feld dennoch zu kritischen Fehlern führen. Wegen der langen Nutzungsdauer vieler Embedded-Systeme und der potenziell tödlichen oder zumindest spektakulären Ausfälle sicherheitskritischer Systeme ist es wichtig, Speicherleck-Bugs frühzeitig zu entdecken und zu beheben. Mit Tracealyzer können Entwickler RTOS-Aufrufe auf dynamische Speicherzuweisungen überwachen und mögliche Speicherlecks markieren.

Aufdecken und Beheben von Bugs bei der RTOS-Entwicklung

Es wurden hier nur die fünf häufigsten Fehler bei der RTOS-Entwicklung behandelt, aber im Code können sich noch zahllose weitere verbergen. Man kann beim Debugging unkoordiniert eine Möglichkeit nach der anderen durchspielen, damit die Applikation korrekt läuft. Um ein Problem aber zu lösen, muss der Entwickler über die ihm vorausgehende Abfolge von Software-Ereignissen Bescheid wissen – einschließlich der Interaktionen zwischen Applikation und RTOS. Traditionelle Debugging-Tools können derartigen Einblick nicht bieten. Die RTOS-Trace-Visualisierung, die so etwas wie eine Zeitlupenaufnahme der internen Abläufe einer Applikation ist, schafft dagegen Gewissheit: Sie zeigt, dass eine RTOS-Software wie vorgesehen funktioniert und kann außerdem die schnellste Möglichkeit zum Aufdecken und Korrigieren von Fehlern sein.

Johan Kraft

(Bild: Percepio)
Gründer und CEO von Percepio

(neu)

Sie möchten gerne weiterlesen?