Linux in Mission Critical Systems einsetzen? Kein Problem: Ein Raspberry Pi mit PREEMPT_RT-Patch zeigt eindrucksvoll die Realzeitfähigkeiten des Linux-Kernels auf.
Spätestens seit die NASA ihre Astronautinnen, Astronauten und Fracht der Crew-Dragon-Kapsel und der Falcon-9-Rakete der Firma SpaceX anvertraut, ist klar: Linux ist ready for mission critical systems. Auf Reddit hat sich das SpaceX-Software-Team unter der Rubrik AMA (ask me anything [1]) im Juni 2020 den Fragen Neugieriger gestellt und bereitwillig geantwortet (Abbildung 1). Die Entwickler berichteten von damals bereits über 30 000 Linux-Knoten im Orbit, die in Starlink-Satelliten verbaut sind, und von über 180 Jahren Linux-Laufzeit im Weltraum. Am Kernel selbst gibt es unabhängig von der Entwicklung eigener Gerätetreiber kaum Veränderungen. Das SpaceX-Team hat lediglich den PREEMPT_RT-Patch auf die Kernel-Quellen losgelassen, um ein deterministischeres Realzeitverhalten zu erreichen. Der Rest liege in der Applikation, heißt es.
Schon seit Jahren krempelt unter anderem der deutsche Entwickler Thomas Gleixner erfolgreich den Linux-Kernel in Richtung Realzeit um und erstellt ein Set von Patches, das dem Betriebssystemkern mehr Realzeitverhalten einhaucht: PREEMPT_RT. Zwischenzeitlich hat Chefarchitekt Linus Torvalds große Teile der Patches als fixen Kernel-Bestandteil seinem Quellcodearchiv einverleibt, den Standard-Kernel um neue Realzeitmechanismen erweitert und das Patch-Set damit immer weiter miniaturisiert. Es ist nur eine Frage der Zeit, bis der Kernel die letzten Patches aufgesaugt haben wird.
Realzeitsysteme zeichnen sich dadurch aus, dass sie neben den funktionalen auch zeitlichen Anforderungen genügen. Sie reagieren auf Ereignisse innerhalb eines klar definierten Zeitfensters (Abbildung 2), weil die Laufzeit von Kernel und Applikation leidlich vorherzusehen und deterministisch ist, sich also mit einer Oberschranke angeben lässt. Die Größe des Zeitfensters nennen Profis Reaktionszeit, während alles, was das eigentliche Bearbeiten des Ereignisses verzögert, die Latenzzeit darstellt.
Da die Reaktionszeit von der jeweiligen Aufgabenstellung (Laufzeit der Algorithmen) abhängt, ist die Latenzzeit eine der ausschlaggebenden Größen, um ein Realzeitsystem zu betrachten und zu bewerten. Je kleiner die Latenzzeit auch bei größter Auslastung und unter allen Umständen ausfällt, desto besser ist das System – oder, um es neutral zu sagen: desto mehr Einsatzmöglichkeiten gibt es.
Die Ursachen für Latenzzeiten sind vielfältiger Natur. Ist der Betriebssystemkern mit etwas sehr Wichtigem beschäftigt, lässt er sich dabei nicht unterbrechen. In den Anfangstagen von Linux, als das Zeitverhalten nur unter dem Gesichtspunkt Durchsatz relevant war und Latenzzeiten für Standardbetriebssysteme keine Rolle spielten, ging man auf Nummer sicher, indem man generell Unterbrechungen unterband. Schließlich gehören Unterbrechungen zu den Hauptübeln für Fehlverhalten von Softwaresystemen.
Latenzzeitkiller
Die Leistung der Entwicklerinnen und Entwickler beim Erstellen und Pflegen der RT-Patches besteht entsprechend darin, die nicht unterbrechbaren Bereiche zu identifizieren und unterbrechbar (preemptible) umzubauen. Daher stammt auch der Name des Patch-Sets: PREEMPT_RT. Unterbrechbarkeit ist aber nur eine Seite der Medaille und des Patch-Sets. Daneben sind Mechanismen wichtig, um beispielsweise gezielt auf die Abarbeitungsreihenfolge von Tasks (das Scheduling) Einfluss zu nehmen.
Das Scheduling auf Basis von Prioritäten ist für ein Realzeitsystem zwingend notwendig und ein Deadline-Scheduling, bei dem die Auswahl der anschließend zu bearbeitenden Task auf Basis der Reaktionszeit stattfindet, wünschenswert. Damit sich Interrupts priorisieren lassen, lagern Realzeitsysteme zeitintensive Teile der Interrupt-Service-Routinen in Kernel-Threads aus. Hier sprechen Expertinnen und Experten von Threaded Interrupts.
Die Prioritätsgeschichte in Kombination mit dem Schutz kritischer Abschnitte wiederum führt zur Prioritätsinversion, der man die Prioritätsvererbung entgegensetzt. Die Tabelle “PREEMPT_RT” zeigt weitere Techniken, die Linux im Rahmen der PREEMPT_RT-Entwicklung eingebaut hat.
|
Kurzbeschreibung |
Realzeit-Technologien |
|---|---|
|
Preemption |
Kernel-Code lässt sich jederzeit unterbrechen. |
|
High Resolution Timer |
Zeitfunktionen mit hoher Auflösung. |
|
Threaded Interrupts |
Auf Applikationsebene priorisierbare Interrupts. |
|
Prioritätsvererbung |
Auf Betriebsmittel wartende Tasks vererben ihre (hohe) Priorität an Betriebsmittel nutzende Tasks. |
|
Tickless Betrieb |
Der Kernel wird nicht periodisch aktiv, sondern bedarfsgesteuert. |
|
Earliest Deadline First Scheduler |
Auswahl zu bearbeitender Tasks auf Basis der Reaktionszeit. |
|
Realzeit-Locks |
Elemente zum Schutz kritischer Abschnitte. |
|
Memory Locking |
Verhindern des zu Latenzzeiten führenden Auslagerns von Speicherseiten. |
Selbst machen
Genug der Vorrede, hinein in die Praxis. Die Wirkungsweise eines PREEMPT_RT-Linux lässt sich sehr eindrucksvoll mit etwas Handarbeit verdeutlichen. Um unsere Arbeitsmaschine nicht modifizieren zu müssen, wählen wir einen Raspberry Pi 4 als Versuchsobjekt, generieren einen (gepatchten) Kernel und installieren sowie booten ihn zusammen mit einigen Testprogrammen. Den zum jeweiligen Kernel passenden PREEMPT_RT-Patch [2] finden Sie ebenso wie bereits gepatchte [3] Kernel-Quellcodes [4] in den Repositories von Kernel.org.
Listing 1 zeigt die Kommandos zum Cross-Kompilieren eines Linux-Kernels für den Raspberry Pi auf einem PC. Hintergrundinfos dazu finden Sie auf den Seiten der Raspberry Pi Foundation [5]. Zunächst installieren Sie die Tools zum Cross-Kompilieren, darauf folgt das Herunterladen des aktuellen, stabilen Linux-Kernels und der Patch-Datei, die schließlich den Kernel auf volle Realzeit umbaut.
Aus Geschwindigkeitsgründen empfiehlt es sich, den Raspberry-Pi-Kernel auf dem PC zu erzeugen. Dazu setzen Sie die Umgebungsvariablen »KERNEL«, »ARCH« und »CROSS_COMPILE«. Für das zu erreichende Realzeitverhalten ist die Konfiguration entscheidend, bei der »make menuconfig« den PREEMPT_RT-Patch erst aktiviert. Die Einstellungen finden Sie in der Kernel-Konfiguration unter dem Oberpunkt General Setup. Sofern das nicht schon als Vorgabe gesetzt ist, wählen Sie zunächst den Modus für expert users aus.
Preemption-Modelle
Grundsätzlich stehen vier Preemption-Modell zur Wahl. Bei »CONFIG_PREEMPT_NONE« handelt es sich um das traditionelle Verhalten eines Unix-Kernels. Der Kernel-Code (Interrupt-Service-Routinen, Systemaufrufe) wird nicht unterbrochen. Diese Konfiguration garantiert den bestmöglichen Durchsatz, da es zu wenigen Kontextwechseln kommt. Daraus resultiert eine gute Ausnutzung von CPU und Caches. Diese Einstellung ist am besten für Server geeignet.
Bei Einsatz von »CONFIG_PREEMPT_VOLUNTARY« lässt sich Kernel-Code an definierten Stellen unterbrechen (»might_sleep()«), an denen der Scheduler zusätzlich aktiv werden kann. So erfolgt die Reaktion auf Ereignisse etwas schneller. Diese Anpassung ist für einen normalen Desktop-Rechner vorgesehen.
Verwenden Sie »CONFIG_PREEMPT«, lassen sich die meisten Stellen im Kernel-Code unterbrechen. Beim Auftreten eines Ereignisses (Interrupts) kann der Scheduler direkt aktiv werden und muss nicht erst auf das Ende einer gerade aktiven Kernel-Codesequenz (zum Beispiel eines gerade aktiven Systemaufrufs) warten. Wer auf seinem Desktop schnelle Reaktionen wünscht, nutzt diese Einstellung.
Mit »CONFIG_PREEMPT_RT« schließlich lässt sich jeglicher Kernel-Code unterbrechen, und typischerweise nicht schlafende Spinlocks können plötzlich schlafen. Interrupt-Service-Routinen sind als Threaded Interrupts realisiert und damit priorisiert. Diese Konfiguration kommt bei Realzeitsystemen mit harten Zeitanforderungen zum Einsatz.
In unserem Fall setzen Sie das Preemption-Modell (Abbildung 3) auf Real-Time (Abbildung 4). Beim Verlassen des Konfigurationsprogramms werden die Änderungen abgespeichert.
Listing 1
Cross-Generierung eines PREEMPT_RT-Kernels
### Installation der notwendigen Werkzeuge $ sudo apt install git bc bison flex libssl-dev make libc6-dev libncurses5-dev ### Installation Cross-Compiler $ sudo apt install crossbuild-essential-armhf $ mkdir ~/preempt-rt-kernel $ cd preempt-rt-kernel ### Quellcode herunterladen $ git clone --depth=1 https://github.com/raspberrypi/linux $ cd linux ### Patch herunterladen -- ACHTUNG: Auf die richtige Version achten! $ wget https://cdn.kernel.org/pub/linux/kernel/projects/rt/5.15/patch-5.15.44-rt46.patch.gz ### Kernel patchen $ patch patch-5.15.44-rt46.patch.gz $ patch -p1 <patch-5.15.44-rt46.patch ### Grundkonfiguration für Raspberry Pi 4 $ KERNEL=kernel7l $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2711_defconfig ### Aktivierung des PREEMPT_RT-Patches $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig ### General Setup | Configure standard kernel features (expert users) -> ### General Setup | Preemption Model (Fully Preemptible Kernel (Real-Time)) -> ### exit -> exit -> yes ### Cross-Generierung von Kernel, Modulen und Devicetree $ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j 4 zImage modules dtbs
Kurze Weile
Mit der Generierung des Kernels, der Module und des Devicetrees ist eine Host-Maschine durchaus einmal ein gutes Stündchen beschäftigt. Die Installation des fertig generierten Kernels veranschaulicht Listing 2. Dazu stecken Sie die SD-Karte mit dem darauf bereits installierten Pi OS in den Rechner. Per Lsblk identifizieren Sie die Gerätedateien, über die Sie auf die beiden Partitionen der SD-Karte zugreifen. Anders als in der Anleitung [2] angegeben, waren das bei uns im Test beispielsweise »/dev/mmcblk0p1« und »/dev/mmcblk0p2«.
Möglicherweise hat Ihr Linux-System die Partitionen auf der SD-Karte automatisch gemountet. Das erkennen Sie an der Angabe eines Verzeichnisses wie »/media/quade/boot/« auf der rechten Seite der Ausgabe des Kommandos »lsblk«. In diesem Fall können Sie entweder diese Verzeichnisse für die Installation verwenden oder hängen die Partitionen aus und dann wie in Listing 2 wieder ein.
Legen Sie dazu zwei Verzeichnisse an, auf die Sie die beiden Partitionen mounten. Installieren Sie schließlich die Kernel-Module, die Devicetree-Overlays und den Haupt-Devicetree. Bevor Sie dann den Realzeit-Kernel Marke Eigenbau installieren, sollten Sie ein Backup des aktuellen Kernels erstellen. Nun gilt es, die beiden Partitionen auszuhängen. Die SD-Karte stecken Sie zurück in den Raspberry Pi und versorgen diesen mit Strom, woraufhin der Mini-Rechner hochfährt.
Listing 2
PREEMPT_RT-Kernel auf SD-Karte installieren
cd ~/preempt-rt-kernel lsblk ### Gerätedatei evaluieren. Sind die Partitionen der SD-Karte eingehängt, ### werden sie mit den zwei nachfolgenden Kommandos wieder ausgehängt: $ sudo umount /dev/mmcblk0p1 $ sudo umount /dev/mmcblk0p2 ### Partitionen einhängen $ mkdir mnt $ mkdir mnt/fat32 $ mkdir mnt/ext4 $ sudo mount /dev/mmcblk0p1 mnt/fat32 $ sudo mount /dev/mmcblk0p2 mnt/ext4 ### Kernel-Module installieren cd linux $ KERNEL=kernel7l $ sudo env PATH=$PATH make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=mnt/ext4 modules_install ### Kernel und Devicetree installieren $ cd ~/preempt-rt-kernel $ sudo cp mnt/fat32/$KERNEL.img mnt/fat32/$KERNEL-backup.img $ sudo cp linux/arch/arm/boot/zImage mnt/fat32/$KERNEL.img $ sudo cp linux/arch/arm/boot/dts/*.dtb mnt/fat32/ $ sudo cp linux/arch/arm/boot/dts/overlays/*.dtb* mnt/fat32/overlays/ $ sudo cp linux/arch/arm/boot/dts/overlays/README mnt/fat32/overlays/ $ sudo umount mnt/fat32 $ sudo umount mnt/ext4 $ sync
Sein oder nicht sein
Nach dem Booten und Einloggen lässt sich anhand zweier Kriterien feststellen, ob der PREEMPT_RT-Patch wirkt. Ein auf der Konsole eingegebenes »uname -a« sollte »PREEMPT_RT« ausweisen. Außerdem muss die Datei »/sys/kernelrealtime« existieren und eine 1 enthalten (Abbildung 5).
Um den eigentlichen Effekt des Patches zu testen, empfiehlt es sich, die Test-Suite »rt-tests« zu installieren. Das Paket gelangt per »git clone« auf den RasPi (Listing 3) und lässt sich nach dem Wechsel in das neue Verzeichnis »rt-tests/« per Make ruckzuck generieren. Aus dem Reigen der jetzt zur Verfügung stehenden Testprogramme wählen Sie für den ersten Test »cyclictest« und »hackbench« aus. Ersteres führt Messungen zur Latenzzeit durch, Letzteres bringt den RasPi gehörig ins Schwitzen und provoziert damit Worst-Case-Situationen.
Listing 3
Testprogramme installieren
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/clrkwllms/rt-tests.git $ cd rt-tests $ make
Um die Latenzzeit zu bestimmen, legt sich Cyclictest [6] per »clock_nanosleep()« für eine definierte Zeit schlafen. Sobald Sie die Task aufwecken, liest es die aktuelle Zeit und berechnet durch Differenzbildung zur erwarteten Weckzeit die Abweichung und damit die Latenz. Das Ganze wird in einer Schleife wiederholt ausgeführt und statistisch ausgewertet.
Cyclictest gibt die minimale Latenz, die durchschnittliche und vor allem die bei den Durchläufen aufgetretene maximale Latenz in Mikrosekunden aus. Aber Achtung: Tritt während der Messung keine hohe Latenz auf, bedeutet das nicht, dass es sie nicht geben kann. Je länger allerdings die Messung dauert und je unterschiedlicher dabei die Lastsituationen für den RasPi ausfallen, desto höher ist die Wahrscheinlichkeit, den Worst Case erwischt zu haben. Für eine erste Bewertung des PREEMPT_RT-Patches genügt das Ergebnis jedoch.
An dieser Stelle sei daran erinnert, dass eine Software nur dann eine niedrige Latenz aufweist, wenn man sie unter Realzeitgesichtspunkten programmiert und betreibt. Dazu gehört neben dem Vergeben einer Realzeitpriorität das zuverlässige Unterbinden von Speicherfehlern. Cyclictest bringt nicht nur solche Mechanismen mit, sondern ermöglicht auch, sie per Kommandozeilenoption ein- und auszuschalten. Die Wahl der Priorität spielt eine besonders wichtige Rolle. Mit dem PREEMPT_RT-Patch sind fast alle Interrupt-Service-Routinen als Interrupt-Threads ausgeführt und damit priorisiert. Sollen Interrupts nicht das Testprogramm selbst ausbremsen, müssen Sie die Priorität entsprechend hoch setzen.
Erkenntnisgewinn
Abbildung 6 zeigt den Aufruf von Cyclictest als normale Applikation, also ohne Priorität, Memory Prefault oder sonstige Realzeittechniken. Die Latenzzeiten liegen bereits recht passabel im zweistelligen Millisekundenbereich. Unter Last fallen sie niedriger aus als ohne Last. Das zunächst seltsam anmutende Phänomen hat mit häufiger auftretenden Unterbrechungen zu tun und dem damit verbundenen, zeitnäheren Abarbeiten von Zeitaufträgen und des Schedulings.
Starten Sie Cyclictest mit einigen Realzeittricks, liegen die Latenzen ebenfalls im zweistelligen Bereich – diesmal aber im Mikro- statt wie vorher im Millisekundenbereich (Abbildung 7). Das ist recht beeindruckend für den Raspberry Pi und das darauf laufende ausgewachsene Betriebssystem. Das Open Source Automation Development Lab (OSADL) nimmt Langzeitmessungen der Latenz von diversen Realzeit-Linuxen vor, bestätigt unser Ergebnis und liefert Plots dazu. Der Raspberry Pi 4 befindet sich in deren Testlabor in Rack 4, Slot 1 [7]. Das Team erstellt die Plots direkt mithilfe von Cyclictest und Gnuplot. Die Häufigkeitsverteilung der Latenzzeiten unserer Messungen auf den einzelnen CPU-Kernen finden sich in Abbildung 8.
Neugierige spielen noch etwas mit den Kommandozeilenoptionen und den Prioritäten. Reduzieren Sie beispielsweise die Priorität von 80 auf 10, bekommen wir auf Core 0 der RasPi-CPU eine dreistellige Latenzzeit – ein signifikantes Zeichen für Threaded Interrupts: Höher priorisierte Interrupt-Threads unterbrechen augenscheinlich die auf CPU-Kern 0 laufenden Tasks.
Aus diesen wenigen Tests lassen sich bereits eine Reihe von spannenden Erkenntnissen ableiten. An erster Stelle steht, dass Linux mit dem PREEMPT_RT-Patch tatsächlich erfreulich niedrige Latenzzeiten ermöglicht. An zweiter Stelle folgt, dass der Patch allein nicht genügt: Wer niedrige Latenzzeiten benötigt, der muss den Werkzeugkoffer der Realzeitprogrammierung öffnen und auf System und Applikation anwenden. Was die Toolbox so alles hergibt, lesen Sie bei Interesse kompakt im eingangs zitierten Reddit-Thread der SpaceX-Entwicklermannschaft nach. (csi)
Die Autoren
Eva-Katharina Kunst ist seit den Anfängen von Linux Fan von Open Source. Jürgen Quade, Professor an der Hochschule Niederrhein, führt auch für Unternehmen Schulungen zu den Themen Treiberprogrammierung und Embedded Linux durch.
Infos
- “Ask Us Anything”: https://www.reddit.com/r/spacex/comments/gxb7j1/we_are_the_spacex_software_team_ask_us_anything/
- PREEMPT_RT-Patches: https://cdn.kernel.org/pub/linux/kernel/projects/rt/,
- Gepatchter Entwickler-Kernel: https://git.kernel.org/cgit/linux/kernel/git/rt/linux-rt-devel.git
- Gepatchter Linux-Kernel (stable): https://git.kernel.org/cgit/linux/kernel/git/rt/linux-stable-rt.git
- Raspberry Pi Foundation – “The Linux Kernel”: https://www.raspberrypi.com/documentation/computers/linux_kernel.html
- The Linux Foundation – Cyclictest: https://wiki.linuxfoundation.org/realtime/documentation/howto/tools/cyclictest/start
- OSADL – “Latency Plot ARM Broadcom BCM2711 in Rack 4, Slot 1”: https://www.osadl.org/Latency-plot-of-system-in-rack-4-slot.qa-latencyplot-r4s1.0.html














