Man stelle sich ein Programm vor, das nahezu niemals ausfällt, immer durchgehend 24 h an sieben Tagen pro Woche läuft und jederzeit überwacht werden kann – was es gerade tut, wie der Systemzustand ist und wenn es doch ausfällt, dokumentiert warum das Programm ausgefallen ist. Zudem ist es zu jeder Zeit möglich, Korrektur-Updates einzuspielen, um ein erkanntes Problem zu lösen oder eine bestimmte Funktion zu verbessern. Das alles bei maximaler Sicherheit.
Ein solches Programm gibt es in Wirklichkeit nicht, aber es kann einiges getan werden, um zumindest die üblichen Fehler zu vermeiden und die Stabilität zu verbessern:
Einsatz moderner Compiler und Programmierparadigmen, objektorientierte Entwicklung, RAII (Resource Acquisition Is Initialization), Smart Pointer, Sammlungen, Lambdas und alles, was noch weiter die Sicherheit erhöht. Standard-Pointer – auch Raw Pointer genannt – gibt es solange wie »C« aber Smart Pointer sind sicherer in der Anwendung. Vorzugsweise sind dynamische und statische Typumwandlungen (Static and Dynamic Casts) gegenüber uminterpretierten Casts und C-Casts zu verwenden.
Tests sind ganz wesentlich für eine stabile Anwendung. Auch wenn es recht kompliziert werden kann, Tests für einen Xtensa-Prozessor im Emulator QEMU auf einem PC durchzuführen, so kann doch mit Hilfe von Abstraktionsebenen und Mocks der größte Teil von entwickeltem Code auf dem Betriebssystem der Entwicklungsmaschine getestet werden. Die Entwicklungsumgebungen ESP-IDF (Espressif IoT Developement Framework) und PlatformIO bieten dazu Möglichkeiten, um die Sache etwas zu erleichtern. Da der Quellcode Standard-C-Code ist, kann zudem das Werkzeug cppcheck zur statischen Code-Analyse verwendet werden.
Versionskontrolle sollte stets für den Quellcode gepflegt werden, auch wenn es sich nur um Applikationen für ein »Proof-of-Concept« (POC) handelt.
Crash Dump Analysis und Stack Overflow Detection sind Teil der Entwicklungsumgebung ESP-IDF und die Anwendung wird dringend empfohlen.
Logs sind wichtig, um Fehlerursachen oder Fehlverhalten aufzudecken und die Entwicklungsumgebung ESP-IDF bietet Logging-Mechanismen zum Tracen des Programmablaufs. Es ist auch sehr hilfreich, diese Meldungen als Dateien speichern zu können, um sie »post mortem« zu analysieren oder einen Netzwerk-Syslog aufsetzen zu können.
OTA-Updates, Aktualisierungen per Funk (OTA – Over The Air), Versionierung, signierte Zertifikate und eine »Inszenierungsumgebung« (Staging Environment), die jene Hardware vollständig abbildet, auf der die Firmware betrieben werden soll.
Was können Embedded-Entwickler also tun, um Software zuverlässiger zu machen, robust und sicher?
Validierung der Eingabedaten: Eingabedaten sind stets zu verifizieren, der »Buffer Overrun« ist immer noch eine häufig verwendete Hacker-Methode und auch SQL-Injection ist noch lange nicht vergessen.
Tests: Sie sind möglichst unter erschwerten Bedingungen durchzuführen, mit ungültigen Eingabedaten, langen WiFi-SSIDs (Service Set Identifier), externen Störimpulsen und wiederholtem Unterbrechen der Stromversorgung. Worst-Case-Fehler können zum Software-Crash führen, dürfen aber nicht die Sicherheit beeinträchtigen.
Moderne Programmierparadigmen: Eingesetzt werden sollten beispielsweise: Warteschlangen, Ereignisse, Callbacks, Ausnahmen, Mikrokomponenten, Layer, Steuerungsumkehr.
Logs: Sie sollten alle Informationen beinhalten die notwendig sind, um ein Problem durch Fernzugriff zu diagnostizieren.
Welche Maßnahmen sind möglich, um Software fehlertolerant, hoch verfügbar und selbsterholend zu gestalten?
Fail Fast: Ein möglichst rasches Auftreten von Fehlern ist zu provozieren und bietet den Vorteil, dass Fehler bereits während der Entwicklungsphase gelöst werden können. Fehler dürfen keinesfalls einfach hingenommen werden.
Exception Handling: Eine Exception tritt in einem Ausnahmezustand auf, der nicht eintreten sollte. Wenn doch, ist die Ursache zwingend herauszufinden. Grundsätzlich sollten Exceptions nicht für eine Standard-Fehlerbehandlung verwendet werden.
Reboot bei Fehler: Die Entwicklungsumgebung ESP-IDF ist konfiguriert zum »Reboot on Panic«, d.h. ungültige Pointer-Zugriffe genau wie Stack-Overflows führen zu einem Reboot.
Watchdog: Manchmal endet ein Fehler nicht in einem Programmabsturz, sondern in einer extrem verzögerten Reaktionszeit. Manchmal gerät eine Task auch in eine Endlosschleife und beansprucht die gesamten Prozessorressourcen, um gar nichts zu tun. Watchdogs und Heartbeats wurden zur Erkennung genau solcher Situationen geschaffen und sollten daher auch verwendet werden (Bild 4).
Erkennung hoher CPU-Last: Sie ist zwar nicht ursächlich dem Betriebssystem FreeRTOS zuzuordnen, sondern schlecht funktionierender Software, sollte aber überwacht werden.
Brownout-Erkennung: Brownout ist ein Zustand mangelnder elektrischer Versorgung. Die Versorgungsspannung ist zu gering, um die Schaltung spezifikationsgemäß zu betreiben. Dieser Zustand sollte zu einem Neustart der Hardware führen.
Was lässt sich tun, um Software vorhersagbarer, leistungsfähiger und im Zeitablauf präziser zu gestalten?
RTOS: Echzeit-Betriebssysteme bieten sichere Multi-Tasking-Eigenschaften, Warteschlangen und Interrupt-Bearbeitung.
Multicore- und schnellere Mikrocontroller: Wenn der Prozessorkern die meiste Zeit mit Rechenoperationen beschäftigt ist, kann er nicht rechtzeitig auf Stimuli reagieren, was im besten Fall zu einer reduzierten Leistung führt, im schlimmsten Fall aber die angeschlossene Hardware zerstören kann.
Verwendung von Interrupts und Timern: Einsatz von Pushing anstelle von Polling.