Interfaccia richiesta OID sincrona in NDIS 6.80

I driver di rete Windows usano richieste OID per inviare messaggi di controllo nello stack di binding NDIS. I driver di protocollo, ad esempio TCPIP o vSwitch, si basano su decine di OID per configurare ogni funzionalità del driver della scheda di interfaccia di rete sottostante. Prima di Windows 10, la versione 1709, le richieste OID sono state inviate in due modi: Regular e Direct.

Questo argomento introduce un terzo stile di chiamata OID: sincrono. Una chiamata sincrona è destinata a bassa latenza, non blocco, scalabile e affidabile. L'interfaccia richiesta OID sincrona è disponibile a partire da NDIS 6.80, inclusa in Windows 10, versione 1709 e successiva.

Confronto con le richieste OID regolari e dirette

Con le richieste OID sincrone, il payload della chiamata (l'OID stesso) corrisponde esattamente alle richieste Regular e Direct OID. L'unica differenza è nella chiamata stessa. Di conseguenza, ciò che è lo stesso in tutti e tre i tipi di OID; solo il modo in cui è diverso.

Nella tabella seguente vengono descritte le differenze tra OID regolari, OID Direct e OID sincroni.

Attributo OID normale OID diretto OID sincrono
Payload NDIS_OID_REQUEST NDIS_OID_REQUEST NDIS_OID_REQUEST
Tipi OID Statistiche, Query, Set, Metodo Statistiche, Query, Set, Metodo Statistiche, Query, Set, Metodo
Può essere rilasciato da Protocolli, filtri Protocolli, filtri Protocolli, filtri
Può essere completato da Miniport, filtri Miniport, filtri Miniport, filtri
I filtri possono modificare
NDIS alloca memoria Per ogni filtro (clone OID) Per ogni filtro (clone OID) Solo se un numero insolitamente elevato di filtri (contesto di chiamata)
Can pend No
Può bloccare No No
IRQL == PASSIVO <= DISPATCH <= DISPATCH
Serializzato da NDIS No No
I filtri vengono richiamati Recursively (Ricorsivo) Recursively (Ricorsivo) Iterativamente
Filtri clonare l'OID No

Filtro

Come gli altri due tipi di chiamate OID, i driver di filtro hanno il controllo completo sulla richiesta OID in una chiamata sincrona. I driver di filtro possono osservare, intercettare, modificare e rilasciare OID sincroni. Tuttavia, per l'efficienza, i meccanismi di un OID sincrono sono leggermente diversi.

Pass-through, intercettazione e origine

Concettualmente, tutte le richieste OID vengono rilasciate da un driver superiore e vengono completate da un driver inferiore. Lungo il percorso, la richiesta OID può passare attraverso qualsiasi numero di driver di filtro.

Nel caso più comune, un driver di protocollo genera una richiesta OID e tutti i filtri passano semplicemente la richiesta OID verso il basso, non modificata. La figura seguente illustra questo scenario comune.

Percorso OID tipico originato da un protocollo.

Tuttavia, qualsiasi modulo di filtro può intercettare la richiesta OID e completarla. In tal caso, la richiesta non passa attraverso i driver inferiori, come illustrato nel diagramma seguente.

Percorso OID tipico originato da un protocollo e intercettato da un filtro.

In alcuni casi, un modulo di filtro può decidere di originare la propria richiesta OID. Questa richiesta inizia a livello del modulo di filtro e attraversa solo i driver inferiori, come illustrato nel diagramma seguente.

Percorso OID tipico originato da un filtro.

Tutte le richieste OID hanno questo flusso di base: un driver superiore (un protocollo o un driver di filtro) genera una richiesta e un driver inferiore (un miniport o un driver di filtro) lo completa.

Funzionamento delle richieste OID regolari e dirette

Le richieste OID regolari o dirette vengono inviate in modo ricorsivo. Il diagramma seguente mostra la sequenza di chiamate di funzione. Si noti che la sequenza stessa è molto simile alla sequenza descritta nei diagrammi della sezione precedente, ma è disposta per mostrare la natura ricorsiva delle richieste.

Sequenza di chiamate di funzione per le richieste OID regolari e dirette.

Se sono installati filtri sufficienti, NDIS sarà costretto a allocare un nuovo stack di thread per continuare a ricreare più profondamente.

NDIS considera una struttura NDIS_OID_REQUEST valida solo per un singolo hop lungo lo stack. Se un driver di filtro vuole passare la richiesta al driver inferiore successivo (che è il caso per la maggior parte degli OID), il driver di filtro deve inserire diverse decine di righe di codice boilerplate per clonare la richiesta OID. Questo boilerplate presenta diversi problemi:

  1. Forza un'allocazione di memoria per clonare l'OID. Il raggiungimento del pool di memoria è sia lento che rende impossibile garantire lo stato di avanzamento della richiesta OID.
  2. La progettazione della struttura OID deve rimanere invariata nel tempo perché tutti i driver di filtro hard-code la meccanica della copia del contenuto di un NDIS_OID_REQUEST in un altro.
  3. Richiedendo così tanto boilerplate oscura ciò che il filtro sta veramente facendo.

Modello di filtro per le richieste OID sincrone

Il modello di filtro per le richieste OID sincrone sfrutta la natura sincrona della chiamata, per risolvere i problemi descritti nella sezione precedente.

Problemi e gestori completi

A differenza delle richieste OID regolari e dirette, sono disponibili due hook di filtro per le richieste OID sincrone: un gestore dei problemi e un gestore completo. Un driver di filtro può registrare nessuno, uno o entrambi gli hook.

Le chiamate ai problemi vengono richiamate per ogni driver di filtro, a partire dalla parte superiore dello stack verso il basso fino alla parte inferiore dello stack. Qualsiasi chiamata di problema del filtro può impedire all'OID di continuare verso il basso e completare l'OID con un codice di stato. Se nessun filtro decide di intercettare l'OID, l'OID raggiunge il driver della scheda di interfaccia di rete, che deve completare l'OID in modo sincrono.

Al termine di un OID, le chiamate complete vengono richiamate per ogni driver di filtro, a partire da qualsiasi posizione nello stack in cui è stato completato l'OID, fino alla parte superiore dello stack. Una chiamata Completa può esaminare o modificare la richiesta OID e controllare o modificare il codice di stato di completamento dell'OID.

Il diagramma seguente illustra il caso tipico, in cui un protocollo genera una richiesta OID sincrona e i filtri non intercettano la richiesta.

Sequenza di chiamate di funzione per le richieste OID sincrone.

Si noti che il modello di chiamata per gli OID sincroni è iterativo. In questo modo l'utilizzo dello stack viene limitato da una costante, eliminando la necessità di espandere lo stack.

Se un driver di filtro intercetta un OID sincrono nel gestore del problema, l'OID non viene assegnato a filtri inferiori o al driver della scheda di interfaccia di rete. Tuttavia, i gestori completi per i filtri più elevati vengono comunque richiamati, come illustrato nel diagramma seguente:

Sequenza di chiamate di funzione per le richieste OID sincrone con un'intercettazione da parte di un filtro.

Allocazioni minime di memoria

Le richieste OID regolari e dirette richiedono un driver di filtro per clonare un NDIS_OID_REQUEST. Al contrario, le richieste OID sincrone non possono essere clonate. Il vantaggio di questa progettazione è che gli IDE sincroni hanno una latenza inferiore, ovvero la richiesta OID non viene clonata ripetutamente mentre si sposta verso il basso lo stack di filtri e ci sono meno opportunità di errore.

Tuttavia, ciò genera un nuovo problema. Se l'OID non può essere clonato, dove un driver di filtro archivia lo stato per richiesta? Si supponga, ad esempio, che un driver di filtro converta un OID in un altro. Nella parte inferiore dello stack, il filtro deve salvare l'OID precedente. Durante il backup dello stack, il filtro deve ripristinare l'OID precedente.

Per risolvere questo problema, NDIS alloca uno slot di dimensioni puntatore per ogni driver di filtro, per ogni richiesta OID sincrona in anteprima. NDIS mantiene questo slot nella chiamata dal gestore issue di un filtro al relativo gestore completo. In questo modo il gestore problema può salvare lo stato disattivato utilizzato in un secondo momento dal gestore Complete. Il frammento di codice seguente mostra un esempio.

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 salva un PVOID per ogni filtro per ogni chiamata. NDIS alloca in modo euristico un numero ragionevole di slot nello stack, in modo che nel caso comune siano presenti allocazioni di pool pari a zero. Questo è in genere non più di sette filtri. Se l'utente configura un caso patologico, NDIS esegue il fallback a un'allocazione del pool.

Boilerplate ridotto

Prendere in considerazione il boilerplate nell'esempio boilerplate per la gestione delle richieste OID regolari o dirette. Questo codice è il costo della voce solo per registrare un gestore OID. Se vuoi rilasciare i tuoi ID, devi aggiungere un'altra dozzina di linee di boilerplate. Con gli ID sincroni, non è necessario che la complessità aggiuntiva di gestione del completamento asincrono sia necessaria. Pertanto, è possibile tagliare gran parte di quella boilerplate.

Di seguito è riportato un gestore di problemi minimo con OID sincroni:

NDIS_STATUS
MyFilterSynchronousOidRequest(
  NDIS_HANDLE FilterModuleContext,
  NDIS_OID_REQUEST *OidRequest,
  PVOID *CallContext)
{
  return NDIS_STATUS_SUCCESS;
}

Per intercettare o modificare un OID specifico, è possibile farlo aggiungendo solo un paio di righe di codice. Il gestore completo minimo è ancora più semplice:

VOID
MyFilterSynchronousOidRequestComplete(
  NDIS_HANDLE FilterModuleContext,
  NDIS_OID_REQUEST *OidRequest,
  NDIS_STATUS *Status,
  PVOID CallContext)
{
  return;
}

Analogamente, un driver di filtro può inviare una nuova richiesta OID sincrona di propria usando una sola riga di codice:

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

Al contrario, un driver di filtro che deve emettere un OID regolare o diretto deve configurare un gestore di completamento asincrono e implementare un codice per distinguere i propri completamenti OID dai completamenti degli OID appena clonati. Un esempio di questo boilerplate è illustrato in Boilerplate di esempio per l'emissione di una richiesta OID regolare.

Interoperabilità

Anche se gli stili di chiamata regolare, diretta e sincrona usano tutte le stesse strutture di dati, le pipeline non passano allo stesso gestore nel miniport. Inoltre, alcune OID non possono essere usate in alcune pipeline. Ad esempio, OID_PNP_SET_POWER richiede un'attenta sincronizzazione e spesso forza il miniport a effettuare chiamate bloccante. Ciò rende difficile la gestione in un callback OID diretto e impedisce l'uso in un callback OID sincrono.

Pertanto, proprio come per le richieste OID dirette, le chiamate OID sincrone possono essere usate solo con un subset di OID. In Windows 10 versione 1709, solo l'OID OID_GEN_RSS_SET_INDIRECTION_TABLE_ENTRIES usato in Receive Side Scaling Version 2 (RSSv2) è supportato nel percorso OID sincrono.

Implementazione di richieste OID sincrone

Per altre info sull'implementazione dell'interfaccia di richiesta OID sincrona nei driver, vedi gli argomenti seguenti: