Interfaz de solicitud OID sincrónica en NDIS 6.80
Los controladores de red de Windows usan solicitudes OID para enviar mensajes de control a la pila de enlaces NDIS. Los controladores de protocolo, como TCPIP o vSwitch, dependen de docenas de OID para configurar cada característica del controlador NIC subyacente. Antes de Windows 10, versión 1709, las solicitudes de OID se enviaron de dos maneras: Regular y Direct.
En este tema se presenta un tercer estilo de llamada a OID: Sincrónico. Una llamada sincrónica está pensada para ser de baja latencia, sin bloqueo, escalable y confiable. La interfaz de solicitud OID sincrónica está disponible a partir de NDIS 6.80, que se incluye en Windows 10, versión 1709 y posteriores.
Comparación con las solicitudes OID normales y directas
Con las solicitudes OID sincrónicas, la carga de la llamada (el propio OID) es exactamente la misma que con las solicitudes OID normales y directas. La única diferencia es en la propia llamada. Por lo tanto, lo que es lo mismo en los tres tipos de OID; solo la forma en que es diferente.
En la tabla siguiente se describen las diferencias entre los OID normales, los OID directos y los OID sincrónicos.
Atributo | OID normal | OID directo | OID sincrónico |
---|---|---|---|
Carga | NDIS_OID_REQUEST | NDIS_OID_REQUEST | NDIS_OID_REQUEST |
Tipos de OID | Estadísticas, consulta, conjunto, método | Estadísticas, consulta, conjunto, método | Estadísticas, consulta, conjunto, método |
Puede ser emitido por | Protocolos, filtros | Protocolos, filtros | Protocolos, filtros |
Se puede completar mediante | Minipuertos, filtros | Minipuertos, filtros | Minipuertos, filtros |
Los filtros pueden modificarse | Sí | Sí | Sí |
NDIS asigna memoria | Para cada filtro (clon de OID) | Para cada filtro (clon de OID) | Solo si un número inusualmente grande de filtros (contexto de llamada) |
Can pend | Sí | Sí | No |
Puede bloquear | Sí | No | No |
IRQL | == PASIVO | <= DISPATCH | <= DISPATCH |
Serializado por NDIS | Sí | No | No |
Se invocan filtros | Recursively | Recursively | Iterativamente |
Filtros clonan el OID | Sí | Sí | No |
Filtros
Al igual que los otros dos tipos de llamadas OID, los controladores de filtro tienen control total sobre la solicitud OID en una llamada sincrónica. Los controladores de filtro pueden observar, interceptar, modificar y emitir OID sincrónicos. Sin embargo, para mejorar la eficacia, la mecánica de un OID sincrónico es algo diferente.
Paso a través, interceptación y origen
Conceptualmente, todas las solicitudes de OID se emiten desde un controlador superior y se completan mediante un controlador inferior. En el camino, la solicitud OID puede pasar por cualquier número de controladores de filtro.
En el caso más común, un controlador de protocolo emite una solicitud de OID y todos los filtros simplemente pasan la solicitud OID hacia abajo, sin modificar. En la ilustración siguiente se muestra este escenario común.
Sin embargo, cualquier módulo de filtro puede interceptar la solicitud OID y completarla. En ese caso, la solicitud no pasa a controladores inferiores, como se muestra en el diagrama siguiente.
En algunos casos, un módulo de filtro puede decidir originar su propia solicitud de OID. Esta solicitud se inicia en el nivel del módulo de filtro y solo atraviesa los controladores inferiores, como se muestra en el diagrama siguiente.
Todas las solicitudes de OID tienen este flujo básico: un controlador superior (ya sea un protocolo o un controlador de filtro) emite una solicitud y un controlador inferior (ya sea un miniporte o un controlador de filtro) lo completa.
Funcionamiento de las solicitudes OID normales y directas
Las solicitudes OID normales o directas se envían de forma recursiva. En el diagrama siguiente se muestra la secuencia de llamadas de función. Tenga en cuenta que la propia secuencia es muy similar a la secuencia descrita en los diagramas de la sección anterior, pero se organiza para mostrar la naturaleza recursiva de las solicitudes.
Si hay suficientes filtros instalados, se forzará a NDIS a asignar una nueva pila de subprocesos para mantener la recursación más profunda.
NDIS considera que una estructura de NDIS_OID_REQUEST es válida solo para un solo salto a lo largo de la pila. Si un controlador de filtro desea pasar la solicitud al siguiente controlador inferior (que es el caso de la gran mayoría de los OID), el controlador de filtro debe insertar varias docenas de líneas de código reutilizable para clonar la solicitud OID. Este reutilizable tiene varios problemas:
- Fuerza una asignación de memoria para clonar el OID. Alcanzar el grupo de memoria es lento y hace imposible garantizar el progreso hacia delante de la solicitud de OID.
- El diseño de la estructura OID debe permanecer igual a lo largo del tiempo porque todos los controladores de filtro codifican de forma rígida la mecánica de copiar el contenido de un NDIS_OID_REQUEST a otro.
- Requerir tanto reutilizable oculta lo que realmente hace el filtro.
Modelo de filtrado para solicitudes de OID sincrónicas
El modelo de filtrado para las solicitudes OID sincrónicas aprovecha la naturaleza sincrónica de la llamada para resolver los problemas descritos en la sección anterior.
Controladores de problemas y completados
A diferencia de las solicitudes OID normales y directas, hay dos enlaces de filtro para las solicitudes de OID sincrónicas: un controlador de problemas y un controlador Completo. Un controlador de filtro no puede registrar ninguno, uno o ambos enlaces.
Las llamadas de problema se invocan para cada controlador de filtro, empezando desde la parte superior de la pila hasta la parte inferior de la pila. Cualquier llamada de problema del filtro puede impedir que el OID continúe hacia abajo y complete el OID con algún código de estado. Si ningún filtro decide interceptar el OID, el OID llega al controlador NIC, que debe completar el OID de forma sincrónica.
Una vez completado un OID, se invocan llamadas completas para cada controlador de filtro, empezando desde dondequiera que se haya completado el OID, hasta la parte superior de la pila. Una llamada Completa puede inspeccionar o modificar la solicitud OID e inspeccionar o modificar el código de estado de finalización del OID.
En el diagrama siguiente se muestra el caso típico, donde un protocolo emite una solicitud OID sincrónica y los filtros no interceptan la solicitud.
Tenga en cuenta que el modelo de llamada para los OID sincrónicos es iterativo. Esto mantiene el uso de la pila limitado por una constante, lo que elimina la necesidad de expandir la pila.
Si un controlador de filtro intercepta un OID sincrónico en su controlador de problemas, el OID no se asigna a filtros inferiores ni al controlador NIC. Sin embargo, todavía se invocan controladores completos para filtros más altos, como se muestra en el diagrama siguiente:
Asignaciones de memoria mínimas
Las solicitudes OID normales y directas requieren un controlador de filtro para clonar un NDIS_OID_REQUEST. Por el contrario, no se permite clonar las solicitudes de OID sincrónicas. La ventaja de este diseño es que los OID sincrónicos tienen una latencia menor: la solicitud OID no se clona repetidamente a medida que recorre la pila de filtros y hay menos oportunidades de error.
Sin embargo, esto genera un nuevo problema. Si no se puede clonar el OID, ¿dónde almacena un controlador de filtro su estado por solicitud? Por ejemplo, supongamos que un controlador de filtro traduce un OID a otro. En el camino hacia abajo de la pila, el filtro debe guardar el OID anterior. Durante la copia de seguridad de la pila, el filtro debe restaurar el OID anterior.
Para solucionar este problema, NDIS asigna una ranura de tamaño de puntero para cada controlador de filtro, para cada solicitud de OID sincrónica en curso. NDIS conserva esta ranura a través de la llamada desde el controlador issue de un filtro a su controlador Complete. Esto permite que el controlador issue guarde el estado desactivado que el controlador Complete consume más adelante. En el siguiente fragmento de código se muestra un ejemplo.
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 guarda un PVOID por filtro por llamada. NDIS asigna de forma heurística un número razonable de ranuras en la pila, de modo que haya asignaciones de grupo cero en el caso común. Normalmente, no es más de siete filtros. Si el usuario configura un caso patológico, NDIS retroceda a una asignación de grupo.
Reutilizable reducido
Tenga en cuenta la reutilizable de ejemplo reutilizable para controlar las solicitudes OID normales o directas. Ese código es el costo de entrada solo para registrar un controlador de OID. Si desea emitir sus propios OID, tiene que agregar otras docenas de líneas de reutilizable. Con los OID sincrónicos, no es necesario la complejidad adicional del control de la finalización asincrónica. Por lo tanto, puede cortar gran parte de esa reutilizableplate.
Este es un controlador de problemas mínimo con OID sincrónicos:
NDIS_STATUS
MyFilterSynchronousOidRequest(
NDIS_HANDLE FilterModuleContext,
NDIS_OID_REQUEST *OidRequest,
PVOID *CallContext)
{
return NDIS_STATUS_SUCCESS;
}
Si desea interceptar o modificar un OID determinado, puede hacerlo agregando solo un par de líneas de código. El controlador completo mínimo es incluso más sencillo:
VOID
MyFilterSynchronousOidRequestComplete(
NDIS_HANDLE FilterModuleContext,
NDIS_OID_REQUEST *OidRequest,
NDIS_STATUS *Status,
PVOID CallContext)
{
return;
}
Del mismo modo, un controlador de filtro puede emitir una nueva solicitud de OID sincrónica propia con solo una línea de código:
status = NdisFSynchronousOidRequest(binding->NdisBindingHandle, &oid);
Por el contrario, un controlador de filtro que necesita emitir un OID normal o directo debe configurar un controlador de finalización asincrónica e implementar algún código para distinguir sus propias finalizaciones de OID de las finalizaciones de OID que acaba de clonar. Un ejemplo de esta reutilizableplate se muestra en Ejemplo reutilizable para emitir una solicitud de OID normal.
Interoperabilidad
Aunque los estilos de llamada regular, directo y sincrónico usan todas las mismas estructuras de datos, las canalizaciones no van al mismo controlador en la minipuerta. Además, algunos OID no se pueden usar en algunas de las canalizaciones. Por ejemplo, OID_PNP_SET_POWER requiere una sincronización cuidadosa y, a menudo, obliga al miniporte a realizar llamadas de bloqueo. Esto dificulta el control en una devolución de llamada de OID directo y evita su uso en una devolución de llamada OID sincrónica.
Por lo tanto, al igual que con las solicitudes OID directas, las llamadas OID sincrónicas solo se pueden usar con un subconjunto de OID. En Windows 10, versión 1709, solo se admite el OID de OID_GEN_RSS_SET_INDIRECTION_TABLE_ENTRIES usado en receive Side Scaling Versión 2 (RSSv2) en la ruta de acceso OID sincrónica.
Implementación de solicitudes de OID sincrónicas
Para obtener más información sobre cómo implementar la interfaz de solicitud OID sincrónica en los controladores, consulte los temas siguientes: