Minidriver, Miniport-Treiber und Treiberpaare
Ein Minidriver oder ein Miniporttreiber fungiert als hälfte eines Fahrerpaars. Treiberpaare wie (Miniport, Port) können die Treiberentwicklung erleichtern. In einem Treiberpaar verarbeitet ein Treiber allgemeine Aufgaben, die für eine ganze Sammlung von Geräten gemeinsam sind, während der andere Treiber Aufgaben verarbeitet, die für ein einzelnes Gerät spezifisch sind. Die Treiber, die gerätespezifische Aufgaben verarbeiten, werden unter einer Vielzahl von Namen verwendet, einschließlich Miniporttreiber, Miniklassentreiber und Minidriver.
Microsoft stellt den allgemeinen Treiber bereit, und in der Regel stellt ein unabhängiger Hardwareanbieter den spezifischen Treiber bereit. Bevor Sie dieses Thema lesen, sollten Sie sich mit den Ideen vertraut machen, die in Geräteknoten und Gerätestapeln und E/A-Anforderungspaketen vorgestellt werden.
Jeder Kernelmodustreiber muss eine Funktion namens DriverEntry implementieren, die kurz nach dem Laden des Treibers aufgerufen wird. Die DriverEntry-Funktion füllt bestimmte Elemente einer DRIVER_OBJECT-Struktur mit Zeigern auf mehrere andere Funktionen aus, die der Treiber implementiert. Beispielsweise füllt die DriverEntry-Funktion den Unload-Member der DRIVER_OBJECT-Struktur mit einem Zeiger auf die Unload-Funktion des Treibers aus, wie im folgenden Diagramm dargestellt.
Das MajorFunction-Element der DRIVER_OBJECT-Struktur ist ein Array von Zeigern auf Funktionen, die E/A-Anforderungspakete (IRPs) verarbeiten, wie im folgenden Diagramm dargestellt. In der Regel füllt der Treiber mehrere Member des MajorFunction-Arrays mit Zeigern auf Funktionen (vom Treiber implementiert), die verschiedene Arten von IRPs verarbeiten.
Ein IRP kann nach seinem Hauptfunktionscode kategorisiert werden, der durch eine Konstante wie IRP_MJ_READ, IRP_MJ_WRITE oder IRP_MJ_PNP identifiziert wird. Die Konstanten, die Hauptfunktionscode identifizieren, dienen als Indizes im MajorFunction-Array . Angenommen, der Treiber implementiert eine Dispatch-Funktion, um IRPs zu verarbeiten, die über den Hauptfunktionscode IRP_MJ_WRITE verfügen. In diesem Fall muss der Treiber das Element MajorFunction[IRP_MJ_WRITE] des Arrays mit einem Zeiger auf die Dispatchfunktion ausfüllen.
In der Regel füllt der Treiber einige Elemente des MajorFunction-Arrays aus und lässt die restlichen Elemente auf Standardwerte festgelegt, die vom E/A-Manager bereitgestellt werden. Das folgende Beispiel zeigt, wie Sie die Debuggererweiterung !drvobj verwenden, um die Funktionszeiger für den Parporttreiber zu überprüfen.
0: kd> !drvobj parport 2
Driver object (fffffa80048d9e70) is for:
\Driver\Parport
DriverEntry: fffff880065ea070 parport!GsDriverEntry
DriverStartIo: 00000000
DriverUnload: fffff880065e131c parport!PptUnload
AddDevice: fffff880065d2008 parport!P5AddDevice
Dispatch routines:
[00] IRP_MJ_CREATE fffff880065d49d0 parport!PptDispatchCreateOpen
[01] IRP_MJ_CREATE_NAMED_PIPE fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[02] IRP_MJ_CLOSE fffff880065d4a78 parport!PptDispatchClose
[03] IRP_MJ_READ fffff880065d4bac parport!PptDispatchRead
[04] IRP_MJ_WRITE fffff880065d4bac parport!PptDispatchRead
[05] IRP_MJ_QUERY_INFORMATION fffff880065d4c40 parport!PptDispatchQueryInformation
[06] IRP_MJ_SET_INFORMATION fffff880065d4ce4 parport!PptDispatchSetInformation
[07] IRP_MJ_QUERY_EA fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[08] IRP_MJ_SET_EA fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[09] IRP_MJ_FLUSH_BUFFERS fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[0b] IRP_MJ_SET_VOLUME_INFORMATION fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[0c] IRP_MJ_DIRECTORY_CONTROL fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[0d] IRP_MJ_FILE_SYSTEM_CONTROL fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[0e] IRP_MJ_DEVICE_CONTROL fffff880065d4be8 parport!PptDispatchDeviceControl
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL fffff880065d4c24 parport!PptDispatchInternalDeviceControl
[10] IRP_MJ_SHUTDOWN fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[11] IRP_MJ_LOCK_CONTROL fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[12] IRP_MJ_CLEANUP fffff880065d4af4 parport!PptDispatchCleanup
[13] IRP_MJ_CREATE_MAILSLOT fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[14] IRP_MJ_QUERY_SECURITY fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[15] IRP_MJ_SET_SECURITY fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[16] IRP_MJ_POWER fffff880065d491c parport!PptDispatchPower
[17] IRP_MJ_SYSTEM_CONTROL fffff880065d4d4c parport!PptDispatchSystemControl
[18] IRP_MJ_DEVICE_CHANGE fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[19] IRP_MJ_QUERY_QUOTA fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[1a] IRP_MJ_SET_QUOTA fffff80001b6ecd4 nt!IopInvalidDeviceRequest
[1b] IRP_MJ_PNP fffff880065d4840 parport!PptDispatchPnp
In der Debuggerausgabe sehen Sie, dass parport.sys GsDriverEntry implementiert, den Einstiegspunkt für den Treiber. GsDriverEntry, das beim Erstellen des Treibers automatisch generiert wurde, führt eine Initialisierung durch und ruft dann DriverEntry auf, was vom Treiberentwickler implementiert wurde.
Sie können auch sehen, dass der Parporttreiber (in seiner DriverEntry-Funktion ) Zeiger zum Senden von Funktionen für diese hauptfunktionscodes bereitstellt:
- IRP_MJ_CREATE
- IRP_MJ_CLOSE
- IRP_MJ_READ
- IRP_MJ_WRITE
- IRP_MJ_QUERY_INFORMATION
- IRP_MJ_SET_INFORMATION
- IRP_MJ_DEVICE_CONTROL
- IRP_MJ_INTERNAL_DEVICE_CONTROL
- IRP_MJ_CLEANUP
- IRP_MJ_POWER
- IRP_MJ_SYSTEM_CONTROL
- IRP_MJ_PNP
Die restlichen Elemente des MajorFunction-Arrays enthalten Zeiger auf die Standardverteilungsfunktion nt! IopInvalidDeviceRequest.
In der Debuggerausgabe sehen Sie, dass der Parporttreiber Funktionszeiger für Unload und AddDevice bereitgestellt hat, aber keinen Funktionszeiger für StartIo bereitgestellt hat. Die AddDevice-Funktion ist ungewöhnlich, da ihr Funktionszeiger nicht in der DRIVER_OBJECT-Struktur gespeichert ist. Stattdessen wird es im AddDevice-Member einer Erweiterung der DRIVER_OBJECT-Struktur gespeichert. Das folgende Diagramm veranschaulicht die Funktionszeiger, die der Parporttreiber in seiner DriverEntry-Funktion bereitgestellt hat. Die von parport bereitgestellten Funktionszeiger sind schattiert.
Einfachere Verwendung von Treiberpaaren
Als Treiberentwickler innerhalb und außerhalb von Microsoft Erfahrungen mit dem Windows-Treibermodell (WDM) sammelten, haben sie im Laufe einer Zeit einiges über Die Dispatchfunktionen erkannt:
- Dispatch-Funktionen sind größtenteils boilerplate. Beispielsweise ist ein Großteil des Codes in der Dispatchfunktion für IRP_MJ_PNP für alle Treiber identisch. Es ist nur ein kleiner Teil des Plug & Play (PnP)-Codes, der für einen einzelnen Treiber spezifisch ist, der eine einzelne Hardware steuert.
- Dispatch-Funktionen sind kompliziert und schwer richtig zu bekommen. Die Implementierung von Features wie Threadsynchronisierung, IRP-Warteschlangen und IRP-Abbruch stellt eine Herausforderung dar und erfordert ein tiefes Verständnis der Funktionsweise des Betriebssystems.
Um Dies für Treiberentwickler zu vereinfachen, hat Microsoft mehrere technologiespezifische Treibermodelle erstellt. Auf den ersten Blick scheinen sich die technologiespezifischen Modelle deutlich voneinander zu unterscheiden, doch ein genauerer Blick zeigt, dass viele von ihnen auf diesem Paradigma basieren:
- Der Treiber ist in zwei Teile unterteilt: eine, die die allgemeine Verarbeitung übernimmt, und eine, die die verarbeitungsspezifisch für ein bestimmtes Gerät verarbeitet.
- Das allgemeine Stück wurde von Microsoft geschrieben.
- Das jeweilige Stück kann von Microsoft oder einem unabhängigen Hardwareanbieter geschrieben werden.
Angenommen, die Unternehmen Proseware und Contoso stellen beide einen Spielzeugroboter her, der einen WDM-Treiber erfordert. Angenommen, Microsoft bietet einen allgemeinen Robotertreiber namens GeneralRobot.sys. Proseware und Contoso können jeweils kleine Treiber schreiben, die die Anforderungen ihrer spezifischen Roboter erfüllen. Beispielsweise könnte Proseware ProsewareRobot.sys schreiben, und das Treiberpaar (ProsewareRobot.sys, GeneralRobot.sys) könnte zu einem einzelnen WDM-Treiber kombiniert werden. Ebenso kann das Treiberpaar (ContosoRobot.sys, GeneralRobot.sys) zu einem einzelnen WDM-Treiber kombiniert werden. In seiner allgemeinsten Form ist die Idee, dass Sie Treiber mithilfe von (specific.sys, general.sys) -Paaren erstellen können.
Funktionszeiger in Treiberpaaren
In einem (specific.sys, general.sys)-Paar lädt Windows specific.sys und ruft die DriverEntry-Funktion auf. Die DriverEntry-Funktion von specific.sys empfängt einen Zeiger auf eine DRIVER_OBJECT-Struktur . Normalerweise würden Sie davon ausgehen , dass DriverEntry mehrere Elemente des MajorFunction-Arrays mit Zeigern auf die Verteilung von Funktionen füllt. Außerdem würden Sie erwarten , dass DriverEntry das Unload-Element (und möglicherweise das StartIo-Element ) der DRIVER_OBJECT-Struktur und das AddDevice-Element der Treiberobjekterweiterung eingibt. In einem Treiberpaarmodell macht DriverEntry dies jedoch nicht unbedingt. Stattdessen übergibt die DriverEntry-Funktion von specific.sys die DRIVER_OBJECT-Struktur an eine Initialisierungsfunktion, die von general.sys implementiert wird. Das folgende Codebeispiel zeigt, wie die Initialisierungsfunktion im Paar (ProsewareRobot.sys, GeneralRobot.sys) aufgerufen werden kann.
PVOID g_ProsewareRobottCallbacks[3] = {DeviceControlCallback, PnpCallback, PowerCallback};
// DriverEntry function in ProsewareRobot.sys
NTSTATUS DriverEntry (DRIVER_OBJECT *DriverObject, PUNICODE_STRING RegistryPath)
{
// Call the initialization function implemented by GeneralRobot.sys.
return GeneralRobotInit(DriverObject, RegistryPath, g_ProsewareRobottCallbacks);
}
Die Initialisierungsfunktion in GeneralRobot.sys schreibt Funktionszeiger auf die entsprechenden Member der DRIVER_OBJECT-Struktur (und deren Erweiterung) und die entsprechenden Elemente des MajorFunction-Arrays . Wenn der E/A-Manager ein IRP an das Treiberpaar sendet, wird der IRP zuerst an eine von GeneralRobot.sys implementierte Dispatchfunktion weitergeleitet. Wenn GeneralRobot.sys die IRP selbst verarbeiten kann, muss der spezifische Treiber, ProsewareRobot.sys, nicht beteiligt sein. Wenn GeneralRobot.sys einige, aber nicht alle der IRP-Verarbeitung verarbeiten kann, erhält sie Hilfe von einer der Rückruffunktionen, die von ProsewareRobot.sys implementiert werden. GeneralRobot.sys empfängt Zeiger auf die ProsewareRobot-Rückrufe im GeneralRobotInit-Aufruf.
Nach der Rückgabe von DriverEntry wird irgendwann ein Gerätestapel für den Proseware Robot-Geräteknoten erstellt. Der Gerätestapel könnte wie folgt aussehen.
Wie im vorherigen Diagramm gezeigt, enthält der Gerätestapel für Proseware Robot drei Geräteobjekte. Das oberste Geräteobjekt ist ein Filtergeräteobjekt (Filter DO), das dem Filtertreiber AfterThought.sys zugeordnet ist. Das mittlere Geräteobjekt ist ein funktionales Geräteobjekt (Functional Device Object, FDO), das dem Treiberpaar zugeordnet ist (ProsewareRobot.sys, GeneralRobot.sys). Das Treiberpaar dient als Funktionstreiber für den Gerätestapel. Das untere Geräteobjekt ist ein physisches Geräteobjekt (PDO), das Pci.sys zugeordnet ist.
Beachten Sie, dass das Treiberpaar nur eine Ebene im Gerätestapel belegt und nur einem Geräteobjekt zugeordnet ist: der FDO. Wenn GeneralRobot.sys einen IRP verarbeitet, ruft er möglicherweise ProsewareRobot.sys auf, um Hilfe zu erhalten. Dies ist jedoch nicht dasselbe wie die Weitergabe der Anforderung im Gerätestapel. Das Treiberpaar bildet einen einzelnen WDM-Treiber, der sich auf einer Ebene im Gerätestapel befindet. Das Treiberpaar schließt entweder die IRP ab oder übergibt es im Gerätestapel an die PDO, die Pci.sys zugeordnet ist.
Beispiel für ein Treiberpaar
Angenommen, Sie verfügen über ein drahtloses Netzwerk Karte auf Ihrem Laptop, und wenn Sie in Geräte-Manager suchen, stellen Sie fest, dass netwlv64.sys der Treiber für das Netzwerk Karte ist. Sie können die Debuggererweiterung !drvobj verwenden, um die Funktionszeiger auf netwlv64.sys zu überprüfen.
1: kd> !drvobj netwlv64 2
Driver object (fffffa8002e5f420) is for:
\Driver\netwlv64
DriverEntry: fffff8800482f064 netwlv64!GsDriverEntry
DriverStartIo: 00000000
DriverUnload: fffff8800195c5f4 ndis!ndisMUnloadEx
AddDevice: fffff88001940d30 ndis!ndisPnPAddDevice
Dispatch routines:
[00] IRP_MJ_CREATE fffff880018b5530 ndis!ndisCreateIrpHandler
[01] IRP_MJ_CREATE_NAMED_PIPE fffff88001936f00 ndis!ndisDummyIrpHandler
[02] IRP_MJ_CLOSE fffff880018b5870 ndis!ndisCloseIrpHandler
[03] IRP_MJ_READ fffff88001936f00 ndis!ndisDummyIrpHandler
[04] IRP_MJ_WRITE fffff88001936f00 ndis!ndisDummyIrpHandler
[05] IRP_MJ_QUERY_INFORMATION fffff88001936f00 ndis!ndisDummyIrpHandler
[06] IRP_MJ_SET_INFORMATION fffff88001936f00 ndis!ndisDummyIrpHandler
[07] IRP_MJ_QUERY_EA fffff88001936f00 ndis!ndisDummyIrpHandler
[08] IRP_MJ_SET_EA fffff88001936f00 ndis!ndisDummyIrpHandler
[09] IRP_MJ_FLUSH_BUFFERS fffff88001936f00 ndis!ndisDummyIrpHandler
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION fffff88001936f00 ndis!ndisDummyIrpHandler
[0b] IRP_MJ_SET_VOLUME_INFORMATION fffff88001936f00 ndis!ndisDummyIrpHandler
[0c] IRP_MJ_DIRECTORY_CONTROL fffff88001936f00 ndis!ndisDummyIrpHandler
[0d] IRP_MJ_FILE_SYSTEM_CONTROL fffff88001936f00 ndis!ndisDummyIrpHandler
[0e] IRP_MJ_DEVICE_CONTROL fffff8800193696c ndis!ndisDeviceControlIrpHandler
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL fffff880018f9114 ndis!ndisDeviceInternalIrpDispatch
[10] IRP_MJ_SHUTDOWN fffff88001936f00 ndis!ndisDummyIrpHandler
[11] IRP_MJ_LOCK_CONTROL fffff88001936f00 ndis!ndisDummyIrpHandler
[12] IRP_MJ_CLEANUP fffff88001936f00 ndis!ndisDummyIrpHandler
[13] IRP_MJ_CREATE_MAILSLOT fffff88001936f00 ndis!ndisDummyIrpHandler
[14] IRP_MJ_QUERY_SECURITY fffff88001936f00 ndis!ndisDummyIrpHandler
[15] IRP_MJ_SET_SECURITY fffff88001936f00 ndis!ndisDummyIrpHandler
[16] IRP_MJ_POWER fffff880018c35e8 ndis!ndisPowerDispatch
[17] IRP_MJ_SYSTEM_CONTROL fffff880019392c8 ndis!ndisWMIDispatch
[18] IRP_MJ_DEVICE_CHANGE fffff88001936f00 ndis!ndisDummyIrpHandler
[19] IRP_MJ_QUERY_QUOTA fffff88001936f00 ndis!ndisDummyIrpHandler
[1a] IRP_MJ_SET_QUOTA fffff88001936f00 ndis!ndisDummyIrpHandler
[1b] IRP_MJ_PNP fffff8800193e518 ndis!ndisPnPDispatch
In der Debuggerausgabe sehen Sie, dass netwlv64.sys GsDriverEntry implementiert, den Einstiegspunkt für den Treiber. GsDriverEntry, das beim Erstellen des Treibers automatisch generiert wurde, führt eine Initialisierung durch und ruft dann DriverEntry auf, das vom Treiberentwickler geschrieben wurde.
In diesem Beispiel implementiert netwlv64.sys DriverEntry, aber ndis.sys implementiert AddDevice, Unload und mehrere Dispatchfunktionen. Netwlv64.sys wird als NDIS-Miniporttreiber bezeichnet, und ndis.sys wird als NDIS-Bibliothek bezeichnet. Zusammen bilden die beiden Module ein Paar (NDIS-Miniport, NDIS-Bibliothek).
Dieses Diagramm zeigt den Gerätestapel für das drahtlose Netzwerk Karte. Beachten Sie, dass das Treiberpaar (netwlv64.sys, ndis.sys) nur eine Ebene im Gerätestapel einnimmt und nur einem Geräteobjekt zugeordnet ist: dem FDO.
Verfügbare Treiberpaare
Die verschiedenen technologiespezifischen Treibermodelle verwenden eine Vielzahl von Namen für die spezifischen und allgemeinen Teile eines Treiberpaars. In vielen Fällen weist der spezifische Teil des Paars das Präfix "mini" auf. Hier sind einige (spezifische, allgemeine) Paare, die verfügbar sind:
- (Anzeige-Miniporttreiber, Anzeigeporttreiber)
- (Audio-Miniporttreiber, Audioporttreiber)
- (Speicher-Miniporttreiber, Speicherporttreiber)
- (Akku-Miniklasse-Treiber, Akkuklassentreiber)
- (HID-Minitreiber, HID-Klassentreiber)
- (Changer miniclass driver, changer port driver)
- (NDIS-Miniporttreiber, NDIS-Bibliothek)
Hinweis Wie Sie in der Liste sehen können, verwenden mehrere Modelle den Begriff Klassentreiber für den allgemeinen Teil eines Treiberpaars. Diese Art von Klassentreiber unterscheidet sich von einem eigenständigen Klassentreiber und von einem Klassenfiltertreiber.