Debugging von RTOS-Software-Systemen Schluss mit dem Rätselraten

Wie sich Debugging gut visualisieren lässt erklärt Dr. Johan Kraft von Percepio.
Wie sich Debugging gut visualisieren lässt erklärt Dr. Johan Kraft von Percepio.

Das Debuggen von Echtzeit-Betriebssystemen stellt Entwickler von Software-Systemen oft vor Herausforderungen. Tracing scheint sich als Lösung anzubieten. Eine gute Visualisierung ist dabei entscheidend.

Das Debugging RTOS-basierter Systeme lässt sich mit verbesserten Einblicken in deren Echtzeit-Ausführung stark vereinfachen. Mit dem Vereinfachen einhergehend profitieren Entwickler von einer verkürzten Debugging-Zeit von Tagen oder gar Wochen auf lediglich Stunden. Voraussetzung hierfür ist ein Software-Tracing auf RTOS-Ebene. Hierbei kommt es auf eine gute Visualisierung an, um die erzeugten Daten sinnvoll zu interpretieren.

Ein Echtzeit-Betriebssystem (Real-Time Operating System, RTOS) ist ein schnelles, deterministisches Betriebssystem, das klein genug für das Verwenden in Mikrocontrollern (MCUs) ist. RTOSs eignen sich daher ideal für Embedded- und IoT-Anwendungen. Weshalb Entwickler zunehmend RTOSs verwenden, liegt daran, dass sie beim Verringern der Code-Komplexität helfen, feste Timing Deadlines garantieren und das Wiederverwenden von Software-Modulen erleichtern. Die mit dem Nutzen eines RTOS auferlegte Struktur verbessert die Pflegbarkeit einer Anwendung und vereinfacht das Hinzufügen neuer Features. Für das Management ist all das von Vorteil, weil es die Entwicklungs-Effizienz steigern, die Markteinführungszeit verkürzen und sowohl die Produktzuverlässigkeit als auch die Kundenzufriedenheit verbessern kann.

Zeitlupenaufnahme hilft Entwicklern

Allerdings werden die Vorzüge mit einigen Komplexitäten erkauft. Subtile Entscheidungen beim Codieren können zu diffizilen, im Quellcode nicht erkennbaren Fehlern oder Leistungsproblemen im finalen Produkt führen. Während das System im Labor wie vorgesehen zu funktionieren scheint, gibt es doch unzählige Szenarien, die sich mit Testen oder Code-Reviews unmöglich alle abdecken lassen. Im ungünstigsten Fall besteht das System sämtliche Tests, stürzen beim Kunden jedoch ab. Um einen zuverlässigen Betrieb zu gewährleisten, sind beim Anwendungs-Code die bewährten Methoden für das RTOS-basierte Design zu befolgen, was allerdings gute Einblicke in das Echtzeit-Verhalten des Systems voraussetzt.

Kommt es zu solchen Schwierigkeiten, kann das Debugging zu einem Alptraum werden, denn die Umstände, die zu dem Problem geführt haben, sind häufig nicht im Detail bekannt und lassen sich dementsprechend schwer reproduzieren. Die Entwickler stochern hier oft im Nebel und müssen eine Möglichkeit nach der anderen ausprobieren, um die Anwendung ordnungsgemäß zum Laufen zu bringen. Um jedoch sicher sein zu können, dass das Problem tatsächlich behoben ist, muss ein Entwickler über die genaue Abfolge der Software-Ereignisse Bescheid wissen, die dazu geführt haben – einschließlich der Interaktionen zwischen Anwendung und RTOS. Traditionellen Debugging-Werkzeugen fehlt die Fähigkeit allerdings.
Die RTOS-Trace-Visualisierung kann man sich wie eine Zeitlupenaufnahme der internen Abläufe der Anwendung vorstellen. Sie ist eine gute Möglichkeit sich Gewissheit darüber zu verschaffen, dass eine RTOS-Software wie beabsichtigt funktioniert. Außerdem ist sie die schnellste Möglichkeit zum Erkennen und Beseitigen von Fehlern.

Ein zweischneidiges Schwert

Die Hauptaufgabe eines RTOS ist es, das Multitasking zu ermöglichen. Es gestattet ein Aufteilen der Funktion einer Software in mehrere »parallele« Programme, sogenannte »Tasks« oder Aufgaben. Ein RTOS schafft also die Illusion des Parallelbetriebs, indem die Verarbeitung schnell von einer Task zur anderen wechselt. Im Unterschied zu Universal-Betriebssystemen gibt ein RTOS dem Entwickler die vollständige Kontrolle über das Multitasking und ermöglicht hiermit ein deterministisches Echtzeitverhalten.

Ein RTOS übernimmt die Kontrolle über die Programmausführung und führt einen neuen Abstraktionsgrad in die Form der Tasks ein. Wird ein RTOS verwendet, geht der Kontrollfluss eines Programms nicht mehr aus seinem Quellcode hervor, denn das RTOS entscheidet, welche Task zum jeweiligen Zeitpunkt ausgeführt wird. Ähnlich wie der Umstieg von Assembler- auf C-Programmierung, stellt das eine grundlegende Umstellung dar. Dank des höheren Abstraktionsgrads macht das einen Produktivitätsgewinn möglich, gleichzeitig bedeutet es jedoch, dass weniger Kontrolle über einzelne Details besteht.

Wir haben es hier also mit einem zweischneidigen Schwert zu tun: Das Design komplexer Anwendungen mag einfacher werden, das Validieren und Debuggen kann sich hinterher jedoch schwieriger gestalten. Ein RTOS kann zwar die Komplexität des Quellcodes der Anwendung reduzieren, verringert jedoch nicht die Komplexität der Anwendung selbst. Eine Reihe scheinbar einfacher RTOS-Tasks kann, wenn sie als System ausgeführt werden, zu einem überraschend komplexen Echtzeitverhalten führen.

Herausforderungen mit Echtzeitbetriebssystemen

Die Entwickler müssen vorgeben, wie die Tasks mithilfe der RTOS-Dienste interagieren und Daten untereinander austauschen sollen. Überdies müssen Entwickler über wichtige RTOS-Parameter wie etwa die Priorität (das heißt die relative Dringlichkeit) der Tasks entscheiden, die alles andere als selbstverständlich sein kann. Selbst wenn der gesamte Code nach den bewährten Methoden für RTOS-basiertes Design geschrieben wurde, kann es sein, dass andere Systembestandteile – ob aus eigener Entwicklung oder zugekauft – in derselben RTOS-Umgebung laufen, sich jedoch nicht an dieselben Prinzipien halten.

Ein grundlegendes Problem, welches das RTOS-basierte Design so kompliziert macht, ist, dass es sich bei RTOS-Tasks nicht um isolierte Objekte handelt. Es bestehen Abhängigkeiten, die das Ausführen einer Task auf unvorhergesehene Weise verzögern oder anhalten können. Das kann die Leistungsfähigkeit beeinträchtigen, das System dazu bringen, dass es nicht reagiert oder instabil wird, oder sogar zu intermittierenden Datenverlusten führen.

Zwischen den Tasks besteht mindestens die Abhängigkeit, dass sie die Verarbeitungszeit desselben Prozessors nutzen. Tasks mit höherer Priorität können aktiv werden und die Prozessorzeit zu nahezu jedem Zeitpunkt für sich beanspruchen, bis alle aktiven höher priorisierten Tasks abgearbeitet sind. Hinzu kommt, dass Tasks häufig bestimmte Software-Ressourcen gemeinsam nutzen (zum Beispiel globale Daten oder Schnittstellentreiber). Das erfordert blockierende Synchronisations-Aufrufe, um Zugriffskonflikte auszuschließen. Solche Task-Abhängigkeiten sind von vielen Faktoren beeinflussbar, zu denen Änderungen bei den Eingangswerten, dem Timing der Eingangswerte oder der Task-Verarbeitungszeiten zählen.

Solche Probleme sind im Code nicht erkennbar und lassen sich oft mit Modultests nicht aufdecken, treten im integrierten Produkt dennoch auf – entweder beim Test des Gesamtsystems oder erst beim Kunden. Das macht es schwer, die Phänomene für Debugging-Zwecke zu reproduzieren, solange nicht die genaue Sequenz der Software-Ereignisse bekannt ist, die dem Problem vorausgingen.