Buchgenerator (deaktivieren)

Programmieren: Treiber

Aus Wikibooks

Wechseln zu: Navigation, Suche
Wikibooks buchseite.svg Zurück zu Datenbanken | One wikibook.svg Hoch zu Inhaltsverzeichnis | Wikibooks buchseite.svg Vor zu Internet und Netzwerk


Inhaltsverzeichnis

[Bearbeiten] Linux

[Bearbeiten] Mac OS X

[Bearbeiten] Windows

[Bearbeiten] Treiberprogrammierung unter Windows 2000

Unter Windows ist ein Gerätetreiber eine Sammlung von Funktionen, die im Kernelmode laufen und die vom I/O-Manager erwartet und benötigt werden. Es gibt verschiedene Arten von Treibern, die sich alle von den Kernelmode-Treibern ableiten. Dazu zählen die Filesystem-, PnP- und Legacy-Treiber. Bis Win98 wurden überwiegend die Legacy-Treiber eingesetzt. Mit Windows 2000 wurde das neue w:Windows Driver Model (WDM) vorgestellt, dessen Treiberarchitektur sich direkt von den PnP-Treibern ableiten lässt. Die WDM-Treiber unterteilen sich wiederum in Class-, Mini, Monolithic function- und Filter-Treiber. Die Filtertreiber haben meist die Funktion, andere Treiber (z.B. Bustreiber) zu überwachen und zu unterstützen. Mit Hilfe der Filtertreiber lassen sich zudem Applikationen wie Netzwerksniffer realisieren.

[Bearbeiten] Voraussetzungen für die Treiberentwicklung

Um einen Treiber für Windows 2000 entwickeln zu können, müssen einige Bedingungen erfüllt sein. Auf der Hardwareseite muss man eigentlich nur die Systemanforderungen für MS Visual Studio 6.0 erfüllen. Für gewöhnlich benötigt man allerdings einen zweiten Rechner, das sog. Ziel- oder Targetsystem. Das ist der PC, auf dem der neue Treiber installiert, getestet und debuggt wird. Es gibt allerdings auch die Möglichkeit, den Treiber auf dem Host/Entwicklungsrechner zu testen. Dabei ist aber entweder kein Debuggen möglich, oder man muss auf teure Spezialsoftware wie NuMegas DriverStudio und dem integrierten SoftIce zurückgreifen. Allerdings ist diese Software um ein vielfaches teurer als ein gewöhnlicher Desktop PC.

Auf Softwareseite benötigt man:

  • einen C Compiler

Am besten MS Visual Studio 6.0. Man kann natürlich auch andere C Compiler benutzen; aber dabei muss sichergestellt sein, dass das Ausgabeergebnis des Fremdcompilers exakt dem von Microsoft entspricht. Nur dadurch wird garantiert, dass der Treiber auch tatsächlich auf allen Win2000-Rechnern läuft.

  • Das Plattform SDK (Win32 API Developer Kit)

Das ist eine Sammlung von Bibliotheken und Headerfiles, die für die Entwicklung benötigt werden und für gewöhnlich im Installationsumfang von MS VC++ 6.0 enthalten ist.

  • Das Driver Developer Kit (DDK)

Das ist ebenfalls eine sehr umfangreiche Sammlung von Bibliotheken und Headerdateien, die für die Treiberentwicklung unentbehrlich sind. Es gibt für jede Windowsversion ein eigenes DDK, aber häufig funktioniert ein Treiber, der mit dem Win98 DDK geschrieben wurde, auch unter Win2000 oder WinXP. Garantiert ist das aber nicht. Das DDK wird von MS als ISO-File zum freien Download angeboten, kann aber auch über die Homepage bestellt werden.

[Bearbeiten] Andere Sprachen als C für die Treiberentwicklung?

Auch wenn die Meinungen da bereits auseinandergehen, sollte man auch C++ verwenden können. Der Einsatz von Klassen und OOP ist aber nicht möglich. Theoretisch ist es auch möglich Assembler zu benutzen. Der Mehraufwand steht aber dank moderner Compiler für die meisten Anwendungen nicht unbeding in einem Verhältnis zu der erzielbaren Verbesserung. Andere Hochsprachen wie Basic oder Pascal scheiden jedoch so gut wie völlig aus. Um mit diesen Sprachen einen Treiber zu entwickeln, müsste man als erstes die größten Teile des DDK in diese Sprache übersetzen und dann völlig auf die Verwendung des Kernelstacks etc. verzichten. Der Aufwand für ein solches Unternehmen wäre sehr groß und völlig ineffizient.

[Bearbeiten] Kompilierung und Installation des eigenen Treibers

Wenn alle Projekteinstellungen korrekt vorgenommen wurden und der Quellcode fertig gestellt ist, kann man mit VS6.0 ein Debug Build des Treibers erstellen. Das Ergebnis sollte eine .sys Datei sein. Falls man einen WDM-Treiber erstellt, benötigt man noch ein Installationsskript. In dieser Inf-Datei werden ID-Nummer, Gerätename und weitere treiberspezifische Daten hinterlegt, die das OS benötigt, um den Treiber im System zu installieren. Bei einem Legacytreiber wird der Treiber mit Hilfe des Kommandozeilenprogrammes: „net start/Stop treibername“ gestartet oder wieder beendet.

[Bearbeiten] Treiberprogrammierung

Genau wie der Rest des Windowskernel ist ein Treiber ‚objekt–basierend’ aufgebaut. Das heißt, dass Daten und Strukturen, die logische Einheiten bilden, auch zu Strukturobjekten zusammengefasst werden. Objekt-basierend unterscheidet sich von objekt-orientiert dahingehend, dass bei der OOP zu den Objektklassen auch noch die zugehörigen Methoden (Funktionen) gekapselt werden. Ein typischer Treiber ist in drei Objekttypen organisiert.

  • DriverObject
    • Bildet eine logische Verwaltungseinheit für den Treiber selbst
    • Verwaltet eine Liste mit den zugehörigen Geräten
    • Verwaltet die Verweise (Funktionszeiger) auf die Algorithmen des Gerätes (MajorFunction)
    • Verweist auf das erste DeviceObject
  • DeviceObject
    • Bildet eine logische Verwaltungseinheit für ein Gerät (Hardware)
    • Enthält Datenobjekte (Variabeln und Konstanten) für die Verwaltung des Gerätes (Flags, DeviceQueue für IRPs...)
    • Verweist auf das nächste, eventuell vorhandene Gerät
    • Verweist zurück auf sein zugehöriges DriverObject
    • Verweist auf angeschlossene DeviceExtension
  • DeviceExtension
    • Weitere gerätespezifische Datenelemente (Port, IRQ...)
    • Auch Zwischenspeicher für die IO-Bearbeitung
    • Verweist auf zugehöriges DeviceObject

Ein DriverObject verweist immer auf min. ein DeviceObject. Einem DeviceObject ist immer genau eine DeviceExtension zugeordnet. Durch die gegenseitigen Verweise kann man im Quelltext immer zu jeweils zugehörigen, über- oder untergeordneten Objekt springen.


Die Routinen eines Treibers lassen sich in verschiedene Gruppen aufteilen:

  • Initialisierungs- und Aufräumroutinen
  • Dispatch Routinen
  • Datentransfer
  • Resourcensynchronisierung
  • Weitere

Eine Routine, die beim Starten des Gerätes vom IO-Managers von Windows automatisch aufgerufen wird, und deshalb unverzichtbar ist, ist die DriverEntry. Bei Legacy Treibern sorgt die DriverEntry für die Erkennung der Hardware und Erzeugung der Device-Objekte. Zudem werden die Dispatchroutinen eingetragen und ein symbolischer Verweis im System angelegt, damit User-Applikationen auf den Treiber und damit das Gerät zugreifen können.

Bei den Dispatchroutinen handelt es sich um Funktionen des Treibers, die ausgeführt werden, wenn die User-Applikation eine bestimmte Anfrage an das Gerät stellt, zum Beispiel Daten schreiben oder lesen. Aber auch für den Treiber fundamentale Routinen zum Laden (IRP_MJ_CREATE) und Entfernen (IRP_MJ_CLOSE) des Treibers aus dem System.

Bei den WDM Treibern hat die DriverEntry nur noch die Funktion des Eintragens dieser Dispatchroutinen. Das Erkennen und Eintragen der Gerätehardware übernimmt hier die Funktion AddDevice. Außerdem kommen hier noch Dispatch-Power Routinen hinzu, die das Gerät für den Plug-and-Play Betrieb steuern. Viele Aufgaben, die bei Legacy ebenfalls noch mühsam selber programmiert werden mussten, werden bei WDM vom Bustreiber übernommen. Damit ist es bei WDM auch nicht mehr nötig Portadressen umzurechnen. Für Windows Vista ist bereits ein neues Treibermodell WDF (Windows Driver Framework) geplant, welches dem Programmierer noch mehr die Arbeit erleichtern und die Treiber stabiler machen soll.

[Bearbeiten] Datentransfer zum und vom Treiber

Für gewöhnlich hat periphere Hardware besondere Aufgaben, die die CPU nur sehr schlecht oder sehr langsam ausführen könnte. Um also die Spezialhardware die besonders komplizierten und für sie viel besser zu lösenden Aufgaben erfüllen zu lassen, ist es nötig, dass User-Applikationen Befehle an die Hardware schicken und Daten in beide Richtungen übertragen werden können. Da aber User-Applikationen und Treiberroutinen in zwei völlig verschiedenen Ebenen im System ablaufen, sind viele Zwischenschritte für diesen Datentransfer nötig. Der typische Ablauf einer Anforderung vom Userspace in den Kernelspace sieht folgendermaßen aus: Zuerst beginnt eine Vorverarbeitung im IO Manager. Der IO Manager ist ein Bestandteil des Teils des Kernels, den man als Executive bezeichnet. Diese Executive sorgt vereinfacht ausgedrückt dafür, dass Aufrufe aus dem Subsystem (für gewöhnlich die Win32-API) in Systemrufe umgewandelt werden. So wird beispielsweise aus dem Win32-API Aufruf WriteFile() der NT-Systemruf NTWriteFile(). Die Systemrufe könnten noch direkt vom Userspace aus aufgerufen werden, sind aber nicht ausreichend dokumentiert, und es wird dringend davon abgeraten. Der IO Manager erzeugt aus dem Aufruf ein IO Request Packet (IRP) und übergibt dieses an den betreffenden Gerätetreiber. Es folgt die treiberspezifische Weiterverarbeitung. Anhand des IRP kann der Treiber entscheiden, welche Dispatchroutine aufgerufen werden muss. An diese wird das IRP weitergereicht, und die Daten können verarbeitet werden. Erst jetzt werden Routinen der Executive aufgerufen, die wiederum Routinen der Hardware Abstraction Layer (HAL) kapseln. Diese Routinen sind für gewöhnlich in Plattformspezifischem Assemblercode hinterlegt und greifen nun direkt auf die Hardware zu. Es kann noch zu Interruptverarbeitung kommen; und wenn die Routinen des HAL abgearbeitet sind, werden treiberspezifische Nachbearbeitungen erst im Treiber, dann wieder im IO Manager vorgenommen. Die User-Applikation erfährt über Statusvariabeln und Flags über den Erfolg der Anfrage. Im Kern besteht eine Berechnung im Gerät und der Datentransfer also aus einer Schreiboperation, der Interruptbehandlung und einer Leseoperation vom Gerät. Falls es zu Fehlern während der Bearbeitung kam, muss man beachten, dass die NT Fehlerkodes über Errormapping in Win32 Fehlerkodes konvertiert werden müssen. Für die weitere Kommunikation mit einem Gerät stehen häufig Ports und Register zur Verfügung.

Für den direkten Datentransfer zwischen Treiber und Userapplikation stehen drei verschiedene Methoden bereit.

  • Buffered IO
    • Speicherblöcke werden zwischen Kernel- und Userspeicher kopiert
  • Direct IO
    • Der Kernel kann im Userbuffer arbeiten, wenn diese über eine Memory Descriptor List adressierbar gemacht wird

sowie eine sogenannte "Neither Buffered nor Direct" Methode, bei welcher der Treiber direkt aus dem Speicher der Anwendung liest.


Wikibooks buchseite.svg Zurück zu Datenbanken | One wikibook.svg Hoch zu Inhaltsverzeichnis | Wikibooks buchseite.svg Vor zu Internet und Netzwerk


Persönliche Werkzeuge