Synchronisation et notification dans les pilotes réseau

Chaque fois que deux threads d’exécution partagent des ressources accessibles en même temps, dans un ordinateur uniprocesseur ou sur un ordinateur multiprocesseur symétrique (SMP), ils doivent être synchronisés. Par exemple, sur un ordinateur uniprocesseur, si une fonction de pilote accède à une ressource partagée et est interrompue par une autre fonction qui s’exécute à un IRQL supérieur, tel qu’un ISR, la ressource partagée doit être protégée pour empêcher les conditions de concurrence qui laissent la ressource dans un état indéterminé. Sur un ordinateur SMP, deux threads peuvent s’exécuter simultanément sur des processeurs différents et tenter de modifier les mêmes données. Ces accès doivent être synchronisés.

NDIS fournit des verrous de rotation que vous pouvez utiliser pour synchroniser l’accès aux ressources partagées entre des threads qui s’exécutent au même IRQL. Lorsque deux threads qui partagent une ressource s’exécutent à des IRQL différents, NDIS fournit un mécanisme pour déclencher temporairement l’IRQL du code IRQL inférieur afin que l’accès à la ressource partagée puisse être sérialisé.

Lorsqu’un thread dépend de l’occurrence d’un événement en dehors du thread, celui-ci s’appuie sur la notification. Par exemple, un pilote peut avoir besoin d’être averti lorsqu’un certain délai s’est écoulé afin qu’il puisse case activée son appareil. Ou un pilote d’interface réseau carte (NIC) peut devoir effectuer une opération périodique telle que l’interrogation. Les minuteurs fournissent un tel mécanisme.

Les événements fournissent un mécanisme que deux threads d’exécution peuvent utiliser pour synchroniser les opérations. Par exemple, un pilote miniport peut tester l’interruption sur une carte réseau en écrivant sur l’appareil. Le pilote doit attendre une interruption pour l’informer que l’opération a réussi. Vous pouvez utiliser des événements pour synchroniser une opération entre le thread en attente de la fin de l’interruption et le thread qui gère l’interruption.

Les sous-sections suivantes de cette rubrique décrivent ces mécanismes NDIS.

Verrous de rotation

Un verrou de rotation fournit un mécanisme de synchronisation pour protéger les ressources partagées par les threads en mode noyau qui s’exécutent au PASSIVE_LEVEL IRQL > sur un ordinateur monoprocesseur ou multiprocesseur. Un verrou de rotation gère la synchronisation entre différents threads d’exécution qui s’exécutent simultanément sur un ordinateur SMP. Un thread acquiert un verrou de rotation avant d’accéder aux ressources protégées. Le verrou de rotation empêche n’importe quel thread, à l’exception de celui qui contient le verrou de rotation, d’utiliser la ressource. Sur un ordinateur SMP, un thread qui attend sur le verrou de rotation effectue une tentative d’acquisition du verrou de rotation jusqu’à ce qu’il soit libéré par le thread qui contient le verrou.

Une autre caractéristique des verrous de rotation est l’IRQL associé. La tentative d’acquisition d’un verrou de rotation déclenche temporairement l’IRQL du thread demandeur vers l’IRQL associé au verrou de rotation. Cela empêche tous les threads IRQL inférieurs sur le même processeur de préempter le thread en cours d’exécution. Les threads, sur le même processeur, s’exécutant à un IRQL supérieur peuvent préempter le thread en cours d’exécution, mais ces threads ne peuvent pas acquérir le verrou de rotation, car il a un IRQL inférieur. Par conséquent, une fois qu’un thread a acquis un verrou de rotation, aucun autre thread ne peut l’acquérir tant qu’il n’a pas été libéré. Un pilote réseau bien écrit réduit la durée pendant laquelle un verrou de rotation est tenu.

Une utilisation classique d’un verrou de rotation consiste à protéger une file d’attente. Par exemple, la fonction d’envoi du pilote miniport, MiniportSendNetBufferLists, peut mettre en file d’attente les paquets qui lui sont passés par un pilote de protocole. Étant donné que d’autres fonctions de pilote utilisent également cette file d’attente, MiniportSendNetBufferLists doit protéger la file d’attente avec un verrou de rotation afin qu’un seul thread à la fois puisse manipuler les liens ou le contenu. MiniportSendNetBufferLists acquiert le verrou de rotation, ajoute le paquet à la file d’attente, puis libère le verrou de rotation. L’utilisation d’un verrou de rotation garantit que le thread contenant le verrou est le seul thread qui modifie les liens de file d’attente pendant que le paquet est ajouté en toute sécurité à la file d’attente. Lorsque le pilote miniport supprime les paquets de la file d’attente, un tel accès est protégé par le même verrou de rotation. Lors de l’exécution d’instructions qui modifient la tête de la file d’attente ou l’un des champs de lien qui composent la file d’attente, le pilote doit protéger la file d’attente avec un verrou de rotation.

Un pilote doit veiller à ne pas surprotéger une file d’attente. Par exemple, le pilote peut effectuer certaines opérations (par exemple, remplir un champ contenant la longueur) dans le champ réservé au pilote réseau d’un paquet avant qu’il ne le met en file d’attente. Le pilote peut effectuer cette opération en dehors de la région de code protégée par le verrou de rotation, mais doit le faire avant de mettre en file d’attente le paquet. Une fois que le paquet se trouve dans la file d’attente et que le thread en cours d’exécution libère le verrou de rotation, le pilote doit supposer que d’autres threads peuvent immédiatement le mettre en file d’attente.

Éviter les problèmes de verrouillage de rotation

Pour éviter un blocage possible, un pilote NDIS doit libérer tous les verrous de rotation NDIS avant d’appeler une fonction NDIS autre qu’une fonction Spinlock NdisXxx. Si un pilote NDIS ne respecte pas cette exigence, un blocage peut se produire comme suit :

  1. Le thread 1, qui contient le verrou de rotation NDIS A, appelle une fonction NdisXxx qui tente d’acquérir le verrou de rotation NDIS B en appelant la fonction NdisAcquireSpinLock .

  2. Le thread 2, qui contient le verrou de rotation NDIS B, appelle une fonction NdisXxx qui tente d’acquérir le verrou de rotation NDIS A en appelant la fonction NdisAcquireSpinLock .

  3. Le thread 1 et le thread 2, qui attendent chacun que l’autre libère son verrou de rotation, se bloquent.

Les systèmes d’exploitation Microsoft Windows n’empêchent pas un pilote réseau de contenir simultanément plusieurs verrous de rotation. Toutefois, si une section du pilote tente d’acquérir le verrou de rotation A en tenant le verrou de rotation B, et qu’une autre section tente d’acquérir le verrou de rotation B en tenant le verrou de rotation A, l’interblocage se produit. S’il acquiert plusieurs verrous de rotation, un pilote doit éviter l’interblocage en appliquant un ordre d’acquisition. Autrement dit, si un pilote applique l’acquisition du verrou de rotation A avant le verrouillage de rotation B, la situation décrite ci-dessus ne se produira pas.

L’acquisition d’un verrou de rotation élève l’IRQL à DISPATCH_LEVEL et stocke l’ancien IRQL dans le verrou de rotation. La libération du verrou de rotation définit l’IRQL sur la valeur stockée dans le verrou de rotation. Étant donné que NDIS entre parfois des pilotes à PASSIVE_LEVEL, des problèmes peuvent survenir avec la séquence de code suivante :

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

Un pilote ne doit pas accéder aux verrous de rotation dans cette séquence pour les raisons suivantes :

  • Entre la libération du verrou de rotation A et la libération du verrou de rotation B, le code s’exécute à PASSIVE_LEVEL au lieu de DISPATCH_LEVEL et est sujet à une interruption inappropriée.

  • Après avoir libéré le verrou de rotation B, le code s’exécute à DISPATCH_LEVEL ce qui peut provoquer une erreur de l’appelant beaucoup plus tard avec une erreur d’arrêt IRQL_NOT_LESS_OR_EQUAL.

L’utilisation de verrous de rotation a un impact sur les performances et, en général, un pilote ne doit pas utiliser de nombreux verrous de rotation. Parfois, les fonctions qui sont généralement distinctes (par exemple, les fonctions d’envoi et de réception) ont des chevauchements mineurs pour lesquels deux verrous de rotation peuvent être utilisés. L’utilisation de plusieurs verrous de rotation peut être un compromis intéressant pour permettre aux deux fonctions de fonctionner indépendamment sur des processeurs distincts.

Minuteurs

Les minuteurs sont utilisés pour les opérations d’interrogation ou de minutage. Un pilote crée un minuteur et associe une fonction au minuteur. La fonction associée est appelée lorsque la période spécifiée dans le minuteur expire. Les minuteurs peuvent être one-shot ou périodiques. Une fois qu’un minuteur périodique est défini, il continue à se déclencher à l’expiration de chaque période jusqu’à ce qu’il soit explicitement effacé. Un minuteur à un coup doit être réinitialisé chaque fois qu’il se déclenche.

Les minuteurs sont créés et initialisés en appelant NdisAllocateTimerObject et définis en appelant NdisSetTimerObject. Si un minuteur non ponctuel est utilisé, il doit être réinitialisé en appelant NdisSetTimerObject. Un minuteur est effacé en appelant NdisCancelTimerObject.

Événements

Les événements sont utilisés pour synchroniser les opérations entre deux threads d’exécution. Un événement est alloué par un pilote et initialisé en appelant NdisInitializeEvent. Un thread s’exécutant à IRQL = PASSIVE_LEVEL appelle NdisWaitEvent pour se placer dans un état d’attente. Lorsqu’un thread de pilote attend sur un événement, il spécifie une durée maximale d’attente, ainsi que l’événement à attendre. L’attente du thread est satisfaite lorsque NdisSetEvent est appelé pour signaler l’événement, ou lorsque l’intervalle de temps d’attente maximal spécifié expire, selon la première éventualité.

En règle générale, l’événement est défini par un thread de coopération qui appelle NdisSetEvent. Les événements ne sont pas signés lorsqu’ils sont créés et doivent être définis pour signaler les threads en attente. Les événements restent signalés jusqu’à l’appel de NdisResetEvent .

Prise en charge du multiprocesseur dans les pilotes réseau