Echtzeit-Betriebssystem Zephyr Die Open-Source-Alternative für kleine Systeme

Von Andreas Klinger*

Anbieter zum Thema

Zephyr ist ein junges Open-Source-RTOS mit vielen Parallelen zu Linux. Es ist aber nicht einfach ein geschrumpftes Linux, sondern ein eigenständiges Betriebssystem. Wieso ein weiteres Open-Source-OS?

Zephyr war ursprünglich der Name einer griechischen Windgottheit. Das inspirierte die Schöpfer des neuen RTOS vermutlich dazu, einen Kinderdrachen im Logo zu verwenden.
Zephyr war ursprünglich der Name einer griechischen Windgottheit. Das inspirierte die Schöpfer des neuen RTOS vermutlich dazu, einen Kinderdrachen im Logo zu verwenden.
(Bild: Vogel Communications Group)

Linux hat seine Grenzen dort, wo kleine Mikrocontroller eingesetzt werden sollen und die Ressourcen Memory und Flash extrem knapp sind. Als unterste Grenzen für Linux auf MMU-losen Systemen gelten 4 MB RAM und 3 MB Flash. Mit MMU sind es 8 MB RAM. Dabei handelt es sich um ein extrem reduziertes Linux-System, welches die allermeisten der großartigen Features, die Linux ausmachen, nicht mehr beinhalten. Der Overhead durch das Betriebssystem ist deutlich größer als bei spezialisierten Betriebssystemen für kleine Mikrocontroller.

Ein typisches Beispiel sind die Cortex-Prozessoren. Während Linux bei der Cortex-A-Reihe eine große Rolle spielt, sind die Cortex-M-Prozessoren nicht mehr seine Domäne. Hier kommen kleinere Betriebssysteme zum Einsatz.

Zephyr: Architektur und Komponenten

Zephyr ist ein Echtzeit-Betriebssystem, welches vom kleinen Mikrocontroller bis hin zu großen Systemen eingesetzt werden kann. Durch ein Konfigurationsmenü können die zu erstellenden Komponenten konfiguriert werden. Dabei beinhaltet die Konfiguration neben den Features des Betriebssystems auch die Treiberauswahl, Libraries und User-Applikationen.

Da es nur ein Executable gibt, laufen diese Applikationen genauso wie das Housekeeping vom Betriebssystem als Threads ab. So ist es möglich, dem Benutzer eine Shell mit beachtlicher Auswahl an Kommandos anzubieten während gleichzeitig Daten aufgezeichnet und auf Interrupts reagiert wird.

In den nachfolgenden Abschnitten wird detaillierter auf die Architektur des Betriebssystems eingegangen.

Priority-Based-Preemptive-Scheduling

Der Scheduler kennt Prioritäten, wobei kleinerer Zahlenwert höhere Priorität bedeutet. Höhere Priorität bedeutet, dass Tasks mit niedrigerer Priorität unterbrochen werden, also ein Priority-Based-Preemptive-Scheduling, wie wir es von vielen anderen Echtzeitbetriebssystemen her auch kennen.

Bild 1: In dem vom Scheduler unterstützten Prioritätsband gibt es konfigurierbare Bereiche für unterschiedliche Threadtypen.
Bild 1: In dem vom Scheduler unterstützten Prioritätsband gibt es konfigurierbare Bereiche für unterschiedliche Threadtypen.
(Bild: IT Klinger)

Eine Besonderheit stellen die Prioritätsbereiche dar, in denen sich unterschiedliche Threadtypen befinden, wie in Bild 1 zu sehen:

  • Hardware-Interrupts stehen über dem Scheduler und unterbrechen diesen
  • Optionale hohe negative Prioritäten (in Bild 1: -21 ... -25): Mit der Konfigurationsvariablen NUM_PREEMPT_PRIORITIES können hochpriorisierte Preemptor-Threads als Meta-Interrupt-Service-Routinen definiert und verwendet werden. Diese dienen dazu, um Code aus den Hardware-Interrupt-Service-Routinen rauszunehmen und vor dem restlichen Scheduling abzuarbeiten. Diese werden faktisch so hoch priorisiert wie eine ISR aber dennoch gescheduled. Daher auch die Bezeichnung "Meta-IRQ".
  • Negative Prioritäten (in Bild 1: Prio -1 ... -20): Kooperative Threads (SCHED_FIFO) unterbrechen sich auf der jeweiligen Prioritätsstufe nicht gegenseitig und werden in der Reihenfolge gescheduled in der sie rechenbereit geworden sind.
  • Positive Prioritäten (in Bild 1: Prio 0 ... 14): Preemptive Threads (SCHED_RR) welche auf der jeweiligen Prioritätsstufe in einem Round-Robin-Verfahren gescheduled werden.
  • Idle Thread (in Bild 1: Prio 15): Genau ein Thread auf unterster Prioritätsebene.

Optional kann zusätzlich zu den beschriebenen Prioritätseinteilungen auch noch ein Deadline-Scheduling verwendet werden (CONFIG_SCHED_DEADLINE). Mit diesem Verfahren werden die Tasks innerhalb einer Prioritätsstufe nach einem Earliest-Deadline-First-Verfahren gescheduled. Das heißt, der Task, bei dem die Deadline am dringendsten ist, kommt zuerst dran. Dieses Verfahren wird für jede Prioritätsstufe angewandt, so dass Tasks niedrigerer Priorität aber mit kürzerer Deadline nicht drankommen würden.

So funktioniert die Speicherverwaltung

Es gibt in dem einen Zephyr-Executable viele Threads. Eine Reihe davon werden in Abhängigkeit der Konfiguration durch das Betriebssystem angelegt (z. B. Netzwerk, Shell, Logging, Tracing, ...) und weitere können durch die Applikation angelegt werden. Diese Threads arbeiten auf dem gleichen Speicher und nutzen den gleichen Adresspool. Jedoch wird die MPU (Memory-Protection-Unit) genutzt, um den Threads nur jeweils den für sie bestimmten Speicher zugänglich zu machen. Dadurch wird verhindert, dass sich die Threads gegenseitig Speicher überschreiben.

Bild 2: Beispiel für ein Memory-Layout mit zwei Threads, Kernel- sowie Userspace-Heap. Im Vergleich zur Bildschirmausgabe (Bild 4) wurden die restlichen Threads der Übersichtlichkeit wegen weggelassen.
Bild 2: Beispiel für ein Memory-Layout mit zwei Threads, Kernel- sowie Userspace-Heap. Im Vergleich zur Bildschirmausgabe (Bild 4) wurden die restlichen Threads der Übersichtlichkeit wegen weggelassen.
(Bild: IT Klinger)

In Bild 2 sieht man die prinzipielle Einteilung des vorhandenen Speichers. Aus Gründen der Übersichtlichkeit werden nur die Code-, Daten- und Stack-Bereiche für zwei Threads dargestellt. In einem realen System sind zumeist deutlich mehr Threads mit ihren Speicherbereichen vorhanden. Zur Erstellzeit wird bereits die Speicherverwendung konfiguriert und festgelegt:

  • Memory-Pool für Allokationen im Kernel mittels k_malloc()
  • Malloc-Arena für den Userspace, angefordert mit der Funktion malloc()
  • Stack der Subsysteme
  • Interrupt-Stack
  • Stack vom Main-Thread

Bild 3: Beispiel aus einem Build-Vorgang
Bild 3: Beispiel aus einem Build-Vorgang
(Bild: IT Klinger)

Die Größe des Executables ergibt sich aus dem Linkvorgang. Aus diesen einzelnen Speicherbereichen heraus kann der Build-Vorgang errechnen, ob der vorhandene Speicher überhaupt ausreicht und wenn nicht, wird der Build mit Fehler abgebrochen. Im erfolgreichen Fall wird eine Zusammenfassung des Speicherverbrauchs angezeigt. Ein Beispiel aus einem Build-Vorgang zeigt Bild 3.

Bild 4: Übersicht über die vorhandenen Threads und deren bislang maximale Stack-Nutzung
Bild 4: Übersicht über die vorhandenen Threads und deren bislang maximale Stack-Nutzung
(Bild: IT Klinger)

Hat man das Zielsystem mit einer Shell und Thread-Monitoring konfiguriert, bekommt man mit dem Befehl "kernel stacks" eine Übersicht über die vorhandenen Threads sowie deren bislang maximale Stack-Nutzung, siehe Bild 4.

Hat man das System eingehend getestet, kann man anhand dieser Werte ggfs. die Stack-Nutzung optimieren, um Speicherplatz zu sparen.

Timer-Events und Sleep-Funktionen

Mit der Konfigurationsvariablen CONFIG_SYS_CLOCK_TICKS_PER_SEC wird bei Zephyr die Zeitbasis definiert. In dieser Granularität können Timer-Events oder Sleep-Funktionen verwendet werden. Wird ein periodischer Systemtick verwendet (CONFIG_TICKLESS_KERNEL ist nicht gesetzt), dann werden in dieser Frequenz periodische Interrupts generiert.

Da mit jedem Interrupt auch ein gewisser Overhead verbunden ist, kann die Frequenz nicht beliebig weit erhöht werden. Ein realistischer Wert, welcher mit unseren Mikrocontrollern noch verwendbar ist, wäre zum Beispiel 10 kHz. Bei diesem Wert ist die zeitliche Auflösung auf 100 µs beschränkt, was für viele Anwendungen jedoch zu grobgranular ist.

Jetzt Newsletter abonnieren

Verpassen Sie nicht unsere besten Inhalte

Mit Klick auf „Newsletter abonnieren“ erkläre ich mich mit der Verarbeitung und Nutzung meiner Daten gemäß Einwilligungserklärung (bitte aufklappen für Details) einverstanden und akzeptiere die Nutzungsbedingungen. Weitere Informationen finde ich in unserer Datenschutzerklärung.

Aufklappen für Details zu Ihrer Einwilligung

Konfiguriert man dagegen einen ticklosen Kernel (CONFIG_TICKLESS_KERNEL ist gesetzt), dann werden Timerereignisse vollständig eventbasiert abgearbeitet und durch das Entfallen des periodischen Ticks und dessen Grundlast lassen sich erheblich feingranularere Zeitbehandlungen realisieren. Eine Erhöhung der zeitlichen Granularität (CONFIG_SYS_CLOCK_TICKS_PER_SEC) auf 1 µs ist dann selbst bei kleinen Mikrocontrollern realistisch.

Zusammengefasst kann unterstrichen werden, dass die Variable CONFIG_SYS_CLOCK_TICKS_PER_SEC bei einem tickbasierenden Kernel die Frequenz der Interrupts vom Timertick definiert und bei einem ticklosen Kernel die minimale zeitliche Auflösung festlegt. Daher wird man diese Frequenz bei einem tickbasierenden Kernel vorsichtig erhöhen, um das System nicht in einer Flut von Timerinterrupts untergehen zu lassen und andererseits bei einem ticklosen System umgekehrt stark erhöhen, um in den Genuss einer hohen zeitlichen Auflösung zu kommen.

Konfiguration und Erstellungsprozess

Als Interface zum Konfigurations-, Erstell- und Debugprozess dient ein Meta-Tool namens "west". Von diesem Tool aus können alle Vorgänge gestartet und durchgeführt werden.

Zephyr – der milde Westwind

Zephyr, deutsch „der vom Berge Kommende“, ist eine Windgottheit aus der griechischen Mythologie, die den (milden) Westwind verkörpert. In der Antike wurde Zephyr als Frühlingsbote und „Reifer der Saaten“ verehrt. Hier schließt sich der Kreis zu Autor Andreas Klinger, der nicht nur Softwareexperte, sondern auch ökologischer Getreideanbauer ist: www.it-klinger.de.

Bezüglich Device-Tree und Konfiguration existieren zwei Ebenen – diejenige des Boards und des Projektes. Während ein Device-Tree das verwendete Board beschreibt und Teil des Zephyr-Repositories ist, wird dieses durch die für das Projekt erforderlichen Erweiterungen und Anpassungen ergänzt. Beide Ebenen befinden sich in unterschiedlichen Verzeichnissen, so dass eine hohe Wiederverwendbarkeit gegeben ist.

Die Konfiguration ist menügeführt und ähnlich derjenigen aus dem Linux-Kernel. Im Projektverzeichnis existiert ein Unterverzeichnis namens "board/". Dort ist für jedes mit dem Projekt verwendbare Board das Device-Tree-Fragment zu finden.

Das Zephyr-Repsitory enthält eine tiefe Verzeichnishierarchie. Für den Entwickler wichtige Unterverzeichnisse sind:

  • arch/$ARCH/: ($ARCH steht für die Architektur, z. B. "arm") Architekturabhängiger Code (Interrupt-Vektoren, Fault-Handling, Timer, ...)
  • dts/$ARCH/$SOCVENDOR/: ($SOCVENDOR steht für den Hersteller vom SOC, z. B. "st") Device-Tree-Includes der SOCs mit grundlegenden, wiederverwendbaren Komponenten
  • soc/$ARCH/${SOCVENDOR}_$SUBARCH/: ($SUBARCH steht für die Unterarchitektur, z. B. "stm32") Anpassungen für den SOC (System-On-Chip)
  • board/$ARCH/$BOARD/: ($BOARD steht für das Board, z. B. "olimex_stm32_e407") Anpassungen an konkretes Board (Konfiguration, Device-Tree, Dokumentation, ...)
  • kernel/: Zephyr-Kernel mit seinem zentralen, architekturunabhängigen Code (Timer, Scheduler, IPC, Treibermodell, ...)
  • subsys/: Subsysteme als Teil von Zephyr (Logging, Shell, Tracing, Bussysteme, ...)
  • drivers/: Treiberimplementierungen
  • lib/: Libraries (C-Library, Posix, OS, ...)
  • include/: Include-Files
  • modules/: Optionale Komponenten
  • samples/: Beispielprojekte für die Verwendung von Zephyr-Features, Treibern, Sensoren, Netzwerk und vielem mehr
  • doc/: Dokumentation

Zusammenfassung

Zephyr ist ein junges Open-Source-RTOS mit vielen Parallelen zu Linux. Es ist aber nicht einfach ein geschrumpftes Linux, sondern ein eigenständiges Betriebssystem. Bei manchen Features muss man sogar genau hinsehen, da im Vergleich zu Linux gleichlautende Mechanismen etwas anders verwendet werden. Als Beispiele sind die Scheduling-Mechanismen zu nennen.  (jw)

* Andreas Klinger, der Linux-Experte ist selbständiger Entwickler, Trainer und Referent. Er bietet Auftragsentwicklung und hält Vorträge und Seminare beim ESE Kongress, im Linuxhotel und während der Linux-Woche.

Artikelfiles und Artikellinks

(ID:49041869)