Suchen

Wie Echtzeit-Software auch ohne Echtzeit-Betriebssystem entwickelt werden kann

Seite: 2/2

Firmen zum Thema

Vorsicht beim Hinzufügen von Interrupts

Das Abfragen von Hardware-Geräten stellt eine erhebliche Einschränkung dar. Die meisten I/O-Geräte sind heute interruptgesteuert. Doch auch interruptgesteuerte Geräte können Probleme verursachen, wenn die Software mit diesen Geräten nicht richtig zusammenarbeitet. Wenn eine Interrupt Service Routine Daten genau an die Task weiterleiten will, die sie gerade unterbricht, kann die Task die erhaltenen Daten oft nicht korrekt verarbeiten. Zum Beispiel kann es vorkommen, dass die Task mit der Verarbeitung der Daten beginnt und die ISR diese Daten dann aktualisiert. Die Task muss dann die Daten zum Weiterverarbeiten erneut lesen. Ein Teil der Daten wird also basierend auf alten Datenwerten verarbeitet und der Rest mit neuen Werten. Dies kann zu einer inkonsistenten Datenausgabe der jeweiligen Task führen.

Das zeitabhängige Verarbeiten inkonsistenter Daten bezeichnet man als “Race Condition” (Wettlaufsituation) – ein schwerwiegender Fehler bei Embedded-Software, denn er tritt scheinbar nur zufällig auf und ist zudem sehr kompliziert zu beheben. Weiteres Beispiel: Eine Task und ISR kommunizieren über eine gemeinsam genutzte Datentabelle. Wenn ein Interrupt auftritt und bearbeitet wird, während die Task gerade die Tabelle liest, erhält die Task möglicherweise alte und neue Daten aus verschiedenen Tabellenbereichen. Die alten und neuen Daten sind unter Umständen inkonsistent. Fehlerhafte Ergebnisse sind die Folge.

Bildergalerie
Bildergalerie mit 16 Bildern

Die Multirate-Executive mit Interrupts

Fallen bei der Interaktion zwischen ISRs und Tasks lassen sich umgehen, indem ISRs ihre Inputdaten in einen bestimmten Buffer schreiben und die Tasks Daten aus einem separaten Buffer holen. Bei jedem Tick (z.B. des Multirate-Executives für periodische Tasks) werden Interrupts deaktiviert, und Inputdaten werden aus dem ISR-Buffer in den Task-Buffer geschrieben. Dann werden die Interrupts wieder aktiviert, und die für diesen Tick vorgesehenen Tasks können ausgeführt werden.

So können ISRs Informationen ohne die Gefahr von Dateninkonsistenz an Tasks übermitteln. Die Interrupts werden wieder aktiviert, wenn die aktuellen Tasks der Applikation laufen. Diese Methode funktioniert, wenn die Ausführung aller geplanten Tasks vor dem nächsten Tick beendet wird. Dieser Scheduler-Typ ist komplex und sollte nicht einfach „nebenbei“ in Eigenregie entwickelt werden. Bei diesem Scheduler wird jede Task vollständig ausgeführt, ehe die nächste startet. Wie bei den vorherigen Executive-Typen können Tasks Informationen durch Schreiben und Lesen gemeinsam genutzter Daten untereinander weiterleiten.

Die Einschränkung, dass Hardware-Geräte abgefragt werden müssen, besteht hier nicht; auch Interruptsteuerung ist möglich. Dennoch ist zu gewährleisten, dass die Daten eines Interrupts, die eine ISR an die Software überträgt, für die weitere Verarbeitung nicht direkt an eine Task weitergeleitet werden. Erst nach dem nächsten Timer-Interrupt werden die ISR-Daten an die Task-Buffer übertragen, was bei manchen Anwendungen unerwünschte Verzögerungen oder Komplikationen verursacht.

Kommunikation zwischen Interrupt Service Routines und Tasks

Die meisten Peripheriegeräte in Embedded-Systemen sind interruptgesteuert. In vielen Embedded- und Echtzeit-Softwaresystemen müssen Daten von interruptgesteuerten Hardware-Geräten schnell über eine ISR an die Task-Software übertragen werden - ohne die Zeitverzögerungen, die in der oben beschriebenen Methode auftreten. Dafür stehen fünf Methoden zur Verfügung, mit denen sich zudem Datenschäden aufgrund von Race Conditions vermeiden lassen:

Hier teilen sich ISR und Task einen Datenbereich, wie in der folgenden Abbildung dargestellt. Für den Zugriff auf diesen Datenbereich muss die Task zuerst den Interrupt deaktivieren und danach wieder aktivieren. So wird sichergestellt, dass die ISR nicht eingreift, solange die Task auf die gemeinsamen Daten zugreift, und Datenschäden aufgrund von Race Conditions werden vermieden.

Falls die Hardware trotz der Deaktivierung versucht, Interrupts zu senden, können Interrupts und die zugehörigen Daten verloren gehen. Dies lässt sich mithilfe einer Hardware-Interrupt-Latch oder Interrupt-Queue vermeiden, die viele Prozessoren für Embedded-Anwendungen zur Verfügung stellen.

Gemeinsam von ISR und Task genutzter Datenbereich mit Update-Zählung

Wenn Interrupts nicht deaktiviert werden sollen, kann stattdessen eine Update-Zählfunktion (Update-Count) verwendet werden. Dies ist eine Variable innerhalb der gemeinsam genutzten Daten. Sobald die ISR Daten in den gemeinsam genutzten Bereich schreibt, erhöht sich dabei auch der Update-Count. Beim Lesen von Daten im gemeinsam genutzten Bereich liest die Task den Update-Count vor und nach dem Lesevorgang. Stimmen die beiden Update-Counts überein, heißt das, dass in der Zwischenzeit keine ISR ausgeführt wurde und somit die von der Task gelesenen Daten in Ordnung sind. Bei unterschiedlichen Update-Count-Werten muss die Task erneut ausgeführt werden und neue Daten aus dem gemeinsam genutzten Bereich lesen (und dabei wieder den Update-Count prüfen).

In dieser Methode werden keine Interrupts deaktiviert, Daten können dennoch verloren gehen, wenn mehrere Interrupts von Eingabegeräten eintreffen, während die Task versucht, im gemeinsam genutzten Datenbereich zu lesen.

Gemeinsam von ISR und Task genutzter Datenbereich mit Update-Bit

Die beschriebene Methode lässt sich verbessern, wenn der Update-Count durch ein Update-Bit ersetzt wird. Auf null zurückzusetzen ist umständlich. Wenn die ISR Daten in den gemeinsam genutzten Bereich schreibt, wird das Update-Bit auf 1 gesetzt. Zum Lesen in einem gemeinsamen Bereich muss eine Task vorher das Update-Bit auf 0 setzen. Nach Beenden des Lesevorgangs überprüft die Task, ob das Update-Bit noch auf 0 ist. In diesem Fall wurde zwischenzeitlich keine ISR ausgeführt und die gelesenen Daten sind in Ordnung. Enthält das Update-Bit eine 1, muss die Task erneut starten und neue Daten aus dem gemeinsam genutzten Datenbereich lesen (und dabei wieder das Update-Bit prüfen). Auch hier können Daten verloren gehen, z.B. wenn mehrere Interrupts von Eingabegeräten eintreffen, während die Task versucht, im gemeinsam genutzten Datenbereich zu lesen.

ISR-Task-Kommunikation: Weitere Methoden und Fragen

Es gibt noch mehr Methoden, um Daten über eine ISR schnell von interruptgesteuerten Hardware-Geräten an die Task-Software zu übertragen und dabei Zeitverzögerungen und Datenschäden aufgrund von Race Conditions zu vermeiden. Wenn ein Mikroprozessor in seiner Maschinensprache über einen atomaren „Test- und Set-Befehl“ verfügt, lässt sich damit ein binärer Semaphor wie in einem RTOS entwickeln.

Möglich ist auch ein Ringspeicher-System, bei dem die ISR eines Eingabegeräts in den einen Buffer schreibt, während eine Task Daten in einem anderen Buffer liest. Der Trick liegt darin, die Zeiger mit atomaren (unterbrechungsfreien) Operationen zu bewegen und die Lese- und Schreibzeiger zu voneinander getrennt zu halten (bzw. Abweichungen entsprechend zu erkennen).

Bildergalerie
Bildergalerie mit 16 Bildern

Die Grenze zwischen selbst entwickelten Lösungen und professionellen RTOS-Systemen

Die zwei zuletzt beschriebenen Methoden sind ähnlich komplex wie die Eigenentwicklung eines RTOS. Ehe Sie nun aber in den sauren Apfel beißen und bei verschiedenen Herstellern Informationen über deren RTOS einholen, stellen Sie sich erst diese Frage: „Wenn sich also ISR-Gerätedaten schnell in Datentabellen übertragen lassen, ist es dann immer noch sinnvoll, Tasks weiterhin in einer normalen, vorab definierten Sequenz auszuführen? Ist es wirklich erforderlich, dass eine bestimmte Task „aus der Reihe tanzt“ und jetzt sofort ausgeführt werden muss?“

Wenn die von der ISR gelieferten Daten wirklich auf der Stelle verarbeiten werden müssen, ist ein preemptiver Task-Scheduler vonnöten, den viele handelsübliche RTOS zur Verfügung stellen.

Kürzere Reaktionszeiten mit preemptivem Task-Scheduling

Die eingangs beleuchteten Task-Scheduler werden als „nicht-preemptiv“ bezeichnet, da ein Umschalten zwischen Tasks erst dann erfolgt, wenn eine Task vollständig ausgeführt wurde und die nächste Task ausgeführt werden soll (von Anfang an). In vielen Fällen lässt sich die Reaktionszeit mithilfe eines preemptiven Schedulers verkürzen, der jederzeit während der Ausführung einer Task ein Task-Switching ermöglicht (auch wenn die Ausführung der Task noch nicht abgeschlossen ist).

Bei einem Interrupt könnte die ISR zum Beispiel etwa so reagieren: „Mir ist egal, welche Task vor meinem Interrupt ausgeführt wurde. Den nächsten Timer-Tick will ich nicht abwarten. Ich will, das Task 67 jetzt sofort ausgeführt wird!“. Mit einem preemptiven Scheduler ist dies möglich.

Ein preemptiver Scheduler ist jedoch viel komplexer als ein nicht-preemptiver Scheduler. Zudem beansprucht ein solcher Scheduler sehr viel RAM-Kapazität für das Speichern von Task-Kontext und anderen Task-Statusinformationen. Es wäre sehr aufwändig, einen solchen Scheduler zu entwickeln, und die meisten standard RTOS-Systeme verfügen über preemptive Scheduler.

Mit einem preemptiven Scheduler lassen sich Hardware-Geräte abfragen oder über Interrupt steuern. Die von einem Interrupt gelieferten und an die Software übertragenen Informationen können zur weiteren Verarbeitung direkt an eine Task weitergeleitet werden, um eine kurze Reaktionszeit zu gewährleisten.

Vorsicht! Auch preemptive Scheduler verursachen manchmal Probleme

Preemptive Scheduler bieten mehr Vorteile und Funktionalität als einfachere, in Eigenregie entwickelte Scheduler. Doch ihre Komplexität birgt auch Probleme, deren sich der Softwareentwickler bewusst sein sollte. Das erste Problem: Welche Tasks dürfen eine bestimmte Task verdrängen, während diese ausgeführt wird? Die Lösung liegt darin, jeder Task eine Prioritätszahl zuzuordnen. Tasks mit höherer Priorität können Tasks mit niedrigerer Priorität verdrängen – nicht jedoch anders herum. Ein preemptiver Scheduler muss wissen, welche Priorität die nächste Task hat, die er schedulen kann.

Ein weiteres Problem ist, dass Tasks, die verdrängt werden und andere Tasks verdrängen können, Informationen nicht durch das Schreiben und Lesen gemeinsam genutzter Daten untereinander weiterleiten. Die einfachen Methoden der Datenweitergabe zwischen Tasks, die bei nicht-preemptiven Schedulern funktionieren, sind bei preemptiven Schedulern nicht möglich.

Bei einem preemptiven Scheduler gibt es ein Problem bei der Datenweitergabe zwischen Tasks durch Schreiben und Lesen gemeinsam genutzter Daten: Wenn eine Task eine andere Task verdrängt, wenn diese gerade eine gemeinsame Datentabelle liest, so liest diese andere Task in den verschiedenen Tabellenbereichen vielleicht alte und neue Daten, nachdem die erste (verdrängende) Task neue Daten in die Tabelle geschrieben hat. Diese Kombination alter und neuer Daten führt zu Dateninkonsistenz und teilweise zu fehlerhaften Ergebnissen

Dieses Problem ist im Wesentlichen dasselbe, das auftritt, wenn eine ISR und eine Task durch Schreiben und Lesen gemeinsam genutzter Daten miteinander kommunizieren wollen, wie oben beschrieben.

Ein Betriebssystem sollte über einen preemptiven Scheduler verfügen und Mechanismen zur Datenweitergabe zwischen den Tasks und an eine ISR /von einer ISR bereitstellen. Dafür bieten RTOS unterschiedliche Methoden an. Message-Queues verwendet eine Zählsemaphore, Event-Flags und asynchrone Signale. Bei Automobilanwendungen, die den OSEK-Standard erfüllen, gibt es „Ressourcen“, „Events“, „Alarm“ und „Nachrichten“. Betriebssysteme, die besonders für hohe Verfügbarkeit in verteilten Multiprozessor-Umgebungen ausgelegt sind, setzen bei der Task-Kommunikation wiederum auf Messages.

Wenn Informationen von einer Task zur anderen weitergegeben werden sollen, müssen diese Mechanismen eingesetzt werden. So ist eine sichere Datenweitergabe in einer preemptiven Umgebung gewährleistet.

* *David Kalinsky ist Leiter für Kundentraining bei D. Kalinsky Associates – Technical Training, einem Anbieter von Intensivseminaren für professionelle Embedded System- und Softwareentwickler.

Artikelfiles und Artikellinks

(ID:24232920)