Typischerweise ist der komplexeste Teil eines eingebetteten Systems die Software. Diese Komplexität bedeutet unweigerlich eine hohe Fehlerwahrscheinlichkeit. Obwohl ein sorgfältiges Entwickeln, gefolgt von umfassendem Debugging und Testen, Fehler minimieren kann, ist es sehr unwahrscheinlich, dass es alle Möglichkeiten für Fehler ausschließt. Durch eine defensive Codierung und das Einbauen von Prüfungen können die Auswirkungen von verbleibenden Codierungsfehlern aber minimiert werden. Es gibt zwei Hauptarten von Softwarefehlern: Datenfehler und Codeschleifen.
Datenfehler
Die häufigste Ursache für Datenfehler in C-Code ist die Verwendung von Zeigern. Zeiger sind eine sehr mächtige Funktion der Sprache, aber diese Macht kann auch gefährlich sein. Es gibt drei häufige Arten, wie Zeiger Probleme verursachen:
Es gibt keine festen Regeln, aber eine strikte Begrenzung: die Indirektion auf nicht tiefer als Zeiger auf Zeiger aufZeiger kann sinnvoll sein. Wenn eine Memory Management Unit (MMU) zur Verfügung steht, kann sie verwendet werden, um den Speicher vor Zugriffen zu schützen und damit jegliche Datenfehler einzudämmen.
Das Überlaufen von aggregierten Datenstrukturen – insbesondere des Stacks und der Arrays – ist eine häufige Ursache für Datenfehler. Vor allem Stacks sind problematisch. Das Festlegen der Stack-Größen in einer Multithread-Anwendung kann eine Herausforderung sein, auch wenn es Tools gibt, die dabei helfen. Wenn ein Stack überläuft, wird die Aufgabe vermutlich ohne Probleme fortgesetzt. Allerdings beschädigt der Überlauf wahrscheinlich den Stack eines anderen Tasks, der möglicherweise abstürzt. Diese Art von Fehler ist sehr schwer zu lokalisieren.
Ein Ansatz zur Abschwächung von Stack-Überläufen ist die Verwendung von »Schutzwörtern« (Guard Words). Dies sind zusätzliche Wörter an beiden Enden des Stacks, die mit einem bekannten Wert geladen werden. Dieser Wert sollte eine eher zufällige, ungerade Zahl sein. Im Hintergrund laufender Code kann die Schutzwörter überwachen und Maßnahmen ergreifen, wenn ihr Wert geändert wird – bei einem 32-bit-System besteht nur eine Wahrscheinlichkeit von 4 Milliarden zu 1, dass ein Datenfehler übersehen wird (Bild).
Wenn der Stack voll ist, zeigt der Stack-Zeiger auf das Schutzwort. Wenn ein weiteres Element auf den Stack geschoben wird, wird das Schutzwort beschädigt. Der Überwachungscode kann Maßnahmen ergreifen, bevor es zu einem Absturz (eines anderen Tasks) kommt.
Der gleiche Ansatz kann auf Verletzungen von Array-Grenzen angewendet werden. Der häufigste Fehler ist auch hier das Überlaufen eines Arrays, sodass ein Schutzwort ebenfalls effektiv sein kann.
Schleifen im Code
Mit Ausnahme der äußeren Schleife eines immer laufenden Tasks sollte es im Code für ein Embedded-System niemals eine Endlosschleife geben. Wenn Code in einer Schleife festhängt, handelt es sich um einen Codierungsfehler, oder der Code wartet auf eine Antwort der Schaltung (Hardware), die nicht kommt.
Der einzige mögliche Schutz vor Codierungsfehlern, die Schleifen verursachen, ist eine Art »Watchdog«. Dies ist typischerweise eine Hardwareeinrichtung. Die Schaltung benötigt einen periodischen Zugriff, andernfalls kommt es zu einer Zeitüberschreitung und der Watchdog »beißt«; dies kann zu einem Reset oder vielleicht zu einem nicht maskierbaren Interrupt führen.
Ein häufiger Fehler ist es, den Watchdog-Servicecode in eine Timer-Interrupt-Service-Routine zu packen. Das bedeutet, dass der Watchdog auch dann bedient wird, wenn der Code des Hauptprogramms (Main) in einer Schleife läuft.
In einer Multithreading-Umgebung (Multitasking) könnte ein Task die Rolle des Watchdogs übernehmen, vorausgesetzt, das Betriebssystem ist präemptiv.
Wenn Code in einer Schleife läuft, während er auf eine Antwort von einem Peripheriegerät wartet, sollte eine Zeitüberschreitung (Timeout) in die Schleife eingebaut werden. Die Möglichkeit eines Hardwareausfalls sollte immer berücksichtigt werden.
Das Ausmaß, in dem ein Fehler gemeldet werden kann, hängt stark von dem jeweiligen System ab. Das Gleiche gilt für die mögliche Wiederherstellung.
In tief eingebetteten Systemen gibt es möglicherweise keine Benutzerschnittstelle, sodass die einzige Möglichkeit im Falle eines Fehlers darin besteht, das System zurückzusetzen, in der Hoffnung, dass das Problem dadurch behoben wird. Möglicherweise ist es machbar, den Fehler für eine spätere Referenz zu protokollieren. Wenn es irgendeine Art von Benutzeroberfläche gibt, bieten sich andere Möglichkeiten:
Wenn nur eine einzelne LED zur Verfügung steht, kann diese blinken, um den Status des Systems anzuzeigen. Blinken ist besser als gleichmäßiges Leuchten, da es zeigt, dass etwas passiert.
Ein guter Ansatz ist ein gleichmäßiger langsamer »Herzschlag«, um zu zeigen, dass das System normal funktioniert. Schnellere Blinkraten können verwendet werden, um einen Fehler anzuzeigen. Verschiedene Tastverhältnisse – Serien von Blinksignalen gefolgt von einer Pause – können verwendet werden, um verschiedene Fehler anzuzeigen (Listing 3).
Selbsttests sind für die meisten Embedded-Systeme eine Überlegung wert. Die erste Regel ist, zu akzeptieren, dass ein Fehler möglich ist, und dann gilt es alles in Betracht zu ziehen, was schief gehen kann. Für den Selbsttest kann zusätzlicher Code hinzugefügt werden, um den Zustand des Systems zu überwachen, und es können Mittel zur Abschwächung oder Behebung von Fehlern in Betracht gezogen werden, inklusive der Warnhinweise an den Benutzer.
Der Autor
Colin Walls
verfügt über mehr als vierzig Jahre Erfahrung in der Elektronikindustrie, wobei er sich hauptsächlich mit Embedded-Software beschäftigt. Er hält häufig Vorträge auf Konferenzen und Seminaren und ist Autor zahlreicher technischer Artikel und dreier Bücher über Embedded-Software. Zuletzt war Walls als Embedded-Software-Technologe bei Siemens Embedded, zuvor Mentor, tätig.