Wie entwickelt man echtzeitfähige Linux-Anwendungen richtig? Dieser Artikel zeigt zentrale Anforderungen, typische Fehlerquellen und konkrete Maßnahmen – von Interrupts über Scheduling bis zur Speicherverwaltung – für stabile Realtime-Performance.
Was gilt es bei der Entwicklung einer Echtzeit-Anwendung mit Linux zu beachten? Und was sollte man dabei keinesfalls tun?
(Bild: KI-generiert / DALL-E)
Das Ereignis, auf welches in Echtzeitsystemen deterministisch reagiert werden soll, ist der Interrupt. Daher kommt ihm eine zentrale Bedeutung zu.
Interruptbehandlung - threaded
(Bild: Andreas Klinger)
Beim RT-Preemption-Patch erfolgt die Interruptbehandlung fast aller Interrupts in zwei Stufen: Ein IRQ-Handler wird im Hardware-Interrupt-Context und ein Threaded-IRQ-Handler wird durch einen Kernel-Thread aufgerufen. (Die wichtigste Ausnahme von diesem Schema bildet der Timer-Interrupt, welcher auch beim RT-Patch nur im Hardware-Interrupt-Context aufgerufen wird).
Der im Hardware-Interrupt-Context aufgerufene Handler besteht aus einer Default-Funktion, welche lediglich zurückgibt, dass der zugehörige Threaded-IRQ-Handler aufgerufen werden soll. Dieser Handler kann durch den Treiber-Entwickler auch ersetzt werden, beispielsweise um bei einem Shared-Interrupt diesen zu Clearen.
SEMINAR-TIPP
Embedded-Linux-Woche
In den Kursen der Embedded-Linux-Woche führen unsere Referenten Sie Schritt für Schritt in die Linux-Welt ein. Fortgeschrittene können sich im Seminarangebot "Echtzeit-Linux und Systemprogrammierung" vom einfachen Embedded-Linux-User zum systemnahen Echtzeit-Entwickler weiterbilden. In nur fünf Tagen erarbeiten Sie sich fundiertes Fachwissen um den Linux-Kernel, Prozessverwaltung, Tracing, Ressoucenverwaltung, Hardware-Anbindung und vieles mehr, so dass Sie sich schon bald als waschechten Linux-Experten bezeichnen können!
Die eigentliche Interrupt-Service-Routine vom ursprünglichen Treiber wird durch einen Kernel-Thread aufgerufen. Wenn dieser aufgeweckt wurde, wird er entsprechend seiner Policy gescheduled (Default: SCHED_FIFO mit Priorität 50). Wenn dies erfolgt, dann ruft er die Interrupt-Service-Routine auf. Durch diese Veränderung an fast allen bestehenden Treibern wird die Interrupt-Arbeit in den Scheduler in den mittleren Prioritätsbereich der Realtime-Policy verlagert.
Dadurch öffnet sich ein Prioritätsfenster im Bereich von 51 bis 98 welches für hochpriorisierte Interrupts und Applikationen (eigentliche Echtzeit-System) durch den Entwickler einstellbar genutzt werden kann. Priorität 99 wird für diverses Houskeeping (z. B. Task-Migration) genutzt und sollte daher gemieden werden.
Scheduling
Wer unterbricht wen im RT-Patch?
(Bild: Andreas Klinger)
Die Aufgabe des Entwicklers ist es nun das Prioritätsfenster für die Echtzeit-Applikation über den Threaded-Interrupts zu nutzen. Dazu wird mit dem Programm chrt die Priorität des echtzeitrelevanten Interrupts hochgehoben genauso wie diejenige der Echtzeitapplikation auf über 50 eingestellt wird, um vor den restlichen Interrupts dranzukommen.
SoftIRQs
threaded SoftIRQ - threaded Interrupt raised
(Bild: Andreas Klinger)
Im Linux-Kernel existiert ein Mechanismus namens SoftIRQ. Dieser dient dazu die Interrupt-Service-Routinen zu entlasten. SoftIRQs werden erst nach Abarbeitung von Hardware-Interrupts aber vor dem eigentlichen Scheduler aufgerufen und während sie rechnen, sind weitere Interrupts enabled. Dies ist auch der entscheidende Unterschied zum Hardware-Interrupt: Weitere anliegende Interrupts kommen während der Ausführung des SoftIRQs durch und sind nicht implizit maskiert.
Bei Aktivierung des RT-Patches muss sich dieses Verhalten ändern, da sie ja vor dem Scheduling und dann vor dem Echtzeit-Interrupt und dessen Applikation rechnen würden. Im RT-Patch werden die aus einem Threaded-Interrupt heraus ablaufbereit markierten (geraised) SoftIRQs direkt wie eine Funktion aufgerufen. Damit laufen sie durch den Scheduler verwaltet im Kontext des Threaded-Interrupts.
Threaded SoftIRQ - Hardware-Interrupt raised order ksoftirqd running
(Bild: Andreas Klinger)
Wird der SoftIRQ jedoch aus einem Hardware-Interrupt heraus geraised, so darf er nicht direkt aufgerufen werden, da die SoftIRQs ja genau dazu erfunden wurden, um Interruptarbeit außerhalb des Interruptkontextes mit implizit gesperrten Interrupts zu machen. In diesem Fall werden die SoftIRQs an den Kernel-Thread mit dem Namen ksoftirqd delegiert und dieser ruft die SoftIRQ-Funktionen auf. Dieser Kernel-Thread hat keine Echtzeit-Priorität (Policy: SCHED_OTHER) und daher fallen diese SoftIRQs in ihrer Wichtigkeit deutlich nach unten.
Für SoftIRQs besteht weiterhin die Einschränkung, dass diese sich auf einem Core nicht gegenseitig unterbrechen, sondern nur sequentiell auf einem Core ausgeführt werden können. Daher werden auch SoftIRQs, welche geraised werden, wenn schon andere SoftIRQs geraised sind, auch in den ksoftirq-Thread eingereiht. Ein Workaround für diese Fälle besteht darin, diese SoftIRQs, wenn (und auch nur dann) sie echtzeitrelevant sind, auf separate Cores zu pinnen. Werden echtzeitrelevante Treiber neu entwickelt, dann empfiehlt es sich auf SoftIRQs zu verzichten und stattdessen Kernel-Threads zu verwenden.
Stand: 08.12.2025
Es ist für uns eine Selbstverständlichkeit, dass wir verantwortungsvoll mit Ihren personenbezogenen Daten umgehen. Sofern wir personenbezogene Daten von Ihnen erheben, verarbeiten wir diese unter Beachtung der geltenden Datenschutzvorschriften. Detaillierte Informationen finden Sie in unserer Datenschutzerklärung.
Einwilligung in die Verwendung von Daten zu Werbezwecken
Ich bin damit einverstanden, dass die Vogel Communications Group GmbH & Co. KG, Max-Planckstr. 7-9, 97082 Würzburg einschließlich aller mit ihr im Sinne der §§ 15 ff. AktG verbundenen Unternehmen (im weiteren: Vogel Communications Group) meine E-Mail-Adresse für die Zusendung von redaktionellen Newslettern nutzt. Auflistungen der jeweils zugehörigen Unternehmen können hier abgerufen werden.
Der Newsletterinhalt erstreckt sich dabei auf Produkte und Dienstleistungen aller zuvor genannten Unternehmen, darunter beispielsweise Fachzeitschriften und Fachbücher, Veranstaltungen und Messen sowie veranstaltungsbezogene Produkte und Dienstleistungen, Print- und Digital-Mediaangebote und Services wie weitere (redaktionelle) Newsletter, Gewinnspiele, Lead-Kampagnen, Marktforschung im Online- und Offline-Bereich, fachspezifische Webportale und E-Learning-Angebote. Wenn auch meine persönliche Telefonnummer erhoben wurde, darf diese für die Unterbreitung von Angeboten der vorgenannten Produkte und Dienstleistungen der vorgenannten Unternehmen und Marktforschung genutzt werden.
Meine Einwilligung umfasst zudem die Verarbeitung meiner E-Mail-Adresse und Telefonnummer für den Datenabgleich zu Marketingzwecken mit ausgewählten Werbepartnern wie z.B. LinkedIN, Google und Meta. Hierfür darf die Vogel Communications Group die genannten Daten gehasht an Werbepartner übermitteln, die diese Daten dann nutzen, um feststellen zu können, ob ich ebenfalls Mitglied auf den besagten Werbepartnerportalen bin. Die Vogel Communications Group nutzt diese Funktion zu Zwecken des Retargeting (Upselling, Crossselling und Kundenbindung), der Generierung von sog. Lookalike Audiences zur Neukundengewinnung und als Ausschlussgrundlage für laufende Werbekampagnen. Weitere Informationen kann ich dem Abschnitt „Datenabgleich zu Marketingzwecken“ in der Datenschutzerklärung entnehmen.
Falls ich im Internet auf Portalen der Vogel Communications Group einschließlich deren mit ihr im Sinne der §§ 15 ff. AktG verbundenen Unternehmen geschützte Inhalte abrufe, muss ich mich mit weiteren Daten für den Zugang zu diesen Inhalten registrieren. Im Gegenzug für diesen gebührenlosen Zugang zu redaktionellen Inhalten dürfen meine Daten im Sinne dieser Einwilligung für die hier genannten Zwecke verwendet werden. Dies gilt nicht für den Datenabgleich zu Marketingzwecken.
Recht auf Widerruf
Mir ist bewusst, dass ich diese Einwilligung jederzeit für die Zukunft widerrufen kann. Durch meinen Widerruf wird die Rechtmäßigkeit der aufgrund meiner Einwilligung bis zum Widerruf erfolgten Verarbeitung nicht berührt. Um meinen Widerruf zu erklären, kann ich als eine Möglichkeit das unter https://contact.vogel.de abrufbare Kontaktformular nutzen. Sofern ich einzelne von mir abonnierte Newsletter nicht mehr erhalten möchte, kann ich darüber hinaus auch den am Ende eines Newsletters eingebundenen Abmeldelink anklicken. Weitere Informationen zu meinem Widerrufsrecht und dessen Ausübung sowie zu den Folgen meines Widerrufs finde ich in der Datenschutzerklärung, Abschnitt Redaktionelle Newsletter.
RT-Throttling
In Linux existiert ein Mechanismus, der es verhindert, dass Realtime-Tasks das System durch eine Busy-Loop lahmlegen können. Dieser Mechanismus wird als Realtime-Throttling bezeichnet.
RT-Throttling
(Bild: Andreas Klinger)
Dabei wird den Realtime-Tasks insgesamt eine maximale Runtime zugestanden. Ist diese erreicht, wird das Realtime-Scheduling unterbrochen und für die bis zur Zeitperiode verbleibende Zeit dürfen normale Tasks rechnen. Die Default-Einstellung für die Runtime ist 950 ms und für die Periode 1 s. Daher sind sporadische Latenzen bis zu 50 ms ein typisches Symptom dafür, dass das RT-Throttling zugeschlagen hat.
In einem Echtzeit-System kann es nun vorkommen, dass ein niedrig priorisierter RT-Task übermäßig lange (länger als Runtime) rechnet und ausgerechnet, wenn das RT-Throttling-Zeitfenster zuschlägt, gerne rechnen möchte. Das würde bedeuten, dass der niedrig priorisierte indirekt den hoch priorisierten ausbremst. Daher sollte man das RT-Throttling für Echtzeitsysteme ausschalten. Dies kann mit folgendem Aufruf erfolgen:
echo -1 > /proc/sys/kernel/sched_rt_runtime_us
Besonderheiten in der Entwicklung
Im Laufe der Echtzeit-Entwicklung stellte sich heraus, dass nicht alle Mechanismen, welche aus der Entwicklung im Userspace bekannt und empfohlen sind, auch für das Echtzeitsystem geeignet sind. Im Gegenteil verursachen manche Mechanismen unerwartete Latenzen.
Datei memory-mappen
(Bild: Andreas Klinger)
Grundsätzlich sollen alle IPC-Mechanismen, welche auf der herkömmlichen Waitqueue basieren, nicht verwendet werden. Dazu gehört der Aufruf von poll(), select() und Co. genauso wie die Signale. Alternativ kann zur Benachrichtigung die Conditional Variable aus der NPTL-Library genutzt werden, wenn diese auch mit einem Priority-Inheritance-Mutex verwendet wird.
Posix-Timer beruhen ebenso auf Signalen und Timer-File-Descriptors auf Waitqueues. Daher ist für zeitlich abhängige Vorgänge empfohlen, den nanosleep() und den clock_nanosleep() (mit CLOCK_MONOTONIC_RAW) zu verwenden. Benötigt man ein fixes Zeitraster, kann das Flag TIMER_ABSTIME zum Einsatz kommen.
Deutschlands Leitkongress der Embedded-Softwarebranche
Bewerben Sie sich als Sprecher
Gestalten Sie den ESE Kongress aktiv mit! Es gibt viele gute Gründe, warum Sie sich mit Ihrem eigenen Vortrag oder Seminar am Programm beteiligen sollten. Teilen Sie Ihr Wissen, Ihre Erfahrung und Ihre Erkenntnisse mit Ihren Branchenkollegen. Nutzen Sie den ESE Kongress als bewährte Plattform, um sich als Software-Experte einen Namen zu machen und Ihren Marktwert zu steigern, und präsentieren Sie Ihre Lösungen einem hochwertigen Fachpublikum.
Memory-Mapping wird im Linux-Kernel vielfältig genutzt. Beispielsweise um Programm- und Librarycode sowie -daten in das RAM zu mappen, dynamischen Speicher zu allozieren sowie um Heap und Stack zu setzen. Das physische Kopieren der Daten vom Massenspeicher oder von einem anderen Speicherbereich (Copy-On-Write-Memory) passiert allerdings erst beim erstmaligen Zugriff. Beim lesenden Zugriff wird im Falle von Copy-On-Write-Memory auf schon anderswo vorhandenen Speicher mit gleichem Inhalt zugegriffen.
Wird nun der Speicher tatsächlich benötigt, löst die MMU eine Exception namens Page-Fault aus. Diese wird durch das Betriebssystem abgefangen und aufgelöst, indem die betreffende Speicherseite in den Adressraum des betreffenden Prozesses kopiert wird. Wir unterscheiden dabei zwischen Major- und Minor-Page-Faults. Beim Major-Fault sind Disk-IO-Operationen notwendig, während beim Minor-Page-Fault nur Speicher kopiert wird (Copy-On-Write-Memory). In beiden Fällen entsteht aus Sicht des ausführenden Tasks eine unerwartete Latenz beim Zugriff auf den Speicher. Um dies zu vermeiden, sind einige Maßnahmen erforderlich.
Speicher soll gelockt werden, so dass einmalig im RAM vorhandener Speicher nicht mehr ausgelagert wird. Dies bewerkstelligt der folgende C-Aufruf:
mlockall(MCL_CURRENT | MCL_FUTURE);
Speicher sollte durch die C-Library auch nicht durch den mmap()-Mechanismus irgendwo im RAM gemapped, sondern vom Heap genommen werden. Beim Heap ist genau bekannt, wo er liegt, nämlich direkt nach den Daten des Executables und er wird von dort bei Bedarf vergrößert (sogenannte Program-Break). Daher ist es für den Heap-Speicher einfach, ihn für zukünftige Allokationen resident im RAM vorzuhalten, was für mmap()-Speicher nicht möglich ist. Die folgende C-Zeile bewerkstelligt dies:
mallop(M_MMAP_MAX, 0);
Die C-Library versucht den Heap zu verkleinern, wenn er gerade nicht benötigt wird. Auch das sollte abgeschaltet werden:
mallopt(M_TRIM_THRESHOLD, -1);
Heap prefaulten
(Bild: Andreas Klinger)
Zu guter Letzt muss man noch dafür sorgen, dass sowohl der Heap als auch der Stack wirklich resident im RAM liegen. Dazu alloziert man im Heap die während des Programmlaufs benötigte Speichermenge und beschreibt (lesen reicht wegen Copy-On-Write-Memory nicht) in jede Page mindestens ein Byte, nachdem obige Einstellungen vorgenommen wurden. Diesen Speicher kann man gleich wieder freigeben und weiß dann sicher, dass bis zu dieser Allokationsgröße der Speicher bei zukünftigen Anforderungen bereits im RAM resident ist.
Stack prefaulten
(Bild: Andreas Klinger)
Das gleiche macht man mit dem Stack, indem man einfach eine Funktion erstellt, welche den maximal benötigten Stack anlegt und ebenso pageweise beschreibt.
Wie kann man feststellen, ob die ergriffenen Maßnahmen wirken? Dazu gibt es einige Diagnosemöglichkeiten. Mit dem Programm pmap sieht man die Speicherbereiche eines Prozesses und wie viel davon wirklich im RAM liegt (RSS - resident set size). Der Aufruf lautet:
pmap -x <pid>
Mit dem guten alten Programm ps kann man sich auch die Page-Faults ansehen, wenn man die Spalten entsprechend auswählt. Der Aufruf lautet:
ps -Leo pid,maj_flt,min_flt,cmd
Auch mit dem Programm perf kann man sich die Page-Faults anschauen. Beispielsweise:
perf stat -e page-faults ./myrtapp
Möchte man genauer sehen, in welchen Programmteilen Page-Faults entstehen, kann man das perf-Interface des Kernels auch im eingenen Programm nutzen. Siehe dazu die Manual-Page perf_event_open(2).
Im Linux gibt es einen "Heckenschützen". Die Rede ist vom Out-Of-Memory-Killer (OOM-Killer). Dieser wird aktiviert, wenn Speicher knapp wird und sucht sich mittels einer Score-Tabelle einen Prozess, der viel Speicher benötigt, und terminiert diesen. Man kann für einen Task diese Score-Tabelle beeinflussen und diesen somit aus der Liste der relevanten Tasks rausnehmen. Dies funktioniert im procfs und auch mit einem Programm namens choom. Der Programmaufruf lautet:
choom -p <pid> -n -1000
Hier ist die Rede von deterministischen Tasks, und sobald durch einen mehr oder weniger zufälligen Mechanismus Tasks aus dem System entfernt werden, wird man möglicherweise auch den Determinismus verlieren. Ansonsten wäre der terminierte Task ja von vornerein überflüssig gewesen, wenn er keine für das Gesamtsystem relevante Aufgabe hatte.
Energieeinstellungen
Mit Hilfe von Frequenzvariationen erlauben wir es unseren heutigen Systemen, sich an unterschiedliche Lastanforderungen anzupassen und damit die Leistungsaufnahme zu reduzieren. Wenn ein Echtzeit-Interrupt an unserem System anliegt und dieser behandelt werden soll, kann es passieren, dass das System bei der Anpassung der Frequenz eine höhere Latenz aufweist. Daher sollten diese Frequenzanpassungen vermieden werden. Dies passiert bei Linux durch Auswahl von Frequency-Governors. Wenn die Frequenz nicht reduziert werden soll, kann der Performance-Governor verwendet werden. Auf der Linux-Command-Line lautet der Aufruf:
cpufreq.governor=performance
Zu beachten ist, dass dann natürlich die Leistungsaufnahme auch ansteigt und es auch zu einer stärkeren Erwärmung des Systems kommt und bei manchen Systemen sogar zu einer Reduzierung der Lebensdauer des Microcontrollers. Hier sollten die Randbedingungen aus der Hardware beachtet werden.
Eine weitere und noch bedeutendere Quelle von unerwarteten Latenzen sind die CPU-C-States. Dabei handelt es sich um Schlafstadien, in welche die CPU versetzt werden kann, wenn nichts gerechnet werden soll. Beim Aufwachen aus diesen Stadien sind zum Teil immense Latenzen durch die Hardware verursacht zu erwarten. Von Linux aus kann man diese C-States auf der Kernel-Command-Line abschalten:
cpuidle.off=1
Auch hier ist wieder zu beachten, dass dadurch eine höhere Leistungsaufnahme und Wärmeentwicklung verursacht werden.
Kernel-Configuration
In der Linux-Kernel-Configuration ist der RT-Preemption-Patch zu aktivieren mit dem Schalter:
CONFIG_PREEMPT_RT_FULL=y
Außerdem sind alle Debug-Optionen unter Kernel hacking für das produktive System zu deaktivieren. Diese Schalter heißen CONFIG_DEBUG_*. Bei der Verwendung einer Linux-Distribution ist zu beachten, dass diese Schalter bei den meisten Distributionen default eingeschaltet sind.
SEMINAR-TIPP
Embedded-Linux-Woche
In den Kursen der Embedded-Linux-Woche führen unsere Referenten Sie Schritt für Schritt in die Linux-Welt ein. Fortgeschrittene können sich im Seminarangebot "Echtzeit-Linux und Systemprogrammierung" vom einfachen Embedded-Linux-User zum systemnahen Echtzeit-Entwickler weiterbilden. In nur fünf Tagen erarbeiten Sie sich fundiertes Fachwissen um den Linux-Kernel, Prozessverwaltung, Tracing, Ressoucenverwaltung, Hardware-Anbindung und vieles mehr, so dass Sie sich schon bald als waschechten Linux-Experten bezeichnen können!
Andreas Klinger wurde 2011, 2014 und 2018 als Referent auf dem ESE Kongress mit dem Best Speaker Award ausgezeichnet.
(Bild: IT-Klinger)
Andreas Klinger ist selbständiger Trainer und Entwickler. Seit Abschluss des Studiums der Elektrotechnik im Jahre 1998 arbeitet er im Bereich der systemnahen Softwareentwicklung mit den Schwerpunkten Kernel-Treiber, Embedded-Linux und Echtzeit. Als Spezialist für Linux beschäftigt er sich mit dem internen Aufbau des Kernels, den Systemmechanismen sowie vor allem mit deren Einsatz in Embedded Systemen. Contributor zum Linux-Kernel und anderen Open-Source-Projekten.
Dieser Beitrag wurde mit freundlicher Genehmigung des Autors aus dem Tagungsband des ESE Kongress 2023 übernommen. (sg)