Suchen

Kein Hexenwerk: Debuggen von RTOS-basierenden Systemen

Autor / Redakteur: Jens Braunes * / Sebastian Gerstl

Der Einsatz eines Echtzeitbetriebssystems (RTOS) auf einem Embedded System wirkt sich nachhaltig auf dessen Laufzeitverhalten aus. Das kann Tests und Fehlersuche erschweren. Worauf ist bei Verwendung eines RTOS bei der Fehlersuche mittels eines Debuggers zu achten?

Firmen zum Thema

Bild 1: Über Konfigurationsparameter in der FreeRTOSConfig.h lassen sich eine Fülle von Einstellungen vornehmen, die auch für das Debugging relevant sind.
Bild 1: Über Konfigurationsparameter in der FreeRTOSConfig.h lassen sich eine Fülle von Einstellungen vornehmen, die auch für das Debugging relevant sind.
(Bild: PLS)

Echtzeitbetriebssysteme (Real-Time Operating Systems; RTOS) ebnen Entwicklern den Weg zu einer besseren Softwarearchitektur mit einer weitgehend hardwareunabhängigen Implementierung. Sie ermöglichen zudem verschiedene Teilaufgaben der Anwendung sauber voneinander zu trennen. Jede Teilaufgabe wird dabei als Task abgearbeitet. Das RTOS stellt sicher, dass jeder einzelnen Task auf der Hardware ausreichend Rechenzeit für die Erfüllung ihrer Aufgaben zur Verfügung steht. Diese inhärente Funktion ist allgemein als „Scheduling“ bekannt.

Zudem garantiert ein RTOS vorgegebene Antwortzeiten der Tasks und hilft gerade bei Embedded-Systemen, die meist knappen Ressourcen möglichst effizient zu nutzen. Neben dem Taskmanagement und Scheduling werden hierfür typischerweise noch weitere, oftmals aber sehr RTOS-spezifische und/oder konfigurationsabhängige Dienste angeboten. Hier einige Beispiele:

Bildergalerie
  • Für die Kommunikation zwischen den Tasks untereinander bzw. zwischen Tasks und Interrupts existieren Mechanismen wie „Queues“ oder „Mailboxes“. Sie erlauben den synchronisierten Datenaustausch und verhindern gegenseitiges Blockieren.
  • Timer können dafür genutzt werden, ausgewählte Funktionen zu bestimmten Zeitpunkten oder periodisch mit einer festgesetzten Aufruffrequenz auszuführen. Die Ausführung erfolgt dabei im Kontext eines Timer Service Tasks und hat nichts mit dem Task-Scheduling zu tun. Des Weiteren ist für diesen Dienst keine spezielle Hardwareunterstützung notwendig, weshalb sie auch als Software-Timer bezeichnet werden.

Eine Reihe weiterer Dienste und auch Programmierkonzepte lassen sich unter dem Oberbegriff „Ressourcenmanagement“ zusammenfassen. Vereinfacht gesagt geht es darum, gemeinsam genutzte Ressourcen – das kann Speicher oder auch ein Peripheral sein – auch über Taskwechsel hinweg stets konsistent zu halten. So muss etwa die Benutzung der Ressource erst sicher beendet sein, bevor sie anderweitig verwendet werden darf. Gleichzeitig ist eine gegenseitige Blockade von Tasks zu vermeiden, die eine gemeinsame Ressource benutzen. Gängige hier zur Anwendung kommende Konzepte sind z.B. Semaphoren oder Mutexe.

Bleibt die Frage, wie eine Echtzeitapplikation, die die Dienste eines RTOS nutzt, erstellt wird und wie man diese letztlich testen und debuggen kann. Betrachten wir dafür als erstes den Build.

Der Build des Echtzeitbetriebssystems

In den meisten Fällen muss das gesamte Software-Konglomerat, bestehend aus dem RTOS-Kern sowie den darauf laufenden Applikationen, als ein gemeinsames Image erstellt werden, damit es auf das Embedded System geladen werden kann. Ein Nachladen einzelner, separat erstellter Applikations-Binaries in ein laufendes Betriebssystem ist unüblich. Für den Build benötigt man also nicht nur den Quellcode der Applikation, sondern auch Quellen, C-Header und Libraries des eingesetzten RTOS.

Um dessen angebotene Dienste nutzen zu können, wird bei der Applikationsentwicklung in der Regel auf die API-Funktionen des RTOS zurückgegriffen. So lassen sich Tasks erzeugen, der Datenaustausch zwischen den Tasks synchronisieren oder Ressourcen anfordern, reservieren und wieder freigeben.

Hinzu kommen noch Konfigurationsdaten, mit deren Hilfe die RTOS-Umgebung den jeweiligen Bedürfnissen angepasst werden kann. Je nach verwendetem RTOS lässt sich beispielsweise das gewünschte Task-Scheduling-Verfahren - kooperativ oder preemptive – einstellen. Oder die Größe des Heaps. Oder, ob bestimmte Dienste oder API-Funktionen überhaupt verfügbar sein sollen. Werden diese nämlich nicht wirklich benötigt, wirkt sich das positiv auf den meist ohnehin knappen Speicher aus.

Bild 1 zeigt einen Auszug aus der Konfiguration für das weit verbreitete FreeRTOS. Die Festlegung der einzelnen Parameter erfolgt über Defines in einer C-Headdatei (FreeRTOSConfig.h). Mit Hilfe von Cross-Compilern werden dann das RTOS und die Applikation passend für den eingesetzten Mikrokontroller kompiliert und zum ladbaren Image zusammengeführt (gelinkt). Ist das alles erledigt, kann die Applikation auf den Mikrocontroller geladen und, etwa mit Hilfe eines Debuggers, getestet werden.

Erst RTOS-Awareness macht sinnvolles Debugging möglich

Grundsätzlich weiß der Debugger erst einmal nicht, dass die geladene und ausgeführte Applikation unter einem RTOS läuft. Setzt man einen Breakpoint in eine Funktion, wird die Ausführung zwar exakt dort unterbrochen und es ist auch sofort erkennbar, wo genau die Funktion steht. Welcher Task zu diesem Zeitpunkt gerade aktiv ist, bleibt aber zunächst einmal unbekannt. Darstellen lassen sich solche zusätzlichen Informationen nur, wenn der Debugger über eine sogenannte RTOS-Awareness verfügt, also sich der Anwesenheit eines RTOS bewusst ist.

Was bedeutet nun RTOS-Awareness konkret und welche Voraussetzungen müssen dafür erfüllt sein? Jedes RTOS besitzt für seine angebotenen Dienste eigene Verwaltungsstrukturen. Dazu zählen unter anderem die Taskverwaltung, Synchronisationsmechanismen, Ressourcenverwaltung etc. Die Awareness für ein bestimmtes RTOS muss diese Strukturen kennen und auch wissen, wo die entsprechenden Daten im Hauptspeicher des Embedded Systems liegen. Das hat zur Folge, dass eine RTOS-Awareness in der Regel nur für ein bestimmtes RTOS bzw. in einigen Fällen eine bestimmte Klasse von hinreichend ähnlichen Echtzeitbetriebssystemen funktionieren kann, nämlich für diejenigen, deren Verwaltungsstrukturen bekannt sind.

Die RTOS-Awareness, wie sie beispielsweise als Debugger-Add-On für die Universal Debug Engine (UDE) verfügbar ist, zeichnen sich zusätzlich durch vielfältige Visualisierungsmöglichkeiten aus. Diese liefern dem Entwickler alle nötigen Informationen, um seine Applikation nun auch im Kontext des RTOS debuggen zu können. Wurde die Ausführung wie vorher erwähnt zum Beispiel am Breakpoint unterbrochen, sieht man zusätzlich zur aktuellen Programmzeigerposition im Quellcode jetzt auch, welcher Task die Funktion gerade ausführt (Bild 2). Zudem erhält der Entwickler eine komplette Übersicht über alle anderen erzeugten Tasks, und dies mit ihrem jeweiligen derzeitigen Status.

Wie im Bild 2 zu erkennen ist, stehen im Fall der UDE für jeden Task auch noch weitere nützliche Informationen zur Verfügung, beispielsweise die gerade aktuelle Stack-Auslastung. Für den Entwickler erweist sich diese Information insbesondere zur Optimierung des Speicherverbrauchs hilfreich. Denn einerseits soll ja nicht zu viel Speicher ungenutzt blockiert werden und damit anderen Funktionen nicht mehr zur Verfügung stehen, andererseits muss der Stack genügend „Luft“ haben, damit es aufgrund von Stack-Überläufen nicht zum Crash kommt. Wer zum Beispiel mit einem der vielen Demo-Projekte für das FreeRTOS erste Gehversuche unternimmt, wird schnell feststellen, dass die aktuelle Stack-Auslastung im Debugger gar nicht angezeigt wird.

Warum das so ist, wird erst klar, wenn man sich mit den RTOS-eigenen Konfigurationsmöglichkeiten tiefer auseinandersetzt. Bestimmte, für die Visualisierung in der RTOS-Awareness relevante Informationen sind für das RTOS selber erst einmal überhaupt nicht relevant. Im sogenannten Task Control Block (TCB) speichert FreeRTOS lediglich den aktuellen Stack-Pointer (pxTopOfStack) und die Basisadresse des Stacks (pxStack).

Die Stack-Größe ist an dieser Stelle jedoch unbekannt und die RTOS-Awareness im Debugger kann die aktuelle Auslastung nicht berechnen. Wichtig ist der folgende Eintrag in der bereits bekannten FreeRTOSConfig.h Konfigurationsdatei:

#define configRECORD_STACK_HIGH_ADDRESS 1

Erst mithilfe dieses Eintrags fügt FreeRTOS in den TCB zusätzlich auch einen Zeiger auf das Ende des Stacks ein (pxEndOfStack). Nun funktioniert auch die Anzeige der aktuellen Stack-Auslastung.

Ähnlich verhält es sich auch mit anderen, für das Debugging nützlichen Informationen. So verwaltet das RTOS beispielsweise keine Queues. Bei FreeRTOS stehen lediglich API-Funktionen bereit, so dass die Applikation Queues anlegen und darüber Daten zwischen Tasks bzw. Interrupts und Tasks austauschen kann. Nur wenn die verwendete Queue im Betriebssystem-Kern mittels der API-Funktion

vQueueAddToRegistry()

registriert wurde und die Defines

#define configQUEUE_REGISTRY_SIZE <size>

auf einen Wert größer 0 gesetzt sowie

#define configUSE_TRACE_FACILITY 1

definiert wurde, kann der Debugger auch Informationen zu den Queues liefern.

Tracing des Schedulings der Task-Ausführungen

Die bisherigen Beispiele für das „RTOS-Aware“-Debugging lieferten immer nur einen „Snapshot“ des Systemzustandes zu einem bestimmten Zeitpunkt. Oftmals ist allerdings der zeitliche Verlauf der Task-Ausführungen deutlich interssanter, und dessen Untersuchung spannender.

Zum Beispiel ist das Fall, wenn ungünstige Priorisierungen bestimmte Tasks zu oft verdrängen und damit das erforderliche Zeitverhalten nicht erreicht wird. Oder auch, wenn sich Tasks aufgrund von gegenseitigen Abhängigkeiten untereinander blockieren. In solchen Fällen hilft eine Visualisierung der Task-Ausführungen (Bild 3). Dafür müssen natürlich die Taskwechsel durch den Debugger beobachtet werden, und zwar ohne das Laufzeitverhalten der Applikation zu beeinflussen. Ein ständiges Anhalten an Breakpoints im Scheduler oder Ähnliches ist also in diesem Fall nicht möglich.

ESE Kongress 2020 digital

Es ist eine Premiere: Deutschlands großer Leitkongress der Embedded-Softwarebranche kommt zu Ihnen nach Hause oder ins Büro - digital, interaktiv, coronasafe und mit bewährt hohem Umfang und fachlicher Tiefe. Holen Sie sich aktuelles Wissen, Ideen und Lösungen zu Technologien, Methoden und Trends und stellen Sie die Weichen für 2021!

Jetzt Programm checken und bis zum 31. Oktober mit dem Early Bird Ticket sparen!

Mehr zu Programm und Teilnahme

Moderne Mikrocontroller bieten hierfür oftmals einen On-Chip-Trace. Mit dessen Hilfe kann das Laufzeitverhalten, sprich die Code-Ausführung, rückwirkungsfrei beobachtet werden. So erhält man zumindest schon einmal einen Eindruck, welche Funktionen wie lange ausgeführt werden und wie sie voneinander abhängen (Call-Tree).

Mit Code-Trace alleine lässt sich allerdings keine übergeordnete Taskausführung visualisieren. Denn es fehlt die Information, welcher Task gerade aktiv ist. Diese ist typischerweise in einer Running-Task-Variable gespeichert, die auf den jeweiligen TCB des aktiven Tasks zeigt. Taskwechsel können über die Beobachtung der Running-Task-Variable zwar einfach aufgezeichnet werden. Dafür muss aber unbedingt Datentrace seitens des verwendeten Mikrocontrollers zur Verfügung stehen, was allerdings oftmals leider nur bei entsprechend teuren Multicore-Bausteinen der Fall ist.

Bei einem entsprechend gut ausgebauten On-Chip-Trace lassen sich Task- und Code-Trace für einen höheren Detailierungsgrad der Visualisierung kombinieren. Bild 4 zeigt die Darstellung der Taskausführung zusammen mit den in den jeweiligen Tasks ausgeführten Funktionen, oftmals als Runnables bezeichnet, im Debugger.

Ein Tool sollte die nötige RTOS-Awareness besitzen

Wer RTOS-basierende Applikationen entwickeln und analysieren will, sollte nicht nur auf die passende RTOS-Awareness im Debugger achten. Für die erfolgreiche Fehlersuche muss außerdem dafür gesorgt werden, dass auch alle für das Debugging notwendigen Informationen aus dem RTOS verfügbar sind. Sich mit den jeweiligen Konfigurationseinstellungen des verwendeten RTOS auseinanderzusetze, bleibt also für ein erfolgreiches und effizientes Debugging letztendlich das A und O.

Kostenloser Tool-Track auf dem ESE Kongress digital

Wenn Sie näheres zum Debugging mit der UDE und anderen hilfreichen Software-Engineering-Tools erfahren möchten, werfen Sie doch einen Blick auf die Vortragsreihe "Tipps, Tricks, Lösungen" des ESE Kongress digital. Die Sondertracks zu hilfreichen Tools rund ums Embedded Software Engineering findet am 27. 11. 2020 online statt und ist nach kostenloser Registrierung frei zugänglich.

Nähere Informationen zum kostenlosen Tool-Track

* * Jens Braunes ... ist Product Marketing Manager bei der PLS Programmierbare Logik & Systeme GmbH in Dresden

(ID:46950986)