Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Chaque fois que deux threads d'exécution partagent des ressources qui peuvent être accédées en même temps, soit sur un ordinateur uniprocesseur, soit 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 compétition qui peuvent laisser la ressource dans un état indéterminé. Sur un ordinateur SMP, deux threads peuvent s’exécuter simultanément sur différents processeurs et tenter de modifier les mêmes données. Ces accès doivent être synchronisés.
NDIS fournit des spinlocks que vous pouvez utiliser pour synchroniser l’accès aux ressources partagées entre les threads qui s’exécutent au même IRQL. Lorsque deux threads qui partagent une ressource s'exécutent à différents niveaux d'IRQL, NDIS fournit un mécanisme permettant de temporairement élever le niveau IRQL du thread prioritaire inférieur afin de sérialiser l'accès à la ressource partagée.
Lorsqu’un thread dépend de l’occurrence d’un événement en dehors du thread, le thread s’appuie sur la notification. Par exemple, un pilote peut avoir besoin d’être averti quand une période donnée est passée afin qu’il puisse vérifier son appareil. Ou un pilote de carte d'interface réseau (NIC) peut être amené à effectuer une opération périodique telle que le sondage. 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 un NIC en écrivant sur le périphérique. Le pilote doit attendre une interruption pour avertir le pilote 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 s’exécutant sur irQL > PASSIVE_LEVEL dans un monoprocesseur ou un ordinateur 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 tout thread sauf celui qui détient le verrou de l'utiliser la ressource. Sur un ordinateur SMP, un thread qui attend sur le verrou de rotation tente d’acquérir le verrou jusqu’à ce qu’il soit libéré par le thread qui détient le verrou.
Une autre caractéristique des verrous de rotation est l’IRQL associé. Une tentative d’acquisition d’un verrou de rotation élève temporairement l’IRQL du thread demandeur au niveau 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 plus élevé peuvent préemptiser 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 spin, aucun autre thread ne peut acquérir le verrou de rotation 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 conservé.
Une utilisation classique pour 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 transmis 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 retire 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 liaison constituant 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. Par exemple, le pilote peut effectuer certaines opérations (par exemple, remplir un champ contenant la longueur) dans le champ réservé du pilote réseau d’un paquet avant de mettre en file d’attente le paquet. Le pilote peut le faire 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 retirer immédiatement le paquet de la file d’attente.
Éviter les problèmes de verrou tournant
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 NdisXxxSpinlock . Si un pilote NDIS ne respecte pas cette exigence, un interblocage peut se produire comme suit :
Thread 1, qui contient le verrou de rotation NDIS A, appelle une fonction NdisXxx qui tente d’acquérir le verrou de spin NDIS B en appelant la fonction NdisAcquireSpinLock .
Thread 2, qui contient le verrou de spin NDIS B, appelle une fonction NdisXxx qui tente d’acquérir le verrou de spin NDIS A en appelant la fonction NdisAcquireSpinLock .
Thread 1 et thread 2, qui attendent l’un de l’autre pour libérer son verrou de rotation, deviennent bloqués.
Les systèmes d’exploitation Microsoft Windows n’empêchent pas un pilote réseau de conserver simultanément plusieurs verrous de rotation. Toutefois, si une section du pilote tente d’acquérir le verrou de rotation A tout en conservant le verrou de rotation B, et qu'une autre section tente d’acquérir le verrou de rotation B tout en maintenant le verrou de rotation A, il en résulte un blocage. S’il acquiert plusieurs verrous de rotation, un pilote doit éviter l’interblocage en appliquant une ordonnance 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 produit pas.
L’acquisition d’un verrou de rotation déclenche 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 soumis à une interruption inappropriée.
Après avoir libéré le verrou de rotation B, le code s’exécute à DISPATCH_LEVEL, ce qui peut entraîner une erreur pour l’appelant à un moment ultérieur 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 valable pour permettre aux deux fonctions de fonctionner indépendamment sur des processeurs distincts.
Minuteries
Les minuteurs sont utilisés pour les opérations d’interrogation ou de délai d’attente. 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 ponctuels ou périodiques. Une fois qu’un minuteur périodique est défini, il continuera à se déclencher à l’expiration de chaque période jusqu’à ce qu’il soit explicitement effacé. Un minuteur ponctuel 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 périodique 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 un événement, il spécifie un temps d’attente maximal, ainsi que l’événement à attendre. L’attente du thread est satisfaite lorsque NdisSetEvent est appelé à l’origine du signal de l’événement ou lorsque l’intervalle maximal d’attente spécifié expire, selon ce qui se produit en premier.
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 signalés lorsqu'ils sont créés et doivent être activés pour signaler les threads en attente. Les événements restent signalés jusqu’à ce que NdisResetEvent soit appelé.