Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Windows-Netzwerktreiber verwenden OID-Anforderungen, um Steuermeldungen nach unten im NDIS-Bindungsstapel zu senden. Protokolltreiber wie TCPIP oder vSwitch basieren auf Dutzenden von OIDs, um jedes Feature des zugrunde liegenden NIC-Treibers zu konfigurieren. Vor Windows 10, Version 1709, wurden OID-Anforderungen auf zwei Arten gesendet: Normal und Direct.
In diesem Thema wird ein dritter Stil des OID-Aufrufs vorgestellt: Synchron. Ein synchroner Aufruf soll geringe Latenz, Nicht-Blockierung, Skalierbarkeit und Zuverlässigkeit bieten. Die synchrone OID-Anforderungsschnittstelle ist ab NDIS 6.80 verfügbar, das in Windows 10, Version 1709 und höher enthalten ist.
Vergleich zu regulären und direkten OID-Anforderungen
Bei synchronen OID-Anforderungen ist die Nutzlast des Aufrufs (OID selbst) genau mit regulären und Direct OID-Anforderungen identisch. Der einzige Unterschied besteht im Aufruf selbst. Daher ist das 'Was' bei allen drei Arten von OIDs dasselbe; nur das 'Wie' ist anders.
In der folgenden Tabelle werden die Unterschiede zwischen regulären OIDs, Direct OIDs und synchronen OIDs beschrieben.
| Merkmal | Reguläres OID | Direktes OID | Synchrones OID |
|---|---|---|---|
| Nutzlast | NDIS_OID_REQUEST | NDIS_OID_REQUEST | NDIS_OID_REQUEST |
| OID-Typen | Statistiken, Abfrage, Set, Methode | Statistiken, Abfrage, Menge, Methode | Statistiken, Abfrage, Satz, Methode |
| Kann ausgestellt werden von | Protokolle, Filter | Protokolle, Filter | Protokolle, Filter |
| Kann abgeschlossen werden, indem | Miniports, Filter | Miniports, Filter | Miniports, Filter |
| Filter können Änderungen vornehmen | Ja | Ja | Ja |
| NDIS weist Arbeitsspeicher zu | Für jeden Filter (OID-Klon) | Für jeden Filter (OID-Klon) | Nur, wenn ungewöhnlich viele Filter (Aufrufkontext) |
| Kann pendeln | Ja | Ja | Nein |
| Kann blockieren | Ja | Nein | Nein |
| IRQL | == PASSIVE | <= AUSLIEFERUNG | <= STEUERUNG |
| Serialisiert von NDIS | Ja | Nein | Nein |
| Filter werden aufgerufen | Rekursiv | Rekursiv | Iterativ |
| Filter klonen das OID | Ja | Ja | Nein |
Filterung
Wie die anderen beiden Arten von OID-Aufrufen haben Filtertreiber die volle Kontrolle über die OID-Anforderung in einem synchronen Aufruf. Filtertreiber können synchrone OIDs beobachten, abfangen, ändern und ausstellen. Aus Effizienzgründen unterscheiden sich die Mechaniken eines synchronen OID jedoch etwas.
Passthrough, Abfangen und Ursprung
Konzeptionell werden alle OID-Anforderungen von einem höheren Treiber ausgestellt und von einem niedrigeren Treiber abgeschlossen. Auf dem Weg kann die OID-Anforderung eine beliebige Anzahl von Filtertreibern durchlaufen.
Im häufigsten Fall gibt ein Protokolltreiber eine OID-Anforderung aus, und alle Filter übergeben einfach die OID-Anforderung nach unten, unmodifiziert. Die folgende Abbildung veranschaulicht dieses häufige Szenario.
Jedes Filtermodul kann die OID-Anforderung jedoch abfangen und abschließen. In diesem Fall wird die Anforderung nicht an niedrigere Treiber übergeben, wie im folgenden Diagramm dargestellt.
In einigen Fällen kann sich ein Filtermodul entscheiden, eine eigene OID-Anforderung zu erstellen. Diese Anforderung beginnt auf der Ebene des Filtermoduls und durchläuft nur niedrigere Treiber, wie das folgende Diagramm zeigt.
Alle OID-Anforderungen haben diesen grundlegenden Fluss: Ein höherer Treiber (entweder ein Protokoll- oder Filtertreiber) gibt eine Anforderung aus, und ein niedrigerer Treiber (entweder ein Miniport- oder Filtertreiber) schließt ihn ab.
Funktionsweise regulärer und direkter OID-Anforderungen
Normale oder direkte OID-Anforderungen werden rekursiv abgewickelt. Das folgende Diagramm zeigt die Funktionsaufrufsequenz. Beachten Sie, dass die Sequenz selbst ähnlich der sequenz ist, die in den Diagrammen aus dem vorherigen Abschnitt beschrieben wird, aber so angeordnet ist, dass die rekursive Natur der Anforderungen angezeigt wird.
Wenn genügend Filter installiert sind, wird NDIS gezwungen, einen neuen Threadstapel zuzuweisen, um weiter zu rekursieren.
NDIS hält eine NDIS_OID_REQUEST Struktur nur für einen einzelnen Hop entlang des Stapels für gültig. Wenn ein Filtertreiber die Anforderung an den nächsten niedrigeren Treiber übergeben möchte (was für die überwiegende Mehrheit der OIDs der Fall ist), muss der Filtertreiber mehrere Dutzend Zeilen Mitbausteincode einfügen, um die OID-Anforderung zu klonen. Dieser Textbaustein hat mehrere Probleme:
- Sie erzwingt eine Speicherzuweisung, um das OID zu klonen. Das Zugreifen auf den Speicherpool ist sowohl langsam als auch verhindert es, den Fortschritt der OID-Anforderung sicherzustellen.
- Der OID-Strukturentwurf muss über die Zeit hinweg gleich bleiben, da alle Filtertreiber die Mechanik des Kopierens der Inhalte eines NDIS_OID_REQUEST in eine andere fest einprogrammieren.
- Wenn sie so viel Standardcode benötigen, wird das, was der Filter wirklich tut, verdeckt.
Das Filtermodell für synchrone OID-Anforderungen
Das Filtermodell für synchrone OID-Anforderungen nutzt die synchrone Art des Aufrufs, um die im vorherigen Abschnitt erläuterten Probleme zu lösen.
Ausgabe- und Abschluss-Handler
Im Gegensatz zu regulären und Direct OID-Anforderungen gibt es zwei Filterhaken für synchrone OID-Anforderungen: einen Problemhandler und einen vollständigen Handler. Ein Filtertreiber kann keines, eines oder beide Hooks registrieren.
Problemaufrufe werden für jeden Filtertreiber aufgerufen, beginnend vom oberen Rand des Stapels bis zum unteren Rand des Stapels. Der Problembehandlungsaufruf eines Filters kann verhindern, dass das OID weiter nach unten fortgesetzt wird, und das OID mit einem bestimmten Statuscode abschließen. Wenn kein Filter entscheidet, das OID abzufangen, erreicht der OID den NIC-Treiber, der das OID synchron abschließen muss.
Nach Abschluss einer OID werden Abschlussaufrufe für jeden Filtertreiber aufgerufen, beginnend an der Stelle im Stapel, an der die OID abgeschlossen wurde, bis zum oberen Ende des Stapels. Ein vollständiger Aufruf kann die OID-Anforderung prüfen oder ändern und den OID-Abschlussstatuscode überprüfen oder ändern.
Das folgende Diagramm veranschaulicht den typischen Fall, in dem ein Protokoll eine synchrone OID-Anforderung ausgibt und die Filter die Anforderung nicht abfangen.
Beachten Sie, dass das Aufrufmodell für synchrone OIDs iterativ ist. Dadurch wird die Stapelnutzung durch eine Konstante begrenzt, sodass der Stapel nie erweitert werden muss.
Wenn ein Filtertreiber ein synchrones OID in seinem Problemhandler abfangen, wird der OID nicht an niedrigere Filter oder den NIC-Treiber übergeben. Vollständige Handler für höhere Filter werden jedoch weiterhin aufgerufen, wie im folgenden Diagramm dargestellt:
Minimale Speicherzuweisungen
Reguläre und Direct OID-Anforderungen erfordern einen Filtertreiber, um eine NDIS_OID_REQUEST zu klonen. Im Gegensatz dazu dürfen synchrone OID-Anforderungen nicht geklont werden. Der Vorteil dieses Designs besteht darin, dass synchrone OIDs eine geringere Latenz aufweisen – die OID-Anforderung wird nicht wiederholt geklont, da sie den Filterstapel nach unten bewegt – und es gibt weniger Fehlermöglichkeiten.
Das löst jedoch ein neues Problem aus. Wenn das OID nicht geklont werden kann, wo speichert ein Filtertreiber den Status pro Anforderung? Angenommen, ein Filtertreiber übersetzt ein OID in einen anderen. Auf dem Weg nach unten im Stapel muss der Filter das alte OID speichern. Auf dem Weg zurück zum Stack muss der Filter die alte OID wiederherstellen.
Um dieses Problem zu lösen, weist NDIS für jeden in Bearbeitung befindlichen synchronen OID-Anforderungsvorgang jedem Filtertreiber einen Zeigerslot zu. NDIS behält diesen Slot über den Aufruf des Issue-Handlers eines Filters bis zum Kompletthandler bei. Dadurch kann der Fehlerhandler den Zustand speichern, der später vom Complete-Handler verarbeitet wird. Der folgende Codeausschnitt zeigt ein Beispiel.
NDIS_STATUS
MyFilterSynchronousOidRequest(
_In_ NDIS_HANDLE FilterModuleContext,
_Inout_ NDIS_OID_REQUEST *OidRequest,
_Outptr_result_maybenull_ PVOID *CallContext)
{
if ( . . . should intercept this OID . . . )
{
// preserve the original buffer in the CallContext
*CallContext = OidRequest->DATA.SET_INFORMATION.InformationBuffer;
// replace the buffer with a new one
OidRequest->DATA.SET_INFORMATION.InformationBuffer = . . . something . . .;
}
return NDIS_STATUS_SUCCESS;
}
VOID
MyFilterSynchronousOidRequestComplete(
_In_ NDIS_HANDLE FilterModuleContext,
_Inout_ NDIS_OID_REQUEST *OidRequest,
_Inout_ NDIS_STATUS *Status,
_In_ PVOID CallContext)
{
// if the context is not null, we must have replaced the buffer.
if (CallContext != null)
{
// Copy the data from the miniport back into the protocol’s original buffer.
RtlCopyMemory(CallContext, OidRequest->DATA.SET_INFORMATION.InformationBuffer,...);
// restore the original buffer into the OID request
OidRequest->DATA.SET_INFORMATION.InformationBuffer = CallContext;
}
}
NDIS speichert ein PVOID pro Filter pro Anruf. NDIS weist heuristisch eine angemessene Anzahl von Slots auf dem Stapel zu, sodass es im gemeinsamen Fall null Poolzuweisungen gibt. Dies ist in der Regel nicht mehr als sieben Filter. Wenn der Benutzer einen pathologischen Fall konfiguriert, fällt NDIS auf eine Poolzuordnung zurück.
Reduzierter Kesselbaustein
Betrachten Sie den Standardtext auf Beispielstandardtext für die Behandlung regulärer oder direkter OID-Anfragen. Dieser Code ist nur die Eintrittskosten, um einen OID-Handler zu registrieren. Wenn Sie Ihre eigenen OIDs ausstellen möchten, müssen Sie ein weiteres Dutzend Zeilen Vorlagen hinzufügen. Bei synchronen OIDs ist es nicht erforderlich, die zusätzliche Komplexität des Umgangs mit asynchroner Vervollständigung zu bewältigen. Daher können Sie einen Großteil dieser Vorlage ausschneiden.
Hier ist ein minimaler Problemhandler mit synchronen OIDs:
NDIS_STATUS
MyFilterSynchronousOidRequest(
NDIS_HANDLE FilterModuleContext,
NDIS_OID_REQUEST *OidRequest,
PVOID *CallContext)
{
return NDIS_STATUS_SUCCESS;
}
Wenn Sie ein bestimmtes OID abfangen oder ändern möchten, können Sie dies tun, indem Sie nur ein paar Codezeilen hinzufügen. Der minimale Complete-Handler ist noch einfacher:
VOID
MyFilterSynchronousOidRequestComplete(
NDIS_HANDLE FilterModuleContext,
NDIS_OID_REQUEST *OidRequest,
NDIS_STATUS *Status,
PVOID CallContext)
{
return;
}
Ebenso kann ein Filtertreiber eine neue synchrone OID-Anforderung selbst mit nur einer Codezeile ausstellen:
status = NdisFSynchronousOidRequest(binding->NdisBindingHandle, &oid);
Im Gegensatz dazu muss ein Filtertreiber, der einen regulären oder direkten OID ausstellen muss, einen asynchronen Abschluss-Handler einrichten und Code implementieren, um zwischen den eigenen OID-Abschlüssen und den Abschlüssen der gerade geklonten OIDs zu unterscheiden. Ein Beispiel für diese Bausteine ist im Beispielbaustein zum Ausgeben einer regulären OID-Anforderung dargestellt.
Interoperabilität
Obwohl die Aufrufstile "Regular", "Direct" und "synchron" dieselben Datenstrukturen verwenden, gehen die Pipelines nicht zum gleichen Handler im Miniport. Darüber hinaus können einige OIDs nicht in einigen der Pipelines verwendet werden. Beispielsweise erfordert OID_PNP_SET_POWER eine sorgfältige Synchronisierung und zwingt häufig den Miniport, blockierende Aufrufe auszuführen. Dies erschwert die Handhabung in einem direkten OID-Rückruf und verhindert die Nutzung in einem synchronisierten OID-Rückruf.
Wie bei Direct OID-Anforderungen können synchrone OID-Aufrufe nur mit einer Teilmenge von OIDs verwendet werden. In Windows 10, Version 1709, wird im synchronen OID-Pfad nur das OID OID_GEN_RSS_SET_INDIRECTION_TABLE_ENTRIES unterstützt, das in Receive Side Scaling Version 2 (RSSv2) verwendet wird.
Implementieren synchroner OID-Anforderungen
Weitere Informationen zum Implementieren der synchronen OID-Anforderungsschnittstelle in Treibern finden Sie in den folgenden Themen: