Synchrone OID-Anforderungsschnittstelle in NDIS 6.80

Windows-Netzwerktreiber verwenden OID-Anforderungen, um Steuernachrichten über den NDIS-Bindungsstapel zu senden. Protokolltreiber, z. B. TCPIP oder vSwitch, sind auf Dutzende von OIDs angewiesen, um jedes Feature des zugrunde liegenden NIC-Treibers zu konfigurieren. Vor Windows 10 Version 1709 wurden OID-Anforderungen auf zwei Arten gesendet: Regular und Direct.

In diesem Thema wird eine dritte Art des OID-Aufrufs eingeführt: Synchron. Ein synchroner Aufruf soll mit geringer Latenz, nicht blockierend, skalierbar und zuverlässig sein. Die Synchrone OID-Anforderungsschnittstelle ist ab NDIS 6.80 verfügbar, das in Windows 10, Version 1709 und höher enthalten ist.

Vergleich mit regulären und direkten OID-Anforderungen

Bei synchronen OID-Anforderungen ist die Nutzlast des Aufrufs (die OID selbst) genau die gleiche wie bei regulären und direkten OID-Anforderungen. Der einzige Unterschied besteht im Aufruf selbst. Daher ist das, was für alle drei Arten von OIDs identisch ist; nur das Wie unterscheidet sich.

In der folgenden Tabelle werden die Unterschiede zwischen regulären OIDs, direkten OIDs und synchronen OIDs beschrieben.

attribute Reguläre OID Direkte OID Synchrone OID
Nutzlast NDIS_OID_REQUEST NDIS_OID_REQUEST NDIS_OID_REQUEST
OID-Typen Stats, Query, Set, Method Stats, Query, Set, Method Stats, Query, Set, Method
Kann ausgestellt werden von Protokolle, Filter Protokolle, Filter Protokolle, Filter
Kann ergänzt werden durch Miniports, Filter Miniports, Filter Miniports, Filter
Filter können ändern Ja Ja Ja
NDIS weist Arbeitsspeicher zu Für jeden Filter (OID-Klon) Für jeden Filter (OID-Klon) Nur bei ungewöhnlich großer Anzahl von Filtern (Aufrufkontext)
Kann stiften Ja Ja Nein
Kann blockieren Ja Nein Nein
IRQL == PASSIV <= DISPATCH <= DISPATCH
Serialisiert durch NDIS Ja Nein Nein
Filter werden aufgerufen. Rekursiv Rekursiv Iterativ
Filter klonen die OID Ja Ja Nein

Filterung

Wie die beiden anderen Typen 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. Für die Effizienz ist die Mechanik einer synchronen OID jedoch etwas anders.

Passthrough, Abfangen und Origination

Konzeptionell werden alle OID-Anforderungen von einem höheren Treiber ausgegeben und von einem niedrigeren Treiber abgeschlossen. Dabei 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 die OID-Anforderung einfach unverändert. Die folgende Abbildung veranschaulicht dieses häufige Szenario.

Der typische OID-Pfad stammt aus einem Protokoll.

Jedes Filtermodul darf die OID-Anforderung jedoch abfangen und abschließen. In diesem Fall wird die Anforderung nicht an niedrigere Treiber übergeben, wie im folgenden Diagramm dargestellt.

Der typische OID-Pfad stammt aus einem Protokoll und wurde von einem Filter abgefangen. .

In einigen Fällen kann 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.

Der typische OID-Pfad stammt aus einem Filter.

Alle OID-Anforderungen verfügen über diesen grundlegenden Ablauf: Ein höherer Treiber (entweder ein Protokoll- oder Filtertreiber) stellt eine Anforderung aus, und ein niedrigerer Treiber (entweder ein Miniport oder ein Filtertreiber) schließt sie ab.

Funktionsweise regulärer und direkter OID-Anforderungen

Reguläre oder direkte OID-Anforderungen werden rekursiv gesendet. Das folgende Diagramm zeigt die Funktionsaufrufsequenz. Beachten Sie, dass die Sequenz selbst der sequenz ähnelt, die in den Diagrammen aus dem vorherigen Abschnitt beschrieben wurde, aber so angeordnet ist, dass sie die rekursive Natur der Anforderungen zeigt.

Funktionsaufrufsequenz für reguläre und direkte OID-Anforderungen.

Wenn genügend Filter installiert sind, wird NDIS gezwungen, einen neuen Threadstapel zuzuweisen, um tiefer zu rekursieren.

NDIS betrachtet eine NDIS_OID_REQUEST-Struktur als nur für einen einzelnen Hop entlang des Stapels gültig. Wenn ein Filtertreiber die Anforderung an den nächsten niedrigeren Treiber übergeben möchte (was für die große Mehrheit der OIDs der Fall ist), muss der Filtertreiber mehrere Dutzend Zeilen Boilerplate-Code einfügen, um die OID-Anforderung zu klonen. Dieses Boilerplate hat mehrere Probleme:

  1. Es erzwingt eine Speicherzuordnung, die OID zu klonen. Das Treffen des Speicherpools ist sowohl langsam als auch unmöglich, den Fortschritt der OID-Anforderung weiterzuleiten.
  2. Der OID-Strukturentwurf muss im Laufe der Zeit gleich bleiben, da alle Filtertreiber die Mechanik des Kopierens der Inhalte einer NDIS_OID_REQUEST in eine andere hart codieren.
  3. Der Bedarf an so viel Boilerplate verschleiert, was der Filter wirklich tut.

Das Filtermodell für synchrone OID-Anforderungen

Das Filtermodell für synchrone OID-Anforderungen nutzt die Synchronität des Aufrufs, um die im vorherigen Abschnitt beschriebenen Probleme zu lösen.

Problem- und vollständige Handler

Im Gegensatz zu regulären und direkten OID-Anforderungen gibt es zwei Filterhooks für synchrone OID-Anforderungen: einen Issue-Handler und einen Complete-Handler. Ein Filtertreiber kann weder, noch einen oder beide Hooks registrieren.

Problemaufrufe werden für jeden Filtertreiber aufgerufen, beginnend vom oberen Rand des Stapels bis zum unteren Rand des Stapels. Der Issue-Aufruf eines Filters kann verhindern, dass die OID nach unten fortgesetzt wird, und die OID mit einigen status Code abschließen. Wenn kein Filter beschließt, die OID abzufangen, erreicht die OID den NIC-Treiber, der die OID synchron abschließen muss.

Nachdem eine OID abgeschlossen wurde, werden vollständige Aufrufe für jeden Filtertreiber aufgerufen, beginnend von der Stelle im Stapel, an der die OID abgeschlossen wurde, bis zur Spitze des Stapels. Ein Vollständiger Aufruf kann die OID-Anforderung überprüfen oder ändern und die Vervollständigung der OID status Code ü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.

Funktionsaufrufsequenz für synchrone OID-Anforderungen.

Beachten Sie, dass das Aufrufmodell für synchrone OIDs iterativ ist. Dadurch bleibt die Stapelnutzung durch eine Konstante begrenzt, sodass die Notwendigkeit entfällt, den Stapel jemals zu erweitern.

Wenn ein Filtertreiber eine synchrone OID in seinem Problemhandler abfängt, wird die OID nicht für niedrigere Filter oder den NIC-Treiber angegeben. Vollständige Handler für höhere Filter werden jedoch weiterhin aufgerufen, wie im folgenden Diagramm gezeigt:

Funktionsaufrufsequenz für synchrone OID-Anforderungen mit einer Abfangen durch einen Filter.

Minimale Speicherbelegungen

Reguläre und direkte 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 Entwurfs besteht darin, dass synchrone OIDs eine geringere Latenz haben – die OID-Anforderung wird nicht wiederholt geklont, während sie den Filterstapel hinunter bewegt – und es gibt weniger Fehlermöglichkeiten.

Dies wirft jedoch ein neues Problem auf. Wo speichert ein Filtertreiber seinen Anforderungsstatus, wenn die OID nicht geklont werden kann? Angenommen, ein Filtertreiber übersetzt eine OID in eine andere. Auf dem Weg nach unten im Stapel muss der Filter die alte OID speichern. Auf dem Weg zur Wiederherstellung des Stapels muss der Filter die alte OID wiederherstellen.

Um dieses Problem zu lösen, ordnet NDIS jedem Filtertreiber für jede synchrone OID-Anforderung einen Steckplatz mit Zeigergröße zu. NDIS behält diesen Slot über den Aufruf des Issue-Handlers eines Filters an den vollständigen Handler bei. Dadurch kann der Issue-Handler den Zustand speichern, der später vom Complete-Handler verwendet 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 einen PVOID pro Filter pro Aufruf. NDIS weist heuristisch eine angemessene Anzahl von Slots auf dem Stapel zu, sodass im allgemeinen Fall keine Poolzuordnungen vorhanden sind. Dies sind in der Regel nicht mehr als sieben Filter. Wenn der Benutzer einen pathologischen Fall einrichtet, fällt NDIS auf eine Poolzuordnung zurück.

Reduziertes Aggregat

Betrachten Sie die Bausteine auf dem Beispiel-Kessel für die Verarbeitung regulärer oder direkter OID-Anforderungen. Dieser Code ist die Kosten für die Eingabe, nur um einen OID-Handler zu registrieren. Wenn Sie Ihre eigenen OIDs ausstellen möchten, müssen Sie weitere Dutzend Zeilen mit Kesselplatten hinzufügen. Bei synchronen OIDs ist die zusätzliche Komplexität der Verarbeitung der asynchronen Vervollständigung nicht erforderlich. Daher können Sie einen Großteil dieses Kessels 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 eine bestimmte 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 mit nur einer Codezeile ausgeben:

status = NdisFSynchronousOidRequest(binding->NdisBindingHandle, &oid);

Im Gegensatz dazu muss ein Filtertreiber, der eine reguläre oder direkte OID ausstellen muss, einen asynchronen Vervollständigungshandler einrichten und Code implementieren, um seine eigenen OID-Vervollständigungen von den Vervollständigungen von OIDs zu unterscheiden, die er gerade geklont hat. Ein Beispiel für diese Bausteine finden Sie unter Beispielbaustein zum Ausgeben einer regulären OID-Anforderung.

Interoperabilität

Obwohl die Formatvorlagen reguläre, direkte und synchrone Aufrufe alle die gleichen Datenstrukturen verwenden, werden die Pipelines nicht an den gleichen Handler im Miniport weitergeleitet. 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 erzwingt häufig den Miniport, blockierende Anrufe zu tätigen. Dies erschwert die Behandlung in einem direkten OID-Rückruf und verhindert die Verwendung in einem synchronen OID-Rückruf.

Daher können synchrone OID-Aufrufe wie bei direkten OID-Anforderungen nur mit einer Teilmenge von OIDs verwendet werden. In Windows 10 Version 1709 wird im synchronen OID-Pfad nur die OID_GEN_RSS_SET_INDIRECTION_TABLE_ENTRIES OID unterstützt, die in der empfangsseitigen Skalierung 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: