Bild 1: Die neuseeländische Eisenbahn Ontrack implementiert ein Vier-Kanal-RoIP-Gerät, das als Bridge zwischen einem IP-Netzwerk und einem herkömmlichen VHF/UHF-Funknetzwerk dient.

Bild 1: Die neuseeländische Eisenbahn Ontrack implementiert ein Vier-Kanal-RoIP-Gerät, das als Bridge zwischen einem IP-Netzwerk und einem herkömmlichen VHF/UHF-Funknetzwerk dient.QNX Software Systems

Eine Loko­mo­tive des Typs GE Evolution beispielsweise nutzt 20 Systeme, die „laufend 2500 bis 5000 Parameter messen und prüfen, und das mit Daten­latenzen, die im Bereich von einigen Mikrosekunden bis zu einigen Sekunden variieren“. Und das ist nur die Spitze des Eisbergs. Eisenbahngesellschaf­ten implementieren zur Gewährleistung von Sicherheit und Effizienz Systeme wie die konventionelle Zugbeeinflussung (Automatic Train Protection – ATP), das US-amerikanische Zugleitsystem Positive Train Control (PTC) oder die funkbasierte Zugbeeinflussung (Communications-Based Train Control – CBTC), und im öffentlichen Personennahverkehr wird zunehmend auf vollautomatischen Zugbetrieb (Automated Train Operations – ATO) gesetzt.

Auf einen Blick

Zugsteuerungssysteme sind sicherheitsrelevant. Sie müssen die Verlässlich­keitsanforderungen aus der Norm IEC 61508 und der Normenreihe EN 5012x erfüllen. Dieser Artikel beleuchtet daher einige der Betriebs­system­eigenschaften, die sich besonders direkt auf die Verlässlichkeit des Systems auswirken. Die Erörterung könnte jedoch auf Themen wie Netzwerkstack, HMI-Technologien, Multicore-Unterstützung und die Verwendung zertifizierter COTS-Komponenten ausgeweitet werden.

Die Normenreihe EN 5012x

Vor zwei Jahrzehnten entwickelte das Europäische Komitee für elektrotech­ni­sche Normung (Cenelec) die Bahnanwendungsnormen EN 50126, EN 50128 und EN 50129. EN 50128 sagt aus:

  • Die Software-Architektur dient als Rahmen für die Entwicklung der grundlegenden Sicherheitsstrategie für die Software sowie deren Sicherheits-Integritätslevel (SIL).Wenn in Systemen, die SIL 3 oder SIL 4 benötigen, COTS-Software (Commercial off-the-shelf – Standardkomponenten) eingesetzt werden soll, so ist eine Strategie zu definieren, die Ausfälle der COTS-Software erkennt und das System vor solchen Ausfällen schützt.
  • EN 50128 sagt außerdem, dass kein Weg bekannt ist, auf dem sich ab einem gewissen Komplexitätsgrad die Fehlerfreiheit sicherheitsrelevanter Software beweisen ließe. Anders gesagt: Wenn wir ein sicheres System bauen, können wir nicht beweisen, dass das System keine Fehler enthält, sondern lediglich Belege liefern, die unsere Aussage untermauern, dass unser System so verlässlich ist, wie wir es von ihm behaupten.

Verlässlichkeit ist bei einem Softwaresystem eine Kombination aus Verfügbarkeit (das System reagiert rechtzeitig auf Anfragen) und Zuverlässigkeit (das System reagiert korrekt). Beides hängt vom Betriebssystem und seiner Architektur ab.

Betriebssystemarchitekturen

Sowohl für die Verlässlichkeit eines Systems als auch für seine Fähigkeit, Komponenten zu isolieren und zu schützen, ist die Architektur des Betriebssystems von grundlegender Bedeutung. Um Verlässlichkeit bieten zu können, sollte das Betriebssystem ein Echtzeitbetriebssystem (RTOS) sein, weil ein RTOS dafür ausgelegt ist, Verlässlichkeitsgarantien zu unterstützen. Dies sind die häufigsten Architekturen für Echtzeitbetriebssysteme:

  • Flache Architektur: Alle Komponenten (Kernel, Netzwerkstack, Dateisysteme, Treiber und Anwendungen) werden gemeinsam in einem einzigen Speicheradressraum ausgeführt. Dies ist effizient, jedoch kann ein einziger Zeigerfehler in einem Modul den vom Kernel oder einem anderen Modul verwendeten Speicher korrumpieren und einen systemweiten Ausfall verursachen, und das System kann abstürzen, ohne Diagnoseinformationen zu hinterlassen.
  • Monolithischer Kernel mit Speicherschutz: Applikationskomponenten laufen als speichergeschützte Prozesse. Der Kernel ist vor Applikationscode geschützt, aber die Kernelkomponenten teilen sich ein und denselben Adressraum mit Dateisystemen, Protokollstacks und Treibern. Ein Fehler in einem dieser Dienste kann das ganze System außer Betrieb setzen.
  • Mikrokernel: Anwendungen, Gerätetreiber und Netzwerkstack befinden sich außerhalb des Kernels in separaten Adressräumen, die gegen den Kernel sowie untereinander isoliert sind. Ein Fehler kann sich nicht durch das gesamte System ausbreiten, und das System kann ausgefallene Komponenten neu starten.

Isolierung von Komponenten

In sicherheitsrelevanten Systemen müssen gegebenenfalls Komponenten benutzt werden, die unterschiedliche Sicherheitsintegritätslevels benötigen. Zum Beispiel kann ein ATO-System bei geeigneter Isolierung eine Komponente mit SIL 0 benutzen, um nicht kritische Informationen auf einem Bildschirm anzuzeigen, aber die Komponenten für die Kommunikation mit der streckenseitigen Infrastruktur benötigen eine Zertifizierung nach SIL 3.

Eine Betriebssystemarchitektur, die den Kernel, Betriebssystem­komponen­ten und Anwendungen voneinander isoliert, bietet diverse Vorteile:

  • Sie vereinfacht das Systemdesign, da zum Beispiel für die SIL-0-Komponente COTS-Software verwendet werden kann.
  • Sie spart Kosten, da Komponenten, die nicht kritisch sind, in diesem Fall nicht nach den Anforderungen eines unnötig hohen SIL entworfen, implementiert und validiert werden müssen.
  • Sie führt zu mehr Sicherheit, da das sicherheitsrelevante System kleiner wird und sich die Entwickler so auf die wirklich kritischen Teile des Systems konzentrieren können.

Isolierung lässt sich auch mit einer virtuellen Maschine (einem Hypervisor) erreichen; eine solche Strategie benötigt jedoch in der Regel leistungsfähige Prozessoren, erhöht die Komplexität und kann die Echtzeitfähigkeit beeinträchtigen.

Weitere Eigenschaften, die ein verlässliches Echtzeitbetriebssystem aufweisen muss: Präemption von Kernel-Aufrufen mit niedrigerer Priorität, Vermeidung von Prioritätsinversion, Vermeidung von Ressourcenmangel bei kritischen Prozessen, Überwachung von Prozessen, Neustarten ausgefallener Komponenten oder Wechsel des Systems in den Design Safe State.

Präemptierbarer Kernel

Wenn bei einem Betriebssystem Applikationsthreads mit höherer Priorität darauf warten, dass ein Kernel-Aufruf mit niedrigerer Priorität endet, kann dies zu unvorhersehbaren Verzögerungen führen. Solche Verzögerungen können dazu führen, dass kritische Aktivitäten nicht rechtzeitig erfolgen, was die Verlässlichkeit beeinträchtigt. Die Lösung liegt in der Verwendung eines präemptierbaren Kernels. Ein solcher Kernel legt eine obere Grenze dafür fest, wie lange die Präemption aufgeschoben werden kann, so dass die Entwickler die schlimmstmöglichen Latenzen ermitteln und beim Design berücksichtigen können.

Ferner muss der Kernel, um derartige Verzögerungen zu vermeiden, so einfach wie möglich gehalten werden. Es muss eine klare Obergrenze für den längsten nicht unterbrechbaren Codepfad durch den Kernel geben. Der beste Weg, diese Art von Einfachheit zu erreichen, besteht darin, in den Kernel nur Dienste mit kurzen Ausführungspfaden aufzunehmen und alle arbeitsintensiven Operationen in externe Threads auszulagern.

Prioritätsvererbung

Bild 2: Eine klassische Prioritätsinversion.

Bild 2: Eine klassische Prioritätsinversion.QNX Software Systems

Wenn ein Thread mit niedrigerer Priorität verhindert, dass ein Thread mit höherer Priorität seine Arbeit fertigstellen kann, spricht man von Prioritätsinversion. Viele Projekte haben unter diesem Problem gelitten; ein besonders bekanntes Beispiel ist der Mars Pathfinder 1997.

Bild 2 zeigt, wie ein Thread mit niedrigerer Priorität einen Thread mit höherer Priorität blockiert. Dies kann durch die Synchronisierung von Res­sour­cenzugriffen verursacht werden, wenn zum Beispiel der Alarm und der Proto­kollierer gemeinsam eine Ressource benutzen, wobei der Zugriff auf diese Ressource durch einen Lock oder eine Semaphore serialisiert wird, und der Alarm darauf wartet, dass der Protokollierer die Ressource freigibt. Oder der Alarm könnte einen Dienst anfordern, den der Protokollierer gerade benutzt.

Bei diesem Beispiel unterbricht der Datensammler den niederprioren Proto­kollierer. Der Datensammler benötigt die vom Protokollierer benutzte Res­sour­ce nicht, so dass sie im Zugriff des Protokollierers verbleibt. Wenn daraufhin der Alarm ausgeführt werden soll, unterbricht er den Daten­samm­ler, kann jedoch nicht auf die Ressource zugreifen, die sich noch im Zugriff des Protokollierers befindet, und blockiert. Da der Alarm blockiert ist, sucht der Scheduler nach dem Thread mit der nächstniedrigeren Priorität, der ausgeführt werden kann, und führt den Datensammler aus, wodurch die Prioritätsreihenfolge der Threads invertiert wird.

Bild 3: Bei Prioritätsvererbung wird der hochpriore Thread nicht blockiert.

Bild 3: Bei Prioritätsvererbung wird der hochpriore Thread nicht blockiert.QNX Software Systems

Prioritätsvererbung ist eine Technik, bei der die Priorität eines blockierten Threads mit hoher Priorität so lange dem niederprioren Thread zugewiesen wird, bis der blockierende Thread abgeschlossen ist. Bild 3 zeigt das obi­ge Beispiel bei einem System mit Prioritätsvererbung. Der Protokollierer erbt die Priorität des Alarms und kann daher von dem Datensammler nicht unter­bro­chen werden. Er schließt seine Arbeit ab und erhält wieder die ursprüng­liche Priorität. Daraufhin wird der Alarm freigegeben und kann fortfahren.

Zeitpartitionierung

Ein System kann seine Dienste möglicherweise nicht korrekt bereitstellen, wenn eines seiner Subsysteme unter Mangel an CPU-Zyklen leidet. Zeitpartitionierung ist eine Technik, die die Einhaltung von CPU-Zeitbudgets erzwingt und verhindert, dass sich Prozesse CPU-Zyklen unter den Nagel reißen, die von anderen Prozessen benötigt werden.

Bei der statischen Partitionierung werden Prozesse in Partitionen gruppiert. Jeder Partition wird ein Anteil an CPU-Zyklen zugewiesen, und kein Thread darf mehr als die seiner Partition zugeteilten Zyklen verbrauchen. Dadurch, dass jeder Partition ein Anteil an CPU-Zyklen garantiert wird, wird sichergestellt, dass alle wichtigen Prozesse immer laufen können. Nachteilig ist jedoch, dass kein Prozess mehr CPU-Zyklen benutzen kann, als seiner Partition zugeteilt sind, selbst wenn andere Partitionen inaktiv sind. Eine statische Partitionierung vergeudet somit CPU-Zyklen und beeinträchtigt die Fähigkeit eines Systems zum Umgang mit Spitzenlasten.

Bild 4: Adaptive Partitionierung schützt bestimmte Threads und Threadgruppen. Die Zahlen geben die Threadprioritäten an.

Bild 4: Adaptive Partitionierung schützt bestimmte Threads und Threadgruppen. Die Zahlen geben die Threadprioritäten an.QNX Software Systems

Bei der adaptiven Partitionierung werden genau wie bei der statischen Partitionierung CPU-Zyklen für einen Prozess oder eine Gruppe von Prozessen reserviert, jedoch arbeitet das Scheduling dynamisch, das heißt: Nicht benötigte Zyklen werden Partitionen zugeteilt, die diese im Moment gebrauchen können, und Budgets werden nur erzwungen, wenn die CPU mit voller Last läuft. Bei adaptiver Partitionierung können sogar im Voraus Kriterien bestimmt werden, anhand derer die Zeitbudgets im laufenden Betrieb angepasst werden. So könnten beispielsweise 30 Prozent der CPU-Zyklen dem Bremssystem zugeteilt werden, wenn die Geschwindigkeit unter 20 km/h liegt, und 45 Prozent bei höheren Geschwindigkeiten.

Software-Watchdog

Systeme, bei denen Verfügbarkeitsgarantien erforderlich sind, können sowohl hardwareorientierte Hochverfügbarkeitslösungen implementieren als auch einen Software-Watchdog, der verschiedene Operationen durchführen kann, die geeignet sind, die Verlässlichkeit des Systems zu gewährleisten und gegebenenfalls Wiederherstellungsmaßnahmen einzuleiten:

  • Abbrechen und Neustarten ausgefallener Prozesse, um einen Systemneustart zu vermeiden.
  • Abbrechen eines ausgefallenen Prozesses und zugehöriger Prozesse, Initialisieren der Hardware in einen sicheren Zustand und koordiniertes Neustarten der Prozesse.
  • Kontrolliertes Herunterfahren oder Zurücksetzen des Systems und Signalisierung eines Alarms bei kritischen Ausfällen.

Auf jeden Fall muss der Watchdog selbstüberwachend und widerstandsfähig gegenüber internen Ausfällen sein. Terminiert er unerwartet, muss er unverzüglich seinen eigenen Zustand rekonstruieren, indem er die Kontrolle an einen Spiegelprozess abgibt.

Schließlich kann ein Software-Watchdog das Auftreten von System­ereignis­sen überwachen, die für einen herkömmlichen Hardware-Watchdog nicht sichtbar sind. Zum Beispiel kann ein Hardware-Watchdog sicherstellen, dass ein Treiber die Hardware bedient, er kann aber nicht erkennen, ob andere Programme korrekt mit dem Treiber kommunizieren. Ein Software-Watchdog kann diese Lücke füllen und handeln, wenn er eine interne Anomalie feststellt.