- Die Unterstützung von Linux für Echtzeit-Preemption wartet seit fast 20 Jahren auf die Aufnahme in den Mainline-Kernel; Thomas Gleixner sagte auf der Linux Plumbers Conference 2023, dass das letzte große Hindernis
printk()sei - Ziel ist, dass Prozesse mit der höchsten Priorität mit vorhersehbar kurzer Latenz ausgeführt werden können; dafür wurden über lange Zeit viele Kernbereiche des Kernels neu geschrieben
printk()kann in praktisch jedem Kontext aufgerufen werden und ist daher weit komplizierter als einfache Log-Ausgabe; die aktuelle synchron arbeitende Ausgabe kollidiert mit den Echtzeit-Latenzzielen- Seit 2018 sind rund 300 Patches upstream gelandet oder warten in linux-next; die verbleibenden Aufgaben sind ein Handover-Mechanismus für dringende Meldungen und die sichere Behandlung von Konsolentreibern
- Wenn die Bereinigung von
printk()abgeschlossen ist und der restliche Echtzeit-Code in linux-next bereitsteht, wäre sogar eine Zusammenführung im selben Merge Window möglich, aber Gleixner will keine Fertigstellungstermine mehr vorhersagen
Fast 20 Jahre Arbeit an Echtzeit-Preemption
- Linux-Echtzeitunterstützung tauchte bei LWN erstmals 2004 auf und wirkte lange so, als sei sie „fast fertig“
- LWN verwendete schon 2009 den Titel the realtime preemption endgame, doch auf der Linux Plumbers Conference 2023 sagte Gleixner, dass das Ende nun wirklich nahe sei
- Für Gleixner persönlich ist es eine Aufgabe von fast 25 Jahren
- Er begann 1999 mit der Arbeit an Linux-Echtzeitunterstützung
- Das Projekt selbst läuft inzwischen seit fast 20 Jahren
- Er sagte, nach Abschluss der Arbeit werde es „a big party“ geben, aber als letzte große Hürde bleibt
printk()
Welche Latenzen Echtzeit-Preemption verringern soll
- Das Ziel von Echtzeit-Preemption ist, dass Prozesse mit der höchsten Priorität stets mit minimaler und vorhersehbarer Latenz ausgeführt werden können
- Dafür muss der Kernel in möglichst vielen Situationen präemptierbar sein; Ausnahmen müssen auf einen engen und klar definierten Bereich beschränkt werden
- Das Grundprinzip steht seit Langem fest, doch die Lösung der Detailprobleme hat viel Zeit gekostet
- Dabei wurden viele Teile des Kernel-Kerns neu geschrieben, und der Nutzen geht weit über Echtzeit-Anwendungsfälle hinaus und kommt dem gesamten Kernel zugute
Warum printk() das letzte Hindernis ist
- Wenn Kernel-Code Meldungen an Konsole oder Logs senden muss, ruft er
printk()oder darauf aufbauende Funktionen auf - Das wirkt wie eine einfache Ausgabe, aber
printk()muss in nahezu jedem Kontext funktionieren- Es kann sogar in non-maskable interrupt-Handlern aufgerufen werden
- Es kann innerhalb eines anderen
printk()-Aufrufs erneut aufgerufen werden - Bei Systemabstürzen kann die ausgegebene Information entscheidend sein, daher lässt sich der Aufrufkontext kaum einschränken
- Diese Anforderungen führen dazu, dass bei
printk()Probleme rund um Parallelität, Locking und Treiberverarbeitung komplex miteinander verflochten sind - Im aktuellen Kernel ist
printk()vollständig synchron aufgebaut- Der Aufruf kehrt erst zurück, wenn die Meldung an alle konfigurierten Ziele übertragen wurde
- Gleixner bezeichnete diese Struktur als „stupid“
- Besonders während des Bootens ist der Großteil der Ausgabe oft bloß Rauschen, dennoch muss gewartet werden, bis alles übertragen ist
- Diese Wartezeit steht in direktem Widerspruch zu den Latenzen, die Echtzeitarbeit eigentlich verringern soll
- Echtzeitentwickler haben die
printk()-Ausgabe schon vor langer Zeit in einen separaten Thread verschoben, um sie asynchron zu machen, doch dieser Code war eher eine Sammlung von Hacks als eine grundlegende Lösung
Überarbeitung von printk() seit 2018
- Das
printk()-Problem wird seit 2018 ernsthaft angegangen; rund 300 Patches sind upstream gelandet oder warten in linux-next - Derzeit laufen die letzten drei Patch-Serien, die für den Abschluss der Arbeit nötig sind
- Eine der schwierigsten Detailaufgaben ist der Handover-Mechanismus
- Wenn der Kernel dringende Meldungen wie Crash-Informationen ausgeben muss, kann es nötig sein, die Kontrolle über eine Konsole zu übernehmen, die gerade Meldungen mit niedriger Priorität ausgibt
- Das in jedem Kontext sicher zu tun, ist nicht einfach
- Eine weitere Aufgabe ist das Markieren von Konsolentreibern, die sich in bestimmten Kontexten nicht sicher verwenden lassen
- Wenn etwa während eines non-maskable interrupt eine Meldung ausgegeben werden soll, dafür aber ein Umschalten des Videomodus nötig wäre, kann das nicht funktionieren
- Gleixner sagte, dass sich das grundlegende Konzept im letzten Jahr nicht geändert habe
- Im Kernel gibt es 76 Konsolentreiber, die angepasst werden müssen
- Der Handover-Code wurde so geändert, dass nicht alle Treiber auf einmal korrigiert werden müssen, sondern einzeln aktualisiert werden können
- Weitere Diskussionen zur jüngsten
printk()-Arbeit gibt es in diesem Artikel
Asynchrone Ausgabe und Bedingungen für die Mainline-Zusammenführung
- Auf die Frage von Masami Hiramatsu, welche Kernel-Meldungen synchron ausgegeben werden müssten, antwortete Gleixner, dass fast alles asynchron werden solle
- Asynchrone Ausgabe reduziert die durch
printk()-Aufrufe entstehende Latenz und ermöglicht einen separaten Kernel-Thread pro Konsole- So kann eine schnelle Konsole in ihrem eigenen Tempo arbeiten, ohne auf die langsamste Konsole warten zu müssen
- Der Code wurde so geändert, dass wichtige Meldungen vollständig in den Message-Buffer kopiert werden, bevor die erste Zeile ausgegeben wird
- Das ist eine Schutzmaßnahme für den Fall, dass ein fehlerhafter Konsolentreiber das gesamte System beschädigt
- Für eine sicherere Ausgabereihenfolge wird zuerst auf bekanntermaßen sichere Konsolen geschrieben
- Wenn es zum Beispiel einen persistent-memory store gibt, wird die Meldung zuerst dort gespeichert, bevor sie an ein physisches Gerät gesendet wird
- So bleibt die Ausgabe erhalten, selbst wenn ein fehlerhafter Treiber das System zum Absturz bringt
- Gleixner sagte, dass das Ziel zwar nahe sei,
printk()aber schwer vorherzusagen bleibe, weshalb er keine Fertigstellungstermine mehr nennen wolle - Dennoch hoffe er, dass der restliche Echtzeit-Preemption-Code noch vor dem 20. Jahrestag Ende 2024 in die Mainline aufgenommen werde
- Auf die Frage von Clark Williams, ob der restliche Echtzeit-Code nach dem Upstreaming der
printk()-Patches im selben Merge Window aufgenommen werde, antwortete Gleixner bedingt mit „yes“- Wenn der gesamte Code in linux-next staged ist und bereit aussieht, könnte man es versuchen
1 Kommentare
Meinungen auf Hacker News
QNX macht das seit Jahrzehnten richtig. Der Mikrokernel hat für alles, was er tut, eine Obergrenze, und der Code umfasst nur einige Zehntausend Zeilen.
Der Mikrokernel kümmert sich nur um Speicherallokation, CPU-Dispatching und Nachrichtenübermittlung zwischen Prozessen. Alles Weitere, einschließlich Treibern und Loggern, läuft im Userspace und kann von Threads mit höherer Priorität präemptiert werden.
Der QNX-Kernel verarbeitet keine Strings. Kein Parsing, keine Formatierung, keine Nachrichten. Linux ist für Echtzeit zu aufgebläht geworden, und die Architektur passt grundsätzlich nicht zu Echtzeit, weil Millionen Zeilen Kernel-Code vollständig präemptierbar gemacht werden müssen. Deshalb hat die Behebung 20 Jahre gedauert.
Der größte Beitrag zum Kernel-Design dürfte vermutlich die durchgängige Nutzung von Capabilities sein, um Kontrolle sicher und zugleich flexibel in den Userspace auszulagern.
Die Aufblähung des Kernels an sich stört mich nicht besonders. In Linux fließt viel Entwicklungszeit, und auch wenn der Desktop nicht so hohe Priorität hat wie Server, dürfte Arbeit an einem performanten Kernel für Bereiche wie mobile Geräte auch Desktop-Nutzern zugutekommen.
main-Funktion in C oder einer C-ähnlichen Sprache.mainkoordiniert nur Aufrufe anderer Funktionen; hier initialisiert der QNX-Kernel zwar weniger, aber das Grundkonzept ist ähnlich.Ich bin kein Kernel-Entwickler, aber diese Art, es einfach zu halten, wirkt gut.
Es gibt ein Beispiel dafür, wie der Kernel selbst in einem sterbenden System noch irgendwie versucht, Logmeldungen auszugeben, und wie das in realen Produktionsumgebungen genutzt wird.
https://netflixtechblog.com/kubernetes-and-kernel-panics-ed6...
Ich frage mich, ob sich damit einige Hardware-/Software-Kombinationen, die für Echtzeit gebaut wurden, ziemlich gut ersetzen lassen. Inzwischen gibt es viele günstige, stromsparende und hoch getaktete ARM- und x86-Chips.
Da die Taktraten so hoch sind, könnte es oft weniger wichtig sein, perfekte Echtzeitfähigkeit zu haben, weil selbst bei Ausreißern noch viele freie Zyklen übrig sind. Ich weiß, dass das weder elegant noch effizient ist, aber manchmal schlägt Standardware eben Präzision.
Eine einzige schlecht gebaute Aufgabe kann den Kernel blockieren und daran hindern, nützliche Arbeit zu leisten. Der Kern harter Echtzeit ist: „Nichts kann die Ausführung dieser wichtigen Aufgabe verhindern.“ In der Automobil- oder Luft- und Raumfahrt müssen Steuerungssysteme unter allen Umständen laufen.
Wenn man wirklich Echtzeit braucht, dann braucht man sie wirklich; „nah genug dran“ gibt es nicht. Das ist allerdings nur mein Eindruck als Außenstehender.
Ein Betriebssystem stört zu stark, selbst wenn es ein RTOS ist. Ich weiß nicht, was diese Änderung daran ändern wird. Es hängt allerdings von der Anwendung ab, und es gibt viele Fälle, die nur „nahezu Echtzeit“ brauchen; dafür könnte es nützlich sein.
Die Diskussion hier konzentriert sich auf die Unterscheidung zwischen „harten“ und „weichen“ Echtzeitanwendungen. Für harte Echtzeit will man vermutlich von vornherein kein Allzweckbetriebssystem wie Linux verwenden, und bei weicher Echtzeit wie Videokonferenzen oder Audiowiedergabe ist es keine Katastrophe, wenn es gelegentlich ruckelt oder ein paar Frames ausfallen.
Die Argumentation lautet, dass RT Linux für solche weichen Echtzeitfälle eine starke Lösung sein wird. Die vorgeschlagenen weichen Anwendungsfälle sind aber schon heute mit Embedded Linux möglich. Niedriglatente Software-Video- oder Audiowiedergabe war nicht unmöglich; das ging auch schon vor 20 Jahren.
Das Problem entsteht, wenn auf einem ausgelasteten System häufig nicht präemptierbares I/O dazwischenfunkt, doch in Embedded-Umgebungen kommt das selten vor. Es gibt überzeugende Gründe, den Kernel vollständig präemptierbar zu machen und mehr Kontrolle über das Scheduling zu geben, aber das hat wenig mit der Frage zu tun, ob Linux minimale Echtzeitbetriebssysteme oder Bare-Metal-Code ersetzen sollte.
Es ist eher gute Hygiene und führt auch bei Nicht-Echtzeitanwendungen zu einem Betriebssystem, das unter Last besser funktioniert.
Das sind gute Nachrichten, aber selbst wenn der Linux-Kernel echtzeitfähig wird, ist die Hardware wegen Caches und komplexer Magie im Inneren der CPU mit hoher Wahrscheinlichkeit nicht echtzeitfähig.
Große, komplexe Hardware passt nicht wirklich zu echter Echtzeit. Deshalb beschäftigen sich AbsInt und Tools zur Worst-Case Execution Time (WCET) meist mit einfachen CPU-Architekturen. 8051 wird wirklich ewig weiterleben. Nebenbei gibt es auch Zephyr RTOS.
Man nimmt einfach an, dass es keinerlei Cache-Hits gibt, maximale Last herrscht usw. Wenn man eine obere Schranke für die benötigte Zeit setzen kann, ist es in Ordnung.
Timer können Quadratur-Encoder-Eingaben entgegennehmen und nur beim Überlauf einen Interrupt senden, oder man kann das GPIO-System an DMA anbinden und ohne CPU-Eingriff Speicher auf Ausgangspins streamen. Auch Streaming an einen DAC oder DMA-Transfers von einem ADC in den Speicher sind möglich. Solche Dinge umgehen für vorhersagbare Latenzen oft den Cache.
Tatsächlich müssen viele Echtzeitsysteme immer weiter wachsende Sensordaten verarbeiten und aggregieren und werden deshalb zunehmend leistungsfähiger.
Als Berufsanfänger habe ich frustrierend viele Vorstellungsgespräche erlebt, in denen die Interviewer nicht wussten, was Echtzeit eigentlich bedeutet. Viele übersehen das im Artikel erwähnte Konzept „und vorhersagbare Latenz“ und scheinen Echtzeit einfach als „schnell“ zu verstehen.
Wenn man ein Bremssystem im Auto steuert, kann „durchschnittliche Latenz 50 ms, maximal 80 ms“ akzeptabel sein, „durchschnittliche Latenz 1 ms, aber beliebig lang und möglicherweise mehrere Sekunden“ dagegen nicht.
Synchrones Logging hat wieder einmal Probleme verursacht. In meiner Firma hatten wir etwas Ähnliches wegen GLOG (Googles Logging-Bibliothek); wenn stdout zum Beispiel eine Datei ist, kann man bei Disk-I/O blockieren.
Wenn unser Service länger als 100 ms stehen blieb, war in 90–99 % der Fälle GLOG die Ursache.
„Was machen Sie, wenn etwas unbedingt geloggt werden muss, aber nicht geloggt werden kann?“ Heutzutage verweise ich einfach auf das CAP-Theorem und sage, dass Logging wie jedes andere verteilte System ist. Vielleicht weil es einen Wikipedia-Artikel mit einem Dreiecksdiagramm und dem Wort „Theorem“ gibt, akzeptieren die Leute das eher.
Danach stellten wir auf UDP-Übertragung um. Ein paar Logs zu verlieren ist besser, als die gesamte Produktion zu verlieren.
$MSFT-Logging-Bibliothek gab es ein Problem der Sorte „die Logging-Bibliothek macht alles kaputt“. Man stelle sich vor, 100 Threads hätten jeweils einen 300 MB großen Logging-Puffer.Natürlich hat das den Speicher zerlegt, und selbst auf der teuersten SKU von Azure App Service stürzte der Server ab.
Das weckt sofort alte Erinnerungen. Vor etwa 17–18 Jahren habe ich einen Kernel für Debian mit RT_PREEMPT kompiliert, um ihn für wissenschaftliche Geräte zu verwenden, die engeres Timing brauchten.
Latenz und Jitter waren sehr beeindruckend. Danach habe ich kaum noch darüber nachgedacht, aber wenn man mit einem Raspberry Pi Embedded-Anwendungen baut und nicht auf einen Mikrocontroller mit RTOS wechseln möchte, dürfte es viele Einsatzmöglichkeiten geben.
Besonders interessant fand ich das, weil ich früher schon einmal den Vorschlag gesehen hatte, Linux als Task eines RTOS laufen zu lassen. Dinge, die harte Echtzeit-Deadlines brauchen, laufen dann im RTOS und bleiben unbeeinflusst von Latenzen, die das virtuelle Speichersystem verursachen kann. Ich erinnere mich nicht, ob das nur eine Idee war oder tatsächlich implementiert wurde, und ich habe auch die Erwähnung, dass RpiOS auf einem RTOS läuft, nur einmal gesehen, daher bin ich neugierig.
Ich frage mich, was das für normale Nutzer bedeutet. Ist das eine Funktion, die man nur in sehr speziellen Situationen aktiviert, oder kann sie auch der breiten Masse ein reaktionsschnelleres System bringen?
Jede Aufgabe bekommt ein Budget X und darf es nicht überschreiten. Wenn der Best Case schnell ist, der Worst Case aber langsam, heißt das, dass das System immer vom Worst Case ausgehen muss.
Das Vermeiden synchroner
printk()-Aufrufe ist genau so ein Beispiel und sollte die Latenz unter Last verbessern, auch wenn RT nicht aktiviert ist. Ich denke, ein vollständig upstream integrierter RT-Kernel wird sich nicht anders verhalten als ein normaler Kernel, solange nicht tatsächlich RT-Prozesse laufen. Der Grund, warum das Upstreaming so lange gedauert hat, war, dass Kompromisse nötig waren, um RT zu ermöglichen; dem Artikel zufolge sind davon inzwischen nicht mehr viele übrig.Denn damit kann man einen aktuellen Mainline-Kernel verwenden, auch wenn Echtzeit-Scheduling benötigt wird.
Ich frage mich, was ihr von Xenomai[1] haltet. Ich nutze es seit Jahren problemlos.
Auf dem BeagleBone Black liegt der Jitter normalerweise im Bereich von einigen hundert Nanosekunden, und ich würde es als „harte“ Echtzeit ansehen. Man kann periodische Aufgaben im Bereich von einigen Dutzend Mikrosekunden schedulen, ohne jemals eine Deadline zu verpassen.
Im Gegensatz zu Real-Time Linux, das Linux selbst präemptierbar machen will, ist Xenomai im Grunde ein eigener Kernel und führt Linux als Aufgabe darüber aus. Es stellt eine ABI bereit, über die benutzerdefinierte Aufgaben neben Linux oder mit höherer Priorität laufen können. So lässt sich zum Beispiel das
printk()-Problem umgehen: Xenomai kümmert sich nicht darum und führt bereitwillig einen Kontextwechsel ausprintkheraus durch, um die Nutzeraufgabe auszuführen.Der Nachteil ist, dass man im Xenomai-Kontext keine normalen Systemaufrufe machen kann. Man kann es zwar, aber natürlich zerstört das das Echtzeitmodell. Wenn man zum Beispiel innerhalb einer Xenomai-Aufgabe
printf()odermalloc()aufruft, ist das nicht präemptierbar. Die Xenomai-ABI bildet vieles von dem nach, was man an Systemaufrufen brauchen könnte, und wenn man damit zufrieden ist, Heap-Allokation selbst zu handhaben, funktioniert das sehr gut.[1]: https://xenomai.org/