
Die Fehlerbehandlung ist bei Betriebssystemen, die für eingebettete Systemanwendungen entwickelt wurden, nicht die häufigste. Dies ist ein unvermeidbares Ergebnis begrenzter Ressourcen, da alle eingebetteten Systeme bestimmte Einschränkungen aufweisen. Und nur eine kleine Anzahl solcher Systeme kann sich wie Desktop-Systeme verhalten, dh dem Benutzer die Möglichkeit bieten, bei außergewöhnlichen Ereignissen Aktionen auszuwählen.
In Nucleus SE gibt es im Allgemeinen drei Arten der Fehlerprüfung:
- Mittel zum Überprüfen des Zustands der ausgewählten Konfiguration, um sicherzustellen, dass die ausgewählten Parameter nicht zu Fehlern führen;
- optional enthaltener Code zur Überprüfung des Laufzeitverhaltens;
- Bestimmte API-Funktionen, die zur Entwicklung zuverlässigerer Codes beitragen.
All dies wird in diesem Artikel zusammen mit einigen Ideen zur Diagnose durch den Benutzer erörtert.
Frühere Artikel in der Reihe: Überprüfen Sie die Einstellungen
Nucleus SE wurde mit dem Fokus auf eine hohe Benutzerkonfigurierbarkeit entwickelt, um die bestmögliche Nutzung der verfügbaren Ressourcen zu gewährleisten. Eine solche Konfigurierbarkeit ist eine komplexe Aufgabe, da die Anzahl der möglichen Parameter und die gegenseitigen Abhängigkeiten zwischen ihnen sehr groß sind. Wie in vielen vorherigen Artikeln angegeben, werden die meisten Benutzerschritte zum Konfigurieren von Nucleus SE mithilfe der Direktiven
#define in der Datei
nuse_config.h ausgeführt .
Um Konfigurationsfehler zu identifizieren, enthält die Datei
nuse_config.c bis
#include die Datei
nuse_config_check.h , die Integritätsprüfungen für die Direktiven
#define durchführt . Das Folgende ist ein Ausschnitt aus dieser Datei:
/*** Tasks and task control ***/ #if NUSE_TASK_NUMBER < 1 || NUSE_TASK_NUMBER > 16 #error NUSE: invalid number of tasks - must be 1-16 #endif #if NUSE_TASK_RELINQUISH && (NUSE_SCHEDULER_TYPE == NUSE_PRIORITY_SCHEDULER) #error NUSE: NUSE_Task_Relinquish() selected - not valid with priority scheduler #endif #if NUSE_TASK_RESUME && !NUSE_SUSPEND_ENABLE #error NUSE: NUSE_Task_Resume() selected - task suspend not enabled #endif #if NUSE_TASK_SUSPEND && !NUSE_SUSPEND_ENABLE #error NUSE: NUSE_Task_Suspend() selected - task suspend not enabled #endif #if NUSE_INITIAL_TASK_STATE_SUPPORT && !NUSE_SUSPEND_ENABLE #error NUSE: Initial task state enabled - task suspend not enabled #endif /*** Partition pools ***/ #if NUSE_PARTITION_POOL_NUMBER > 16 #error NUSE: invalid number of partition pools - must be 0-16 #endif #if NUSE_PARTITION_POOL_NUMBER == 0 #if NUSE_PARTITION_ALLOCATE #error NUSE: NUSE_Partition_Allocate() enabled – no partition pools configured #endif #if NUSE_PARTITION_DEALLOCATE #error NUSE: NUSE_Partition_Deallocate() enabled – no partition pools configured #endif #if NUSE_PARTITION_POOL_INFORMATION #error NUSE: NUSE_Partition_Pool_Information() enabled – no partition pools configured #endif #endif
Der obige Code führt die folgenden Überprüfungen durch:
- Überprüfen Sie, ob mindestens eine, aber nicht mehr als 16 Aufgaben konfiguriert sind.
- Bestätigung, dass die ausgewählten API-Funktionen mit dem ausgewählten Scheduler und anderen angegebenen Parametern kompatibel sind;
- Überprüfung, dass nicht mehr als 16 Instanzen anderer Kernelobjekte erstellt wurden;
- Bestätigung, dass API-Funktionen für nicht deklarierte Objekte nicht ausgewählt wurden.
- Sicherstellen, dass die API-Funktionen für Signale und Systemzeit nicht verwendet werden, wenn die Unterstützung für diese Dienste deaktiviert ist;
- Überprüfung des ausgewählten Schedulertyps und der zugehörigen Parameter.
In allen Fällen führt die Erkennung von Fehlern zur Ausführung der Direktive
#error bei der Kompilierung. Dies führt normalerweise dazu, dass die Kompilierung gestoppt wird und die entsprechende Meldung angezeigt wird.
Diese Datei garantiert nicht die Unmöglichkeit, eine falsche Konfiguration / Konfiguration zu erstellen, macht sie jedoch sehr unwahrscheinlich.
API-Einstellungen überprüfen
Wie Nucleus RTOS kann Nucleus SE Code einschließen, um die Parameter des Aufrufs von API-Funktionen zur Laufzeit zu überprüfen. Normalerweise wird dies nur beim ersten Debuggen und Testen verwendet, da im endgültigen Programmcode ein übermäßiger Speicherverbrauch unerwünscht ist.
Die Parameterprüfung wird aktiviert, indem der Parameter
NUSE_API_PARAMETER_CHECKING in der Datei
nuse_config.h auf
TRUE gesetzt wird . Dies führt zur Kompilierung des erforderlichen zusätzlichen Codes. Das folgende Beispiel zeigt die Überprüfung der Parameter einer API-Funktion:
STATUS NUSE_Mailbox_Send(NUSE_MAILBOX mailbox, ADDR *message, U8 suspend) { STATUS return_value; #if NUSE_API_PARAMETER_CHECKING if (mailbox >= NUSE_MAILBOX_NUMBER) { return NUSE_INVALID_MAILBOX; } if (message == NULL) { return NUSE_INVALID_POINTER; } #if NUSE_BLOCKING_ENABLE if ((suspend != NUSE_NO_SUSPEND) && (suspend != NUSE_SUSPEND)) { return NUSE_INVALID_SUSPEND; } #else if (suspend != NUSE_NO_SUSPEND) { return NUSE_INVALID_SUSPEND; } #endif #endif
Eine solche Überprüfung der Parameter kann dazu führen, dass der API-Aufruf einen Fehlercode ausgibt. Es ist ein negativer Wert der Form
NUSE_INVALID_xxx (z. B.
NUSE_INVALID_POINTER ). In der Datei
nuse_codes.h ist ein vollständiger Satz von Definitionen enthalten.
Um Fehlerwerte zu verarbeiten, kann ein zusätzlicher Anwendungscode (möglicherweise mithilfe der bedingten Kompilierung erstellt) hinzugefügt werden. Um diese zu erkennen, ist es jedoch besser, die Datenüberwachungstools moderner Firmware-Debugger zu verwenden.
Das Überprüfen der Parameter führt zu einem zusätzlichen Speicherverbrauch (zusätzlicher Code) und beeinträchtigt die Leistung des Codes. Daher wirkt sich seine Verwendung auf das gesamte System aus. Da der gesamte Quellcode von Nucleus SE dem Entwickler zur Verfügung steht, kann die Überprüfung und das Debuggen des endgültigen Anwendungscodes manuell durchgeführt werden, wenn absolute Genauigkeit erforderlich ist.
Überprüfen des Taskstapels
Bis der Run to Completion Scheduler verwendet wird, bietet Nucleus SE die Möglichkeit, den Task-Stack zu überprüfen. Dieser ähnelt einer ähnlichen Funktion in Nucleus RTOS und zeigt den verbleibenden Speicherplatz auf dem Stack an. Dieser API-Dienstprogrammaufruf (
NUSE_Task_Check_Stack () ) wurde in einem
früheren Artikel (Nr. 12) ausführlich beschrieben. Einige Ideen zum Überprüfen auf Stapelfehler finden Sie weiter unten in diesem Artikel im Abschnitt Benutzerdefinierte Diagnose.
Versionsinformationen
Nucleus RTOS und Nucleus SE verfügen über eine API-Funktion, die einfach Kernel-Versions- / Build-Informationen zurückgibt.
Nucleus RTOS API-Aufruf
Prototyp eines Serviceabrufs:
CHAR * NU_Release_Information (VOID);
Parameter: keine.
Rückgabewert:
Zeiger auf eine Zeichenfolge mit Versionsinformationen, die mit einem Null-Byte enden.
Nucleus SE API-Aufruf
Dieser API-Aufruf unterstützt die Kernfunktionalität der Nucleus RTOS-API.
Prototyp eines Serviceabrufs:
char * NUSE_Release_Information (void);
Parameter: keine.
Rückgabewert:
Zeiger auf eine Zeichenfolge mit Versionsinformationen, die mit einem Null-Byte enden.
Rufen Sie an, um Informationen zur Nucleus SE-Baugruppe zu erhalten
Die Implementierung dieses API-Aufrufs ist ziemlich einfach. Ein Zeiger wird auf die konstante Zeile
NUSE_Release_Info zurückgegeben , die in der Datei
nuse_globals.c deklariert und initialisiert
wird .
Diese Linie hat die Form Nucleus SE -
Xyymmmdd , wobei:
X - Build Status:
A = Alpha;
B = Beta;
R = Freigabe
JJ - Erscheinungsjahr
mm - Veröffentlichungsmonat
dd - Veröffentlichungstag
Nucleus RTOS-kompatibel
Nucleus RTOS bietet optionale Unterstützung für Geschichtsmagazine. Der Kernel zeichnet die Details verschiedener Systemaktionen auf. Es gibt API-Funktionen, mit denen Programme:
- Protokollierung aktivieren / deaktivieren;
- einen Journaleintrag erstellen;
- Holen Sie sich einen Journaleintrag.
Diese Funktion wird in Nucleus SE nicht unterstützt.
Nucleus RTOS verfügt außerdem über mehrere Fehlerverwaltungsmakros, mit denen Sie keine Fehlerbestätigungen (ASSERT) durchführen und benutzerdefinierte Funktionen für kritische Fehler aufrufen können. Sie sind optional in der Betriebssystembaugruppe enthalten. Nucleus SE unterstützt diese Funktionalität nicht.
Benutzerdiagnose
Bisher haben wir uns in diesem Artikel mit den von Nucleus SE selbst bereitgestellten Diagnose- und Fehlerprüfungswerkzeugen befasst. Nun lohnt es sich zu erklären, wie die vom Benutzer festgelegten oder anwendungsorientierten Diagnosetools mithilfe der vom Kernel bereitgestellten Tools und / oder unseres Wissens über seine interne Struktur und Implementierung implementiert werden können
Anwendungsspezifische Diagnose
In fast jeder Anwendung können Sie zusätzlichen Code hinzufügen, um die Integrität zur Laufzeit zu überprüfen. Der Multitasking-Kern macht es einfach und unkompliziert, eine spezielle Aufgabe für diesen Job zu erstellen. Offensichtlich werden wir in diesem Artikel nicht zu ungewöhnliche Diagnosefälle betrachten, sondern einige allgemeine Ideen.
Speicherprüfungen
Offensichtlich ist ein ordnungsgemäßer Speicherbetrieb entscheidend für die Integrität eines Prozessorsystems. Es ist nicht weniger offensichtlich, dass Sie bei einem kritischen Fehler nicht nur die Diagnose, sondern das gesamte Softwareprodukt als Ganzes ausführen können (
Anmerkung des Übersetzers: Dies ist übrigens genau der Fall, den wir im Artikel „Fake Blue Pill“ untersucht haben ). Es gibt jedoch Situationen, in denen ein bestimmter Fehler auftritt, der Anlass zur Sorge gibt, die Codeausführung jedoch nicht beeinträchtigt. Gedächtnistests sind ein ziemlich kompliziertes Thema, das den Rahmen dieses Artikels sprengt, daher werde ich nur einige allgemeine Ideen geben.
Die zwei häufigsten Fehler, die im RAM auftreten, sind:
- "Sticky Bits", wenn das Bit einen Wert von 0 oder 1 hat, der nicht geändert werden kann;
- "Übersprechen", wenn benachbarte Bits sich gegenseitig stören.
Beide Fehler können überprüft werden, indem nacheinander bestimmte Testmuster in jeden RAM-Bereich geschrieben und gelesen werden. Einige Überprüfungen können nur beim Start durchgeführt werden, noch bevor der Stapel gebildet wurde (
Anmerkung des Übersetzers: In dem oben erwähnten Artikel stellte sich heraus, dass es die erste Verwendung des Stapels war, die alles auf einmal zerstörte ). Zum Beispiel eine "laufende Einheit" -Prüfung, bei der jedem Speicherbit der Wert Eins zugewiesen wird und alle anderen Bits überprüft werden, um sicherzustellen, dass sie gleich Null sind. Während des Betriebs können andere bitweise Testmuster durchgeführt werden, vorausgesetzt, während der RAM-Bereich beschädigt ist, erfolgt keine Kontextumschaltung. Die Verwendung der Restriktionsmakros für kritische Abschnitte von
Nucleus SE NUSE_CS_Enter () und
NUSE_CS_Exit () ist recht einfach und skalierbar.
Verschiedene Arten von ROMs sind ebenfalls anfällig für periodische Fehler, aber es gibt nicht viele Tools, um sie zu überprüfen. Hier kann eine Prüfsumme hilfreich sein, die nach dem Zusammenstellen des Codes berechnet wird. Diese Überprüfung kann zur Startzeit und möglicherweise zur Laufzeit durchgeführt werden.
Ein Fehler in der Speicheradressierungslogik kann sowohl ROM als auch RAM betreffen. Sie können eine spezielle Prüfung für diesen Fehler entwickeln, die jedoch höchstwahrscheinlich im Rahmen der oben beschriebenen Prüfungen erkannt wird.
Peripheriegeräte testen
Neben der CPU können auch Peripherieschaltungen fehleranfällig sein. Dies ist natürlich von System zu System sehr unterschiedlich. Bei den meisten Geräten gibt es jedoch verschiedene Möglichkeiten, ihre Integrität mithilfe von Diagnosesoftware zu überprüfen. Beispielsweise kann ein Kommunikationskanal einen Loopback-Überprüfungsmodus haben, in dem alle in den Kanal kommenden Daten sofort zurückgegeben werden.
Watchdog-Service
Eingebettete Entwickler verwenden häufig einen Watchdog. Dies ist ein Peripheriegerät, das entweder die CPU unterbricht und auf eine Antwort wartet oder (bevorzugter) einen regelmäßigen Zugriff von der Software erfordert. In beiden Fällen ist ein häufiges Ergebnis eines Watchdog-Timers ein System-Reset.
Die effektive Verwendung eines Watchdogs in einer Multitasking-Umgebung ist ein komplexes Problem. Wenn Sie eine Aufgabe erstellen, die regelmäßig darauf zugreift (Watchdog-Timer), wird bestätigt, dass diese bestimmte Aufgabe funktioniert. Eine mögliche Lösung könnte die Implementierung der „Dispatcher-Aufgabe“ sein. Ein Beispiel für eine solche Aufgabe wird unten angegeben.
Stapelüberlaufprüfung
Wenn Sie den Run to Completion Scheduler nicht verwenden, wird für jede Aufgabe in der Nucleus SE-Anwendung ein Stapel erstellt. Die Integrität dieser Stapel ist sehr wichtig, aber die RAM-Größe ist wahrscheinlich begrenzt. Daher ist es wichtig, die Anwendungsgröße optimal zu gestalten. Die statische Vorhersage der Anforderungen für den Stapel jeder Aufgabe ist möglich, aber sehr schwierig. Der Stapel sollte groß genug sein, um selbst die am meisten verschachtelten Funktionen sowie den anspruchsvollsten Interrupt-Handler zu unterstützen. Ein einfacherer Ansatz zur Lösung dieses Problems wäre die Verwendung umfassender Laufzeitprüfungen.
Im Allgemeinen gibt es zwei Ansätze zur Stapelüberprüfung. Wenn Sie einen ausgeklügelten Debugger für eingebettete Software verwenden, können die Grenzen des Stapels überwacht und alle Verstöße erkannt werden. Die
Position und Größe der Nucleus SE-Stapel sind in den globalen ROM-Datenstrukturen verfügbar:
NUSE_Task_Stack_Base [] und
NUSE_Task_Stack_Size [] .
Eine Alternative ist das Testen der Laufzeit. Ein üblicher Ansatz besteht darin, "Schutzwörter" am Ende jedes Stapels zu verwenden, normalerweise das erste Element jedes Bereichs der Stapeldaten. Diese Wörter werden mit einem erkannten Wert ungleich Null initialisiert. Die Service- / Diagnoseaufgabe prüft dann, ob sich diese Wörter geändert haben, und führt die entsprechenden Aktionen aus. Das Mischen des Sicherheitsworts bedeutet nicht, dass der Stapel voll ist, sondern zeigt an, dass dies bald geschehen wird. Daher funktioniert die Software möglicherweise weiterhin. Möglicherweise müssen Sie Korrekturmaßnahmen ergreifen oder dem Benutzer einen Fehler melden.
Supervisor-Aufgabe
Trotz der Tatsache, dass Nucleus SE keine der 16 möglichen Aufgaben für seine eigenen Bedürfnisse reserviert, kann der Benutzer eine Aufgabe für die Diagnose auswählen. Dies kann eine Aufgabe mit niedriger Priorität sein, die einfach eine beliebige „freie“ Prozessorzeit verwendet, oder es kann eine Aufgabe mit hoher Priorität sein, die regelmäßig über einen kurzen Zeitraum ausgeführt wird, um sicherzustellen, dass die Diagnose regelmäßig durchgeführt wird.
Das Folgende ist ein Beispiel dafür, wie eine ähnliche Aufgabe funktionieren könnte.
Die Signalflags der Dispatcher-Task werden verwendet, um den Betrieb von sechs kritischen System-Tasks zu verfolgen. Jede dieser Aufgaben verwendet ein bestimmtes Flag (von Bit 0 bis Bit 5) und sollte es regelmäßig setzen. Die Dispatcher-Task setzt alle Flags zurück und pausiert dann ihre Arbeit für einen bestimmten Zeitraum. Wenn sie die Arbeit wieder aufnimmt, erwartet sie, dass alle sechs Aufgaben durch Setzen des entsprechenden Flags "überprüft" werden, und sucht dann nach einer genauen Übereinstimmung mit dem Wert von
b00111111 (aus der Datei
nuse_binary.h ). Wenn alles den Anforderungen entspricht, werden die Flags zurückgesetzt und erneut angehalten. Wenn nicht, wird die Routine zur Behandlung kritischer Fehler aufgerufen, die beispielsweise das System neu starten kann.
In einer alternativen Implementierung könnten Gruppen von Ereignisflags verwendet werden. Dies ist sinnvoll, wenn die Signale nicht an anderer Stelle in der Anwendung verwendet werden (andernfalls führt dies zu einer übermäßigen Verwendung des Arbeitsspeichers durch alle Aufgaben) und insbesondere, wenn die Ereignisflags für andere Zwecke verwendet werden.
Rückverfolgung und Profilerstellung
Trotz der Tatsache, dass viele moderne Embedded-Software-Debugger einen hohen Grad an Anpassung aufweisen und für die Arbeit mit RTOS verwendet werden können, kann das Debuggen einer Multithread-Anwendung immer noch schwierig sein. Ein weit verbreiteter Ansatz ist das Post-Execution-Profiling, bei dem der Code (RTOS) implementiert wird, damit eine detaillierte Prüfung seiner Arbeit im Nachhinein analysiert werden kann. Typischerweise umfasst die Implementierung eines solchen Dienstes zwei Komponenten:
- Dem RTOS wird ein zusätzlicher Code hinzugefügt, um Aktionen zu protokollieren. In der Regel wird es in Präprozessoranweisungen eingeschlossen, um die bedingte Kompilierung zu verwenden. Dieser Code zeichnet mehrere Informationsbytes auf, wenn ein wichtiges Ereignis eintritt (z. B. Aufrufen einer API-Funktion oder Wechseln des Kontexts). Zu diesen Informationen können gehören:
- aktuelle Adresse (PC);
- ID der aktuellen Aufgabe (Index);
- Indizes anderer verwendeter Objekte;
- Code, der der ausgeführten Operation entspricht.
- Die Aufgabe, die zum Entladen des Profilinformationspuffers in einen externen Speicher zugewiesen wird, normalerweise auf den Hostcomputer.
Die Analyse der so erhaltenen Daten erfordert ebenfalls einige Arbeit, ist jedoch nicht komplizierter als die Verwendung einer normalen Excel-Tabelle.
Im nächsten Artikel werden wir die Kompatibilität von Nucleus SE und Nucleus RTOS im Detail untersuchen.
Über den Autor: Colin Walls ist seit über dreißig Jahren in der Elektronikindustrie tätig und widmet sich die meiste Zeit der Firmware. Heute ist er Firmware-Ingenieur bei Mentor Embedded (einer Abteilung von Mentor Graphics). Colin Walls spricht häufig auf Konferenzen und Seminaren, Autor zahlreicher technischer Artikel und zweier Bücher über Firmware. Lebt in Großbritannien.
Colins professioneller
Blog , E-Mail: colin_walls@mentor.com.