Linux-Treiber entwickeln: | ||
---|---|---|
Zurück | Kapitel 9. Über das Schreiben eines guten, performanten Treibers | Weiter |
Für den Treiberentwickler sind Zeitaspekte wichtig:
In einem Treiber werden für viele Aufgaben Zeiten und ebenso ein »Zeitgefühl« benötigt.
Die eigene Komponente – der Treiber – darf das Zeitverhalten des Systems nicht negativ beeinflussen.
Kenntnisse über den inneren Aufbau des Systems und insbesondere über das Zeitverhalten erweitern die Einsatzmöglichkeiten des Linux-Kernels. Der Griff zu einer echtzeitfähigen Variante, z.B. zu RTAI oder RT-Linux, kann in vielen Fällen unterbleiben, wenn Teile der Echtzeitapplikation im Kernel realisiert werden.
Zeiten und das Zeitgefühl. Wie im Treiber Zeiten erfasst werden können respektive wie der Treiber ein Zeitgefühl bekommt, ist bereits im Kapitel Vom Umgang mit Zeiten angeschnitten worden. Im Wesentlichen sind die folgenden Punkte zu beachten:
Sehr genaue Zeitmessungen sind über die Auswertung des Taktzyklenzählers moderner Prozessoren möglich. Vorsicht ist jedoch bei Prozessoren geboten, deren Taktfrequenz dynamisch eingestellt werden kann (beispielsweise Intel-Prozessoren mit Speedstep- oder AMD-Prozessoren mit PowerNow-Technologie).
Über Timer-Softirqs (siehe Kapitel Timer-Funktionen) lassen sich Funktionen zu bestimmten Zeitpunkten mit einer Auflösung von 1 ms aufrufen. Allerdings hängt die Genauigkeit insbesondere von der Interrupt-Latenzzeit des verwendeten Kernels bzw. von der eingesetzten Hardware ab.
Eine noch höhere zeitliche Auflösung für die Verwendung der Timer-Softirqs kann bekommen, wer die Frequenz, mit der der Systemtimer Interrupts auslöst, erhöht.
So wie ein PC eine so genannte Realtime-Clock besitzt, haben andere Rechnersysteme noch freie Timer, die verwendet werden können, um sehr genau Interrupts auszulösen. Innerhalb der zugehörigen ISR kann ein Tasklet gescheduled werden, welches mit sehr guter Auflösung und hoher Genauigkeit abgearbeitet wird.
Für Verzögerungen innerhalb des Treibers stehen die Funktionen udelay und mdelay zur Verfügung (siehe Kapitel Aktives Warten).
Dank des 64 Bit breiten Jiffies-Zählers lassen sich beinahe beliebig lange Zeitdifferenzen erfassen.
Für das Rechnen mit Zeiten auf Basis der Jiffies stehen Makros zur Verfügung (siehe Kapitel Relativ- und Absolutzeiten).
Einfluss auf das Zeitverhalten. Das Echtzeitverhalten eines Betriebssystems wird – vereinfacht – durch zwei Werte charakterisiert: der Interrupt- und der Task-Latenzzeit. Die Interrupt-Latenzzeit gibt die Zeit an, die zwischen dem Auslösen des Interrupts und dem Start der zugehörigen ISR maximal (im Worst Case) vergeht. Die Task-Latenzzeit gibt die Zeit an, die zwischen dem Eintreffen eines Ereignisses (z.B. einem Interrupt) und dem Start der zugehörigen Task vergeht.
Auf User-Ebene ist insbesondere die Task-Latenzzeit interessant, während innerhalb des Kernels selbst die Interrupt-Latenzzeit die größere Rolle spielt.
Bei dem Entwurf und bei der Realisierung eines Treibers sind folgende Regeln einzuhalten:
Alle langen Aktivitäten im Kernel nehmen den Applikationen Rechenzeit weg. Folglich sollten unnötige Berechnungen unterbleiben.
Da Funktionen, die auf Kernel-Ebene ablaufen, höher priorisiert sind als Applikationen, beeinflussen diese direkt die Wartezeit der Anwendung (Task-Latenzzeit). Noch einmal: Funktionen sollen zeitlich kurz gehalten werden. Möglicherweise ist hier ein umfangreicher, dafür aber schnell abzuarbeitender Code einem kompakten Code vorzuziehen.
Die Latenzzeiten kommen zustande, wenn Interrupts gesperrt sind und wenn kritische Abschnitte geschützt werden müssen (z.B. über Semaphore oder Spinlocks):
Interrupt-Service-Routinen müssen kurz gehalten werden.
Die im Kontext eines Interrupts möglicherweise notwendig geworden längeren Berechnungen sind in ein Tasklet auszulagern.
Kritische Abschnitte sind nach Möglichkeit generell zu vermeiden. Ist dies nicht möglich, müssen sie kurz gehalten werden.
Es sollte überprüft werden, ob sich ein langer, kritischer Abschnitt nicht in zwei kurze kritische Abschnitte aufsplitten lässt.
Ein Semaphor beeinflusst das Zeitverhalten in anderer Weise als ein Spinlock. Daher sollte die Verarbeitungszeit innerhalb eines kritischen Abschnittes abgeschätzt werden. Ist diese Verarbeitungszeit kürzer als die Prozesswechselzeit (Context-Switch), ist ein Spinlock einem Semaphor vorzuziehen. Hierdurch lassen sich die Kontextwechselzeiten einsparen. Ist dagegen die Verweildauer innerhalb des kritischen Abschnittes lang, führt die Verwendung eines Semaphors zu kürzeren Interrupt-Latenzzeiten.
Die verwendete Methode zum Schutz des kritischen Abschnittes sollte an die spezifischen Gegebenheiten angepasst werden. Eine Hilfestellung dazu bietet Tabelle Der Schutz kritischer Abschnitte aufgrund der beteiligten Instanzen. So gilt es beispielsweise zu überprüfen, ob anstelle eines normalen Spinlocks nicht ein RW-Lock eingesetzt werden kann.
Um den Einsatz des Treibers in eingebetteten Systemen zu ermöglichen, sollte »Management-Code« per bedingter Kompilierung ausschaltbar sein. Codeteile, die nicht unbedingt notwendig sind, werden dabei weggelassen; der Treiber wird schlanker und schneller (siehe hierzu auch Abbildung Einsparung von Code durch Verwendung von »#define«).
Mit Ressourcen, die von mehreren Komponenten genutzt werden, muss sorgsam umgegangen werden. Das Zeitverhalten anderer Kernel-Komponenten würde beispielsweise beeinflusst werden, falls eine Funktion, die in die Event-Workqueue eingehängt wird, selbige schlafen legt. Das muss vermieden werden.
Innerhalb des Kernels wird anders programmiert als auf User-Ebene. Bei der Realisierung des Treibers ist auf effiziente Programmierung – die die Verwendung von Goto einschließt – zu achten.
Funktionen, die nicht echtzeitfähig sind, sollten nach Möglichkeit vermieden werden. Hierzu gehört beispielsweise das dynamische Allozieren von Speicher mittels kmalloc.
Zeitaufwendige Aktionen können möglicherweise während einer zeitlich unkritischen Initialisierung vorgenommen werden (zum Beispiel das Starten von Kernel-Threads: Ein Kernel-Thread könnte nicht erst gestartet werden, wenn eine Anforderung eintrifft, sondern prophylaktisch bereits beim Laden des Treibers).
Echtzeit-Applikationen mit einem Standard-Linux. Das Zeitverhalten eines Standard-Linuxsystems reicht für einige Echtzeit-Anwendungen nicht aus. Das gilt sowohl für den Bereich der weichen Echtzeit (zum Beispiel Multimedia-Anwendungen) als auch für harte Realzeit-Systeme (vielfach Steuerungssysteme). In Kernel 2.6 hat sich einiges getan, um das Echtzeitverhalten – sowohl das deterministische Verhalten als auch das Verhalten bezüglich der Rechtzeitigkeit – zu verbessern (Stichwort Preemption im Kernel). Dennoch: Ein schlecht erstellter Treiber kann das ganze Zeitverhalten des Kernels kompromittieren.
Doch es gibt Möglichkeiten, das Zeitverhalten eines Standard-Linux zu verbessern beziehungsweise Mechanismen des Kernels zu nutzen, um das System in Echtzeitanwendungen einsetzen zu können. Damit umgeht man den Einsatz einer Realzeitvariante wie beispielsweise RTAI oder RT-Linux, die sich durch ein deterministischeres Zeitverhalten mit kurzen Zeitkonstanten auszeichnet. Diese Echtzeitvarianten bieten bisher kein ausgereiftes Treiber-Interface. Vielmehr muss der Anwender neue Interfaces erlernen. Solange es möglich ist, sollte man daher bei einem Standard-Linux bleiben, welches dem Entwickler eine bekannte Entwicklungsumgebung und ein stets aktuelles System mit einer erprobten Treiberschnittstelle bietet.
Das können Sie in Angriff nehmen, um das Zeitverhalten des Kernels zu verbessern:
Entfernen bzw. disablen Sie aus dem Kernel alle, das Zeitverhalten störende Komponenten. So müssen Sie beispielsweise das Umschalten zwischen Text- und Grafikkonsole verhindern. Eine sorgfältige Auswahl der im Kernel befindlichen Komponenten verbessert das Zeitverhalten des Kernels immens.
Messen Sie das Zeitverhalten des Kernels aus. Verlässliche Informationen über das Zeitverhalten gibt es kaum. Nicht verwunderlich, da schließlich jede Hardware unterschiedlich ist und nicht nur aufgrund ihrer Leistungsfähigkeit das Realzeitverhalten beeinflusst.
Um das Zeitverhalten auszumessen, müssen Sie ein geeignetes Messprogramm schreiben und die Messwerte erfassen, wenn das System unter Last steht.
Das ist zugegebenermaßen keine ganz optimale oder befriedigende Lösung, sondern vielmehr ein pragmatischer Ansatz, um überhaupt an verwertbare Messgrößen zu kommen.
Entwerfen Sie Ihre Realzeit-Applikation so, dass Sie harte Echtzeitfunktionalitäten aus der User-Ebene in die Kernel-Ebene transferieren. Innerhalb der Kernel-Ebene haben Sie sowohl ein deterministischeres Zeitverhalten als auch eine kürzere Reaktionszeit. Außerdem lässt sich dadurch die Kontext-Wechselzeit einsparen.
Die Echtzeitfunktionalitäten selbst können Sie in Form eines Tasklets, eines Timers oder eines Kernel-Threads realisieren. In besonders zeitkritischen Fällen kann auch die ISR selbst genutzt werden.
Zurück | Zum Anfang | Weiter |
Realisierung | Nach oben | Kernel generieren und installieren |