Suchen

Echtzeitbetriebssysteme – Einführung und Konzepte

Autor / Redakteur: David Kalinsky * / Sebastian Gerstl

Echtzeitbetriebssysteme arbeitem unter knallharten Bedingungen: Begrenzte Ressourcen, untypische Schnittstellen, strikte Anforderungen an die Bereitstellung von Tasks. Diese Einführung erklärt die wesentlichen Konzepte des Herzstücks vieler Embedded-Systeme.

Firmen zum Thema

In einem Embedded-System muss das verwendete Betriebssystem unter knappen Ressourcen harte Echtzeit-Anforderungen erfüllen können, Ein "normales" OS reicht hier oft aus diversen Gründen nicht aus.
In einem Embedded-System muss das verwendete Betriebssystem unter knappen Ressourcen harte Echtzeit-Anforderungen erfüllen können, Ein "normales" OS reicht hier oft aus diversen Gründen nicht aus.
(Bild: Clipdealer)

Echtzeit- und Embedded-Systeme laufen in Umgebungen, in denen nur begrenzte Rechenspeicherkapazität und Verarbeitungsressourcen zur Verfügung stehen. Ihre Dienste müssen sie oft innerhalb einer streng definierten Zeitspanne (Deadline) erbringen. Diese Einschränkung hinsichtlich der Speicherkapazität und die Anforderungen an Geschwindigkeit und Zeit machen den Einsatz von Echtzeitbetriebssystemen (engl. Real-time Operating System, RTOS) in Embedded-Software erforderlich.

Bei Embedded-Systemen ist oft nicht direkt ersichtlich, dass es sich um Rechner handelt. Sie verrichten ihre Arbeit verborgen im Inneren der verschiedensten Gegenstände um uns herum, die uns den Alltag erleichtern. Mit der Außenwelt sind Embedded-Systeme meist nicht über gängige Rechnerschnittstellen, sprich, Maus, Tastatur oder Benutzeroberfläche verbunden, sondern über eher untypische Schnittstellen, wie Sensoren, Aktuatoren und spezielle Kommunikationslinks.

Grundlegende Kernel-Services von Echtzeitbetriebssystemen

Nachfolgend betrachten wir den Kernel (Kern), also den Teil eines Betriebssystems, der die wichtigsten Dienste für die Applikationssoftware auf einem Prozessor erbringt.

Der Kernel eines Echtzeitbetriebssystems bildet eine „Abstraktionsschicht“, die die Hardwaredetails des Prozessors (bzw. der Prozessoren), auf denen die Applikationssoftware ausgeführt wird, vor dieser Software verbirgt, wie in Bild 1 gezeigt. Der RTOS-Kernel stellt diese „Abstraktionsschicht“ bereit und bietet der Applikationssoftware wichtige Services an. Diese lassen sich in fünf Hauptkategorien einteilen, wie sie in Bild 2 zu sehen sind.

Die wichtigste Kernel-Service-Kategorie, im Bild 2 mittig dargestellt, ist das Task-Management. Diese Services erlauben es dem Software-Entwickler, seine Software als verschiedene „Einheiten“ zu entwerfen. Jede Einheit ist für einen bestimmten Bereich bzw. ein bestimmtes Ergebnis zuständig und hat u.U. eine eigene Echtzeit-Deadline. Diese Einheiten sind die sogenannten „Tasks“ (Aufgaben). Über die Services dieser Kategorie lassen sich Tasks starten und mit einer Priorität versehen. Der wichtigste RTOS-Service ist dabei das Scheduling von Tasks, während das Embedded-System in Betrieb ist. Der Task-Scheduler steuert die rechtzeitige und korrekte Ausführung der Applikationssoftware-Tasks. Wie das genau funktioniert, wird später erklärt.

Die zweite Kategorie der Kernel-Services, im Bild 2 oben dargestellt, ist die Intertask-Kommunikation und -Synchronisation. Damit können Tasks untereinander Daten weiterleiten. Zudem sorgen diese Services dafür, dass sich die Tasks untereinander abstimmen und effizient zusammenwirken. Auch verhindern sie, dass die Daten, die die Tasks austauschen, beschädigt werden oder dass sich die Tasks gegenseitig behindern.

Bild 2: Ein RTOS- Kernel stellt diese wesentlichen Services bereit.
Bild 2: Ein RTOS- Kernel stellt diese wesentlichen Services bereit.
(Bild: Kalinsky Associates)

Für viele Embedded-Systeme gelten strenge Timing-Anforderungen, daher bieten die meisten RTOS-Kernels wichtige Timer-Services an, z.B. Task-Delay und Timeouts; diese sind im Bild 2 rechts dargestellt.

Viele RTOS-Kernels (nicht alle) stellen auch Services wie dynamische Speicherallokation bereit. In dieser Service-Kategorie können die Tasks vorübergehend bestimmte RAM-Bereiche in der Applikationssoftware nutzen. Diese Speicherbereiche werden auch von Task zu Task weitergegeben, so dass sich große Datenmengen zwischen den Tasks kommunizieren lassen. Manche sehr kleine RTOS-Kernels für extrem speicherlimitierte Umgebungen bieten die dynamische Speicherallokation nicht an.

Viele RTOS-Kernels haben darüber hinaus eine Servicekategorie “Device I/O Supervisor”. Diese Services stellen ein einheitliches Framework für die Organisation und den Zugriff auf die Hardwaredevice-Treiber bereit, die in den meisten Embedded-Systemen zum Einsatz kommen.

Zusätzlich zu den Kernel-Services bieten viele RTOS-Systeme optionale Add-on-Betriebssystemkomponenten für komplexere Services wie Filesystem-Organisation, Netzwerk-Kommunikation, Datenbankmanagement, Oberflächengrafik etc. an. Diese Add-on-Komponenten sind zwar oft viel größer und komplexer als der RTOS-Kernel, sind aber dennoch vom RTOS-Kernel abhängig und machen sich dessen wesentliche Services zunutze. Eine solche Komponente kommt nur dann in einem Embedded-System zum Einsatz, wenn seine Services erforderlich sind, um eine Embedded-Applikation zu implementieren, so dass möglichst wenig Programmspeicher verbraucht wird.

Dieser Beitrag beleuchtet im weiteren die wesentlichen RTOS-Kernel-Services für das Task-Management, die Intertask-Kommunikation und -Synchronisation sowie die dynamischen Speicherallokation.

Echtzeit- vs. "Standard"-Betriebssystem: Determinismus

Auch viele „normale“ Betriebssysteme (die allerdings keine Echtzeitbetriebssysteme sind) bieten solche Kernel-Services an. Der wesentliche Unterschied zwischen normalen Betriebssystemen und Echtzeitbetriebssystemen liegt darin, dass letztere ein deterministisches Zeitverhalten aufweisen müssen.

Deterministisch bedeutet, dass die Betriebssystem-Dienste jeweils nur eine bekannte und erwartete Zeit in Anspruch nehmen. Diese Dienste ließen sich theoretisch als mathematische Formeln darstellen, die strikt algebraisch sein müssen und keine zufälligen Timingkomponenten enthalten dürfen. Zufallselemente im Service-Timing verursachen u.U. Zufallsverzögerungen in der Applikationssoftware; die Applikation würde dann die Echtzeit-Anforderungen nicht erfüllen. In Embedded-Echtzeit-Systemen ist ein solches Szenario nicht akzeptabel.

Normale Betriebssysteme sind oft nicht deterministisch. Ihre Services können Zufallsverzögerungen in der Applikationssoftware verursachen, und die Reaktionen dieser Applikation sind dann langsamer und zeitlich nicht präzise. Wenn Sie den Entwickler eines normalen Betriebssystems (z.B. Windows, Unix oder Linux) nach der algebraischen Formel fragen, die das Zeitverhalten einer Betriebssystem-Komponente beschreibt (z.B. das Senden einer Message von einer Task zur anderen), wird er ihnen diese Formel nicht nennen können - er wirft Ihnen höchstens einen fragenden Blick zu. Deterministisches Zeitverhalten ist bei normalen Betriebssystemen nun mal kein Designziel.

Echtzeitbetriebssysteme bieten häufig mehr als nur einfachen Determinismus, sondern ermöglichen den meisten Kernel-Services ein konstantes, lastenunabhängiges Timing. Die algebraische Formel ist entsprechend einfach: T(message_send) = constant, unabhängig von der Länge der zu sendenden Message oder anderen Faktoren, z.B. der Anzahl an Tasks, Queues und Messages, die das RTOS verwaltet.

Bild 3: Zeitachse für prioritätsbasiertes präemptives Scheduling anhand von Beispielen.
Bild 3: Zeitachse für prioritätsbasiertes präemptives Scheduling anhand von Beispielen.
(Bild: Kalinsky Associates)

Task Scheduling in Echtzeitbetriebssystemen

Bei den meisten RTOS-Systemen basiert das Task-Scheduling auf dem sogenannten prioritätsbasierten präemptiven Scheduling. Jeder Task in einer Software-Applikation ist eine Priorität zuzuweisen. Eine höhere Priorität bedeutet, dass eine schnellere Reaktion erforderlich ist. Durch das präemptive Task-Scheduling wird eine sehr schnelle Reaktion sichergestellt.

Präemptiv bedeutet, dass der Scheduler eine gerade laufende Task an jeder Stelle anhalten kann, wenn er erkennt, dass eine andere Task sofort ausgeführt werden muss.

Die Grundregel, auf der das prioritätsbasierte präemptive Scheduling basiert, besagt, dass die ausführbereite Task mit der höchsten Priorität immer die Task ist, die ausgeführt werden muss. Wenn also sowohl eine Task mit niedriger als auch eine Task mit höherer Priorität ausführbereit sind, sorgt der Scheduler dafür, dass zuerst die Task mit der höheren Priorität läuft. Die Task mit niedrigerer Priorität wird erst dann ausgeführt, wenn die höherpriore Task verarbeitet wurde.

Wie wird vorgegangen, wenn eine höherpriore Task bereit wird, die Ausführung einer niederprioren Task aber schon begonnen hat? Dieser Fall könnte z.B. eintreten, wenn ein externer Schalter geschlossen wird. Ein prioritätsbasierter präemptiver Scheduler gestattet in diesem Fall der niederprioren Task die Ausführung des aktuellen Assemblerbefehls (jedoch nicht die Fertigstellung einer ganzen Hochsprachencodezeile oder das Weiterlaufen bis zum nächsten Timer Tick). Danach hält der Scheduler sofort die Ausführung der niederprioren Task an und lässt die höherpriore Task ablaufen.

Wenn die höherpriore Task fertig abgelaufen ist, kann die niederpriore Task weiter ausgeführt werden. Dies wird in Bild 3 dargestellt; die höherpriore Task wird hier als „Mid-Priority Task“ (Task mit mittlerer Priorität) bezeichnet.

Es kann natürlich auch vorkommen, dass die mittelpriore Task noch abläuft und dabei eine Task mit noch höherer Priorität bereit wird. Im Bild 3 ist dies dargestellt als „Trigger_2“, aufgrund dessen die hochpriore Task bereit wird. In diesem Fall würde die gerade laufende Task (also die "Mid-Priority Task") verdrängt, und die Ausführung der hochprioren Task wird ermöglicht. Nach Beendigung der hochprioren Task kann die mittelpriore Task weiter ausgeführt werden. Dieses Szenario wird auch als „verschachtelte Präemption“ bezeichnet.

Wenn der prioritätsbasierte präemptive Scheduler über einen externen Trigger (z.B. Schalter, der geschlossen wird) oder Software-Trigger aktiviert wird, muss er die folgenden 5 Schritte ausführen:

  • Feststellen, ob die gerade ablaufende Task weiterlaufen soll; andernfalls …
  • Feststellen, welche Task als nächstes ausgeführt werden soll
  • Umgebung der Task speichern, die angehalten wurde (damit sie später weiter ausgeführt werden kann)
  • Ablaufumgebung für die als nächstes auszuführende Task bereitstellen
  • Ausführen dieser Task

Dieses Vorgehen wird auch als Task Switching bezeichnet.

Task Switching mit vorgegebenem Timing

Bild 4: Zeitverhalten beim Task Switching.
Bild 4: Zeitverhalten beim Task Switching.
(Bild: Kalinsky Associates)

Die Zeit, die das Task-Switching in Anspruch nimmt, ist bei der Evaluierung eines Betriebssystems zu berücksichtigen. Manche einfachen Betriebssysteme (nicht präemptiv) führen das Task-Switching nur bei Timer-Ticks aus; diese können z.B. 10 Millisekunden auseinanderliegen. Ist ein Task-Switch innerhalb dieser Zeitspanne von 10 Millisekunden erforderlich, würde er erst am Ende der aktuellen Zeitspanne stattfinden. Eine solche Verzögerung wäre bei den meisten Embedded-Echtzeit-Systemen nicht akzeptabel.

Komplexere präemptive Task-Scheduler müssen häufig Task-Arrays durchsuchen, um festzustellen, welche Task als nächstes auszuführen ist. Je mehr Tasks zu durchforsten sind, desto länger dauert die Suche. Die meisten normalen Betriebssysteme gehen so vor und sind daher nicht deterministisch.

Echtzeitbetriebssysteme dagegen vermeiden solche Suchvorgänge und verwenden stattdessen inkrementell aktualisierte Tabellen, anhand derer der Task-Scheduler erkennt, welche Task als nächstes auszuführen ist. Diese beiden unterschiedlichen Zeitverhalten werden im Bild 4 dargestellt.

Die notwendige Zeit beim Task Switching nimmt zu, je mehr zu planende Tasks ein Softwaresystem enthält. Die tatsächlich für ein Task-Switch erforderliche Zeit ist jedoch nicht die Zeit, die mit der roten Strichlinie dargestellt wird. Sie könnte bei anderen Task-Switch-Vorgängen auch darüber oder darunter liegen. Die schattierten Bereiche um die rote Strichlinie herum zeigen nur an, mit welcher Wahrscheinlichkeit die tatsächliche Task-Switch-Zeit so weit über oder unter der roten Strichlinie liegt.

Die gerade grüne Linie dagegen stellt die Eigenschaft der Task-Switch-Zeit in einem Echtzeitbetriebssystem dar. Sie ist konstant und unabhängig von Lastfaktoren, z.B. Anzahl von Tasks in einem Softwaresystem.

In manchen Fällen, wie z.B. im linken Bereich der Grafik, kann die Task-Switch-Zeit bei normalen Betriebssystemen auch schneller sein als bei Echtzeitbetriebssystemen. Trotzdem eignen sich Echtzeitbetriebssysteme besser für den Einsatz in Embedded-Echtzeit-Applikationen. Letztlich steht der Begriff „Echtzeit“ nicht für „schnellstmöglich“. „Echtzeit“ erfordert stattdessen ein konsistentes, wiederholbares und bekanntes Zeitverhalten. Bei einer kleinen Anzahl an Tasks führt ein normales Betriebssystem den Task-Switch vielleicht schneller aus, doch kann schon beim nächsten Ausführen desselben Task-Switch eine längere zeitliche Verzögerung auftreten.

Der Vorteil eines Echtzeitbetriebssystems ist seine bekannte und reproduzierbare Timing-Performance, die meist auch schneller ist als die eines nicht-deterministischen Task-Schedulers, wenn sich viele Tasks in einem Software-System befinden. In den meisten Fällen sind die Task-Switch-Zeiten eines Echtzeitbetriebssystems viel schneller als in einem normalen Betriebssystem, wenn mehr als 5 oder 10 Tasks vorliegen.

Bild 5: Message-Kommunikation zwischen Tasks
Bild 5: Message-Kommunikation zwischen Tasks
(Bild: Kalinsky Associates)

Intertask-Kommunikation und -Synchronisation in Echtzeitbetriebssystemen

Die meisten Betriebssysteme, auch Echtzeitbetriebssysteme, bieten verschiedene Mechanismen zur Kommunikation und Synchronisation zwischen Tasks an. Diese Mechanismen sind in einer präemptiven Umgebung mit vielen Tasks erforderlich, ansonsten könnten die Tasks beschädigte Daten kommunizieren oder sich gegenseitig behindern.

Beispielsweise könnte eine Task verdrängt werden, während sie gerade eine Datentabelle aktualisiert. Wenn die andere Task, welche die erste Task verdrängt hat, dann die Tabelle liest, greift sie auf Bereiche zu, die gerade aktualisiert wurden, sowie auf Bereiche, die noch nicht aktualisiert wurden. Dies könnte zu inkorrekten oder nicht plausiblen Ergebnissen führen. Beispiel: Eine Datentabelle mit Temperaturmessungen beginnt mit dem Inhalt „10 C”.

Eine Task beginnt, diese Tabelle zu aktualisieren und schreibt den neuen Wert „99 F“ Zeichen für Zeichen in die Tabelle. Wird diese Task mitten in der Aktualisierung verdrängt, könnte die verdrängende Task dann einen Wert wie „90 C“, „99 C“ oder „99 F“ lesen, je nachdem, an welcher Stelle die erste Task verdrängt wurde. Die teils aktualisierten Werte sind definitiv nicht korrekt und basieren auf komplexen zeitlichen Koinzidenzen, die sich schwer debuggen bzw. konsistent reproduzieren lassen.

In einem RTOS stehen Mechanismen zur Kommunikation und Synchronisation zwischen Tasks zur Verfügung, um solche Fehler zu vermeiden. Die meisten RTOS bieten mehrere Mechanismen an, wobei jeder auf die zuverlässige Übertragung unterschiedlicher Informationen zwischen Tasks optimiert ist.

Die wohl gängigste Kommunikationsmethode zwischen Tasks in Embedded-Systemen ist die Weitergabe von Daten von einer Task an eine andere. Die meisten RTOS bieten hierfür einen Weitergabemechanismus für Messages an, wie im Bild 5 gezeigt. Jede Message kann ein Daten-Array oder gepufferte Daten enthalten.

Bild 6: Message-Weitergabe über eine Message Queue
Bild 6: Message-Weitergabe über eine Message Queue
(Bild: Kalinsky Associates)

Wenn Messages schneller übertragen werden, als sie verarbeitet werden können, stellt das RTOS Message Queues (Warteschlangen) bereit, in denen die Messages auf ihre Verarbeitung warten, wie in Bild 6 gezeigt.

Eine weitere Art der Kommunikation zwischen Tasks in Embedded-Systemen ist die Weitergabe sogenannter „Synchronisationsinformationen“ von einer Task an eine andere. Eine Synchronisationsinformation ist wie ein Befehl; manche Befehle sind positiv und andere negativ. Ein negativer Befehl an eine Task wäre beispielsweise „Bitte jetzt nicht drucken, da meine Task gerade den Drucker verwendet“ bzw. allgemeiner: „Ich will … für den eigenen Gebrauch blockieren“. Ein positiver Befehl könnte lauten: „Ich habe einen Kardionotfall erkannt und bitte um Hilfe bei der Behandlung des Notfalls“ bzw. allgemeiner: „Ich bitte um Unterstützung bei der Behandlung von …“.

Die meisten RTOS bieten einen Semaphore- oder Mutex-Mechanismus zur Behandlung negativer Synchronisation an (auch „Mutual Exclusion“ bzw. wechselseitiger Ausschluss genannt). Mit diesen Mechanismen können Tasks bestimmte Embedded-Systemressourcen für den Eigengebrauch blockieren und nach Fertigstellung wieder freigeben.

Für die positive Synchronisation stehen je nach RTOS verschiedene Mechanismen zur Verfügung, darunter Event Flags oder Signale bzw. die Message- oder Daten-Weitergabe.

Determinismus und schnelle Message-Weitergabe zwischen RTOS-Tasks

Auch bei der Message-Kommunikation zwischen Tasks weisen unterschiedliche Betriebssysteme ein unterschiedliches Zeitverhalten auf. Bei den meisten Betriebssystemen werden Messages zweimal kopiert, wenn sie von Task zu Task über eine Message Queue weitergegeben werden (siehe Bild 6). Im ersten Kopiervorgang wird die Message aus der Message-Sender-Task in einen „geheimen“ RAM-Bereich des Betriebssystems kopiert (Implementieren der Message Queue); dann wird die Message aus diesem „geheimen“ RAM-Bereich kopiert und an die Message-Receiver-Task übermittelt. Das Timing ist dabei nicht deterministisch, denn je länger die Messages sind, desto länger dauern die Kopiervorgänge.

Um diesen nicht-deterministischen Ansatz zu vermeiden und die Performance zu beschleunigen, kann das Betriebssystem einen Pointer (Zeiger) auf die Message kopieren und diesen Pointer an die Message-Receiver-Task übermitteln, ohne dass der eigentliche Message-Inhalt bewegt wird. Um Zugriffskollisionen zu vermeiden, muss das Betriebssystem dann zurück zur Message-Sender-Task gehen und dort die Kopie des Pointers auf die Message löschen. Bei großen Messages entfallen damit zeitaufwändige Kopiervorgänge, und ein deterministisches Zeitverhalten wird sichergestellt.

Dynamische Speicher-Allokation in Echtzeitbetriebssystemen

Bild 7: Speicherpool – Liste der verfügbaren Puffer
Bild 7: Speicherpool – Liste der verfügbaren Puffer
(Bild: Kalinsky Associates)

Auch im Bereich der dynamischen RAM-Allokation spielt das deterministische Zeitverhalten eine wichtige Rolle. Bei vielen normalen Betriebssystemen wird Speicher aus einem Heap zugewiesen. Die Services „malloc“ und „free“, bekannt aus der C-Programmierung, arbeiten mit einem Heap. Über den Aufruf von „malloc“ kann eine Task Speicherbereiche aus dem Betriebssystem-Heap temporär nutzen und die erforderliche Speichergröße definieren.

Wenn diese (oder eine andere) Task den Inhalt des entsprechenden Speichers abgearbeitet hat, kann sie den Speicher über den Aufruf von „free“ an das Betriebssystem zurückgeben. Das Betriebssystem gibt den Speicher an den Heap zurück, wo er z.B. als Teil eines großen Puffers wiederverwendet oder später in mehrere kleinere Puffer aufgeteilt werden kann.

Die bei Heaps auftretende „externe Speicherfragmentierung“ kann die Leistung der Heap-Services beeinträchtigen: Ein Puffer, der dem Heap zurückgegeben wurde, wird möglicherweise in kleinere Puffer aufgebrochen, sobald über einen „malloc“-Aufruf kleinere Puffergrößen anfordert werden. Ein Heap, der bereits mehrere malloc- und free-Zyklen durchlaufen hat, weist häufig kleinere Speicherabschnitte zwischen den Pufferspeicherbereichen auf, die von den Tasks verwendet werden.

Diese Abschnitte sind so klein, dass sie für die Tasks nicht nutzbar sind. Jedoch stecken sie zwischen den Puffern fest, die von den Tasks verwendet werden, und lassen sich deshalb nicht in zusammenhängende Puffer mit nutzbarer Größe zusammenfügen. Mit der Zeit hat ein Heap immer mehr dieser kleinen, nicht nutzbaren Speicherabschnitte. Dies kann dazu führen, dass Anfragen einer Task (malloc) nach Pufferspeicher mit einer bestimmten Größe vom Betriebssystem nicht bedient werden, obwohl im Heap genügend Speicher zur Verfügung steht. Dieser Speicher ist jedoch in sehr kleine Abschnitte zerteilt, die sich an verschiedensten Stellen im Heap befinden. Im Kontext von Betriebssystemen bezeichnet man diese Abschnitte als „Fragmente“ und das damit verbundene Problem als „externe Speicherfragmentierung“.

Dieses Problem der Fragmentierung lässt sich durch eine sogenannte „Garbage Collection“ Software zur automatischen Speicherbereinigung beheben (Defragmentierung). Leider sind Garbage Collection Algorithmen oft alles andere als deterministisch und verursachen zufällig auftretende Delays mit zufälliger Dauer in den Heap-Services. Dazu kommt es häufig in den Speicherallokations-Services normaler Betriebssysteme.

Embedded-Software-Entwickler, die ein normales Betriebssystem verwenden wollen, stecken also in der Klemme. Dürfen in dem Embedded-System gelegentlich zufällige Delays von zufällig langer Dauer auftreten, wenn die Garbage Collection ausgeführt wird? Oder erlaubt man dem Embedded-System, seinen Speicher zu fragmentieren, bis „malloc“-Anforderungen der Applikationssoftware an den Heap zurückgewiesen werden, obwohl noch genügend freier Speicher zur Verfügung steht? Für Embedded-Systeme, die ihre Services über lange Zeitspannen zuverlässig erbringen müssen, ist keiner der beiden Ansätze akzeptabel.

Echtzeitbetriebssysteme dagegen umgehen dieses Dilemma, indem sie sowohl die Speicherfragmentierung und Garbage Collection als auch die damit verbundenen Folgen vermeiden. Sie verwenden eine nicht-fragmentierende Speicherallokation anstelle von Heaps und stellen der Applikationssoftware nur bestimmte Speichergrößen zur Verfügung. Dieser Ansatz ist zwar nicht so flexibel wie der Einsatz von Speicher-Heaps. Die externe Speicherfragmentierung wird so jedoch vermieden, und es ist keine Defragmentierung erforderlich.

Der „Pools“-Mechanismus zur Speicherallokation beispielsweise ermöglicht es, Speicherbereiche mit 4 oder 8 verschiedenen Größen je Pool zuzuweisen. Pools vermeiden die externe Speicherfragmentierung und gestatten nicht, dass ein an den Pool zurückgegebener Puffer später in kleinere Teile gesplittet wird. Der Puffer kommt stattdessen bei Rückgabe auf eine Liste freier Puffer derselben Größe (free buffer list).

Diese Puffer stehen dann in ihrer ursprünglichen Größe zur Wiederverwendung zur Verfügung, siehe Bild 7. Die Speicherallokation und -deallokation erfolgt mit deterministischem und oft konstantem Timing.

Zusammenfassung: Echtzeitbetriebssystem vs. normales Betriebssystem

Echtzeit- und Embedded-Systeme kommen in vielen Bereichen zum Einsatz, z.B. in Flugzeugcomputern, medizinischen Instrumenten und Kommunikationssystemen.

Typisch für Embedded-Systeme

  • sind eingeschränkte Prozessorspeicherkapazität und Rechenleistung,
  • sowie unübliche Schnittstellen zur Außenwelt.
  • Aufgrund der Echtzeitanforderungen sind strenge Zeitvorgaben einzuhalten, in denen die Ergebnisse der Embedded-Verarbeitung zu erbringen sind.

Die Kernels eines RTOS verbergen die zugrundeliegenden Details der Systemhardware vor der Applikationssoftware. Gleichzeitig stellen sie der Applikationssoftware Services verschiedener Kategorien zur Verfügung, z.B. Task-Management mit prioritätsbasiertem präemptiven Scheduling, zuverlässige Intertask-Kommunikation und -Synchronisation, nicht-fragmentierende dynamische Speicherallokation sowie wichtige Timer-Services.

Mit ihrem deterministischen Zeitverhalten unterscheiden sich Echtzeitbetriebssysteme maßgeblich von normalen Betriebssystemen (nicht Echtzeit). Diese Thematik tritt in vielen Bereichen der Betriebssystem-Kernels zutage, z.B.

  • Task-Scheduler,
  • dynamische Speicherallokation und
  • Message-Kommunikation zwischen Tasks.

Normale Betriebssysteme bieten für diese Bereiche oft nicht-deterministische Services an; Echtzeit- und Embedded-Systeme erfordern dagegen voll deterministische Lösungen. Viele Echtzeitbetriebssysteme haben diese Lösungen in ihren kompakten, leistungsstarken Kernels implementiert.

* David Kalinsky ist Berater, Trainer und Dozent für Echtzeit- und Embedded-Programmierung in Sunnyvale/Kalifornien (USA)

(ID:44941011)