Sincronización y notificación en controladores de red

Cada vez que dos subprocesos de recursos compartidos de ejecución a los que se puede acceder al mismo tiempo, ya sea en un equipo uniprocesador o en un equipo multiprocesador simétrico (SMP), deben sincronizarse. Por ejemplo, en un equipo uniprocesador, si una función de controlador tiene acceso a un recurso compartido y se interrumpe mediante otra función que se ejecuta en un IRQL superior, como un ISR, el recurso compartido debe protegerse para evitar condiciones de carrera que dejan el recurso en un estado indeterminado. En un equipo SMP, dos subprocesos se podrían ejecutar simultáneamente en distintos procesadores e intentar modificar los mismos datos. Estos accesos deben sincronizarse.

NDIS proporciona bloqueos de número que puede usar para sincronizar el acceso a los recursos compartidos entre subprocesos que se ejecutan en el mismo IRQL. Cuando dos subprocesos que comparten un recurso se ejecutan en diferentes IRQLs, NDIS proporciona un mecanismo para generar temporalmente el IRQL del código IRQL inferior para que se pueda serializar el acceso al recurso compartido.

Cuando un subproceso depende de la aparición de un evento fuera del subproceso, el subproceso se basa en la notificación. Por ejemplo, es posible que un controlador deba recibir una notificación cuando haya transcurrido algún período de tiempo para que pueda comprobar su dispositivo. O bien, un controlador de tarjeta de interfaz de red (NIC) podría tener que realizar una operación periódica, como el sondeo. Los temporizadores proporcionan un mecanismo de este tipo.

Los eventos proporcionan un mecanismo que dos subprocesos de ejecución pueden usar para sincronizar las operaciones. Por ejemplo, un controlador de minipuerto puede probar la interrupción en una NIC escribiendo en el dispositivo. El controlador debe esperar a una interrupción para notificar al controlador que la operación se realizó correctamente. Puede usar eventos para sincronizar una operación entre el subproceso esperando a que se complete la interrupción y el subproceso que controla la interrupción.

En las subsecciones siguientes de este tema se describen estos mecanismos NDIS.

Bloqueos de número

Un bloqueo de número proporciona un mecanismo de sincronización para proteger los recursos compartidos por subprocesos en modo kernel que se ejecutan en irQL > PASSIVE_LEVEL en un uniprocesador o en un equipo con varios procesadores. Un bloqueo de número controla la sincronización entre varios subprocesos de ejecución que se ejecutan simultáneamente en un equipo SMP. Un subproceso adquiere un bloqueo de número antes de acceder a los recursos protegidos. El bloqueo de número mantiene cualquier subproceso, pero el que contiene el bloqueo de número del uso del recurso. En un equipo SMP, un subproceso que está esperando en los bucles de bloqueo de número que intentan adquirir el bloqueo de número hasta que el subproceso lo libera el subproceso que contiene el bloqueo.

Otra característica de los bloqueos de número es el IRQL asociado. Intento de adquisición de un bloqueo de giro genera temporalmente el IRQL del subproceso solicitante al IRQL asociado al bloqueo de número. Esto evita que todos los subprocesos IRQL inferiores del mismo procesador adelanten el subproceso en ejecución. Los subprocesos, en el mismo procesador, que se ejecutan en un IRQL superior pueden reemplazar el subproceso en ejecución, pero estos subprocesos no pueden adquirir el bloqueo de número porque tiene un IRQL inferior. Por lo tanto, después de que un subproceso haya adquirido un bloqueo de número, ningún otro subproceso puede adquirir el bloqueo de giro hasta que se haya liberado. Un controlador de red bien escrito minimiza la cantidad de tiempo que se mantiene un bloqueo de giro.

Un uso típico de un bloqueo de número es proteger una cola. Por ejemplo, la función de envío del controlador de miniporte, MiniportSendNetBufferLists, podría poner en cola paquetes pasados por un controlador de protocolo. Dado que otras funciones de controlador también usan esta cola, MiniportSendNetBufferLists debe proteger la cola con un bloqueo de número para que solo un subproceso a la vez pueda manipular los vínculos o el contenido. MiniportSendNetBufferLists adquiere el bloqueo de número, agrega el paquete a la cola y, a continuación, libera el bloqueo de número. El uso de un bloqueo de número garantiza que el subproceso que contiene el bloqueo sea el único subproceso que modifica los vínculos de cola mientras el paquete se agrega de forma segura a la cola. Cuando el controlador de minipuerto quita los paquetes de la cola, este acceso está protegido por el mismo bloqueo de número. Al ejecutar instrucciones que modifican el encabezado de la cola o cualquiera de los campos de vínculo que componen la cola, el controlador debe proteger la cola con un bloqueo de número.

Un controlador debe tener cuidado de no sobreproteger una cola. Por ejemplo, el controlador puede realizar algunas operaciones (por ejemplo, rellenar un campo que contenga la longitud) en el campo reservado por controlador de red de un paquete antes de poner en cola el paquete. El controlador puede hacerlo fuera de la región de código protegida por el bloqueo de número, pero debe hacerlo antes de poner en cola el paquete. Una vez que el paquete está en la cola y el subproceso en ejecución libera el bloqueo de número, el controlador debe asumir que otros subprocesos pueden poner en cola el paquete inmediatamente.

Evitar problemas de bloqueo de número

Para evitar un posible interbloqueo, un controlador NDIS debe liberar todos los bloqueos de número NDIS antes de llamar a una función NDIS distinta de una función NdisXxxSpinlock . Si un controlador NDIS no cumple este requisito, podría producirse un interbloqueo como se indica a continuación:

  1. Subproceso 1, que contiene el bloqueo de número NDIS A, llama a una función NdisXxx que intenta adquirir el bloqueo de número NDIS B mediante una llamada a la función NdisAcquireSpinLock .

  2. Subproceso 2, que contiene el bloqueo de número NDIS B, llama a una función NdisXxx que intenta adquirir el bloqueo de número NDIS A mediante una llamada a la función NdisAcquireSpinLock .

  3. Subproceso 1 y subproceso 2, que están esperando a que el otro libere su bloqueo de número, se interbloquee.

Los sistemas operativos Microsoft Windows no restringen que un controlador de red mantenga simultáneamente más de un bloqueo de número. Sin embargo, si una sección del controlador intenta adquirir el bloqueo de número A mientras mantiene el bloqueo de giro B y otra sección intenta adquirir el bloqueo de giro B mientras mantiene el bloqueo de giro A, los resultados del interbloqueo. Si adquiere más de un bloqueo de giro, un controlador debe evitar el interbloqueo aplicando un orden de adquisición. Es decir, si un controlador exige la adquisición del bloqueo de número A antes del bloqueo de giro B, la situación descrita anteriormente no se producirá.

La adquisición de un bloqueo de número eleva el IRQL para DISPATCH_LEVEL y almacena el IRQL antiguo en el bloqueo de número. Al liberar el bloqueo de número, el IRQL se establece en el valor almacenado en el bloqueo de número. Dado que NDIS a veces entra en controladores en PASSIVE_LEVEL, pueden surgir problemas con la siguiente secuencia de código:

NdisAcquireSpinLock(A);
NdisAcquireSpinLock(B);
NdisReleaseSpinLock(A);
NdisReleaseSpinLock(B);

Un controlador no debe acceder a los bloqueos de número en esta secuencia por los siguientes motivos:

  • Entre liberar el bloqueo de número A y liberar el bloqueo de número B, el código se ejecuta en PASSIVE_LEVEL en lugar de DISPATCH_LEVEL y está sujeto a interrupciones inapropiadas.

  • Después de liberar el bloqueo de número B, el código se ejecuta en DISPATCH_LEVEL que podría provocar un error del autor de la llamada en mucho más tarde con un error de IRQL_NOT_LESS_OR_EQUAL detención.

El uso de bloqueos de giro afecta al rendimiento y, en general, un controlador no debe usar muchos bloqueos de giro. En ocasiones, las funciones que suelen ser distintas (por ejemplo, funciones de envío y recepción) tienen superposiciones menores para las que se pueden usar dos bloqueos de número. El uso de más de un bloqueo de giro puede ser un equilibrio valioso para permitir que las dos funciones funcionen de forma independiente en procesadores independientes.

Temporizadores

Los temporizadores se usan para las operaciones de sondeo o de tiempo de espera. Un controlador crea un temporizador y asocia una función con el temporizador. Se llama a la función asociada cuando expira el período especificado en el temporizador. Los temporizadores pueden ser de un solo disparo o periódicos. Una vez establecido un temporizador periódico, se seguirá activando a la expiración de cada período hasta que se borre explícitamente. Se debe restablecer un temporizador de un solo disparo cada vez que se activa.

Los temporizadores se crean e inicializan mediante una llamada a NdisAllocateTimerObject y se establecen llamando a NdisSetTimerObject. Si se usa un temporizador noperiódico, debe restablecerse llamando a NdisSetTimerObject. Se borra un temporizador llamando a NdisCancelTimerObject.

Eventos

Los eventos se usan para sincronizar las operaciones entre dos subprocesos de ejecución. Un evento lo asigna un controlador e inicializa llamando a NdisInitializeEvent. Un subproceso que se ejecuta en IRQL = PASSIVE_LEVEL llama a NdisWaitEvent para colocarse en un estado de espera. Cuando un subproceso de controlador espera en un evento, especifica un tiempo máximo para esperar, así como el evento en el que se va a esperar. La espera del subproceso se satisface cuando se llama a NdisSetEvent , lo que hace que se señale el evento o cuando expire el intervalo máximo de tiempo de espera especificado, lo que ocurra primero.

Normalmente, el evento se establece mediante un subproceso de cooperación que llama a NdisSetEvent. Los eventos no se firman cuando se crean y se deben establecer para indicar subprocesos en espera. Los eventos permanecen señalados hasta que se llama a NdisResetEvent .

Compatibilidad con varios procesadores en controladores de red