Compartir a través de


Sincronización y notificación en controladores de red

Cada vez que dos subprocesos de ejecución comparten recursos a los que se puede acceder al mismo tiempo, ya sea en un ordenador uniprocesador o en un ordenador multiprocesador simétrico (SMP), es necesario sincronizarlos. 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 podrían ejecutarse simultáneamente en distintos procesadores e intentar modificar los mismos datos. Estos accesos deben sincronizarse.

NDIS proporciona bloqueos de giro que puede utilizar para sincronizar el acceso a los recursos compartidos entre hilos que se ejecutan en el mismo IRQL. Cuando dos subprocesos que comparten un recurso se ejecutan en diferentes IRQLs, NDIS proporciona un mecanismo para elevar temporalmente la 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, es posible que un controlador de tarjeta de interfaz de red (NIC) tenga que realizar una operación periódica, como el sondeo. Los temporizadores proporcionan este mecanismo.

Los eventos proporcionan un mecanismo que dos subprocesos de ejecución pueden usar para sincronizar las operaciones. Por ejemplo, un controlador de miniporte puede probar la interrupción en una NIC escribiendo en dicho 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 que espera 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 por subproceso

Un candado de giro 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 computadoras multiprocesador. Un spinlock maneja la sincronización entre varios subprocesos que se ejecutan simultáneamente en un equipo SMP. Un hilo adquiere un bloqueo de giro antes de acceder a los recursos protegidos. El bloqueo de giro evita que cualquier subproceso, excepto aquel que posee el bloqueo de giro, utilice el recurso. En un equipo SMP, un subproceso que está esperando en el bloqueo de giro sigue intentando adquirirlo hasta que el subproceso que sostiene el bloqueo lo libera.

Otra característica de los spinlocks es el IRQL asociado. Intento de adquisición de un candado de giro eleva temporalmente el IRQL del subproceso solicitante al IRQL asociado con el candado de giro. Esto impide 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 adelantar el subproceso en ejecución, pero estos subprocesos no pueden adquirir el bloqueo de spin porque tienen un IRQL inferior. Por lo tanto, después de que un subproceso haya adquirido un cerrojo de giro, ningún otro subproceso puede adquirir el cerrojo 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 giro es proteger una cola. Por ejemplo, la función de envío del controlador miniport, MiniportSendNetBufferLists, podría poner en cola los paquetes pasados por un controlador de protocolo. Dado que otras funciones del controlador también usan esta cola, MiniportSendNetBufferLists debe proteger la cola con un bloqueo de giro para que solo un hilo a la vez pueda manipular los enlaces o el contenido. MiniportSendNetBufferLists adquiere el bloqueo de giro, agrega el paquete a la cola y, a continuación, libera el bloqueo de giro. El uso de un cerrojo de giro garantiza que el subproceso que tiene el cerrojo sea el único subproceso que modifica los vínculos de la cola mientras el paquete se agrega de forma segura a la cola. Cuando el controlador de minipuerto quita los paquetes de la cola, este tipo de acceso está protegido por el mismo spinlock. Al ejecutar instrucciones que modifiquen 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 giro.

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 spinlock, 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 giro, el controlador debe asumir que otros subprocesos pueden quitar el paquete inmediatamente.

Evitar problemas de spin lock

Para evitar un posible interbloqueo, un controlador NDIS debe liberar todos los bloqueos de giro 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. El subproceso 1, que mantiene el bloqueo de giro NDIS A, llama a una función NdisXxx que intenta adquirir el bloqueo de giro NDIS B mediante la llamada a la función NdisAcquireSpinLock.

  2. Subproceso 2, que sostiene el spin lock de NDIS B, llama a una función NdisXxx que intenta adquirir el spin lock de NDIS A llamando a la función NdisAcquireSpinLock.

  3. El subproceso 1 y el subproceso 2, que están esperando a que el otro suelte su spinlock, entran en interbloqueo.

Los sistemas operativos Microsoft Windows no restringen que un controlador de red mantenga simultáneamente más de un bloqueo de giro. Sin embargo, si una sección del controlador intenta adquirir el bloqueo de giro A mientras mantiene el bloqueo de giro B, y la otra sección intenta adquirir el bloqueo de giro B mientras mantiene el bloqueo de giro A, se produce un 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 giro A antes del bloqueo de giro B, la situación descrita anteriormente no se producirá.

La adquisición de un cerrojo de espera activa eleva el IRQL a DISPATCH_LEVEL y almacena el IRQL anterior en el cerrojo de espera activa. Al liberar el bloqueo de número, se establece IRQL en el valor almacenado en el bloqueo de número. Dado que el NDIS a veces introduce controladores a 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 giro A y liberar el bloqueo de giro B, el código se ejecuta en PASSIVE_LEVEL en lugar de DISPATCH_LEVEL y está sujeto a una interrupción inapropiada.

  • Después de liberar el spin lock B, el código se ejecuta en DISPATCH_LEVEL, lo cual podría ocasionar que el llamador falle mucho más tarde con un error de detención IRQL_NOT_LESS_OR_EQUAL.

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, las funciones de envío y recepción) tienen superposiciones menores para las que se pueden usar dos cierres giratorios. 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 al temporizador. Se llama a la función asociada cuando expira el período especificado en el temporizador. Los temporizadores pueden ser únicos o periódicos. Una vez que se establezca un temporizador periódico, se activará al vencimiento de cada período hasta que se cancele explícitamente. Un temporizador de un solo disparo debe restablecerse cada vez que se activa.

Los temporizadores se crean e inicializan llamando a NdisAllocateTimerObject y se establecen llamando a NdisSetTimerObject. Si se usa un temporizador noperiódico, debe restablecerse llamando a NdisSetTimerObject. Un temporizador se borra 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 mediante una llamada a NdisInitializeEvent. Un subproceso que se ejecuta en IRQL = PASSIVE_LEVEL llama a NdisWaitEvent para colocarse en un estado de espera. Cuando un hilo de controlador espera por un evento, especifica un tiempo máximo de espera y el evento en el que se va a esperar. La espera del subproceso se cumple cuando se invoca NdisSetEvent, lo que provoca que se señale el evento, o cuando expire el intervalo temporal máximo de espera especificado, lo que ocurra primero.

Normalmente, el evento lo establece un subproceso cooperante que llama a NdisSetEvent. Los eventos no están señalizados cuando se crean y deben activarse para señalizar subprocesos en espera. Los eventos permanecen señalados hasta que se llama a NdisResetEvent .

Compatibilidad con varios procesadores en controladores de red