Utilisation de verrous de rotation : exemple
Réduire la durée pendant laquelle un pilote tient les verrous de rotation peut améliorer considérablement les performances du pilote et du système dans son ensemble. Par exemple, considérez la figure suivante, qui montre comment un verrou de rotation d’interruption protège les données spécifiques à l’appareil qui doivent être partagées entre un ISR et les routines StartIo et DpcForIsr dans une machine SMP.
Alors que l’ISR du pilote s’exécute sur DIRQL sur un processeur, sa routine StartIo s’exécute à DISPATCH_LEVEL sur un deuxième processeur. Le gestionnaire d’interruptions du noyau contient l’objet InterruptSpinLock pour l’ISR du pilote, qui accède aux données protégées spécifiques au périphérique, telles que l’état ou les pointeurs vers les registres de périphériques (SynchronizeContext), dans l’extension de périphérique du pilote. La routine StartIo , qui est prête à accéder à SynchronizeContext, appelle KeSynchronizeExecution, en passant un pointeur vers les objets d’interruption associés, le SynchronizeContext partagé et la routine SynchCritSection du pilote (AccessDevice dans la figure précédente).
Jusqu’à ce que l’ISR retourne, libérant ainsi l’objet InterruptSpinLock du pilote, KeSynchronizeExecutiontourne sur le deuxième processeur, empêchant AccessDevice de toucher SynchronizeContext. Toutefois, KeSynchronizeExecution déclenche également IRQL sur le deuxième processeur sur le SynchronizeIrql des objets d’interruption, empêchant ainsi une autre interruption d’appareil de se produire sur ce processeur afin qu’AccessDevice puisse être exécuté sur DIRQL dès que l’ISR est retourné. Toutefois, des interruptions DIRQL plus élevées pour d’autres appareils, des interruptions d’horloge et des interruptions de panne d’alimentation peuvent toujours se produire sur l’un ou l’autre des processeurs.
Lorsque l’ISR met en file d’attente le DpcForIsr du pilote et retourne, AccessDevice s’exécute sur le deuxième processeur à l’emplacement SynchronizeIrql des objets d’interruption associés et accède à SynchronizeContext. Pendant ce temps, DpcForIsr est exécuté sur un autre processeur à DISPATCH_LEVEL IRQL. Le DpcForIsr étant également prêt à accéder à SynchronizeContext, il appelle KeSynchronizeExecution à l’aide des mêmes paramètres que la routine StartIo à l’étape 1.
Lorsque KeSynchronizeExecution acquiert le verrou de rotation et exécute AccessDevice pour le compte de la routine StartIo , la routine de synchronisation fournie par le pilote AccessDevice reçoit un accès exclusif à SynchronizeContext. Étant donné qu’AccessDevice s’exécute sur SynchronizeIrql, l’ISR du pilote ne peut pas acquérir le verrou de rotation et accéder à la même zone tant que le verrou de rotation n’est pas libéré, même si l’appareil s’interrompt sur un autre processeur pendant l’exécution d’AccessDevice.
AccessDevice retourne, libérant le verrou de rotation. La routine StartIo reprend son exécution à DISPATCH_LEVEL sur le deuxième processeur. KeSynchronizeExecution exécute désormais AccessDevice sur le troisième processeur, afin qu’il puisse accéder à SynchronizeContext pour le compte de DpcForIsr. Toutefois, si une interruption d’appareil s’est produite avant que le DpcForIsr appelé KeSynchronizeExecution à l’étape 2, l’ISR peut s’exécuter sur un autre processeur avant que KeSynchronizeExecution puisse acquérir le verrou de rotation et exécuter AccessDevice sur le troisième processeur.
Comme l’illustre la figure précédente, alors qu’une routine s’exécutant sur un processeur contient un verrou de rotation, toutes les autres routines qui tentent d’acquérir ce verrou de rotation n’obtiennent aucun travail. Chaque routine qui tente d’acquérir un verrou de rotation déjà détenu tourne simplement sur son processeur actuel jusqu’à ce que le détenteur libère le verrou de rotation. Lorsqu’un verrou de rotation est libéré, une seule routine peut l’acquérir ; toutes les autres routines qui essaient actuellement d’acquérir le même verrou de rotation continueront à tourner.
Le détenteur d’un verrou de rotation s’exécute à un IRQL élevé : soit à DISPATCH_LEVEL pour un verrou de spin exécutif, soit au niveau d’un DIRQL pour un verrou de rotation d’interruption. Les appelants de KeAcquireSpinLock et KeAcquireInStackQueuedSpinLock s’exécutent à DISPATCH_LEVEL jusqu’à ce qu’ils appellent KeReleaseSpinLock ou KeReleaseInStackQueuedSpinLock pour libérer le verrou. Les appelants de KeSynchronizeExecution déclenchent automatiquement l’IRQL sur le processeur actuel sur le SynchronizeIrql des objets d’interruption jusqu’à ce que la routine SynchCritSection fournie par l’appelant se termine et que KeSynchronizeExecution retourne le contrôle. Pour plus d’informations, consultez Appeler des routines de support qui utilisent des verrous de rotation.
Gardez à l’esprit le fait suivant concernant l’utilisation des verrous de rotation :
Tout le code qui s’exécute à un IRQL inférieur ne peut pas effectuer de travail sur l’ensemble des processeurs occupés par un support de verrouillage tournant et par d’autres routines qui tentent d’acquérir le même verrou de rotation.
Par conséquent, la réduction du temps pendant lequel un pilote tient les verrous de rotation entraîne de meilleures performances du pilote et contribue considérablement à de meilleures performances globales du système.
Comme le montre la figure précédente, le gestionnaire d’interruptions du noyau exécute des routines s’exécutant au même IRQL dans un ordinateur multiprocesseur sur la base du premier arrivé, premier servi. Le noyau effectue également les opérations suivantes :
Lorsqu’une routine de pilote appelle KeSynchronizeExecution, le noyau entraîne l’exécution de la routine SynchCritSection du pilote sur le processeur à partir duquel l’appel à KeSynchronizeExecution s’est produit (voir étapes 1 et 3).
Lorsque l’ISR d’un pilote met en file d’attente son DpcForIsr, le noyau entraîne l’exécution de la DPC sur le premier processeur disponible sur lequel l’IRQL se trouve en dessous de DISPATCH_LEVEL. Il ne s’agit pas nécessairement du même processeur à partir duquel l’appel IoRequestDpc s’est produit (voir Étape 2).
Les opérations d’E/S pilotées par interruption d’un pilote peuvent avoir tendance à être sérialisées dans un ordinateur monoprocesseur, mais les mêmes opérations peuvent être véritablement asynchrones dans un ordinateur SMP. Comme le montre la figure précédente, l’ISR d’un pilote peut s’exécuter sur CPU4 sur un ordinateur SMP avant que son DpcForIsr commence à traiter une IRP pour laquelle l’ISR a déjà géré une interruption de périphérique sur CPU1.
En d’autres termes, vous ne devez pas supposer qu’un verrou de rotation d’interruption peut protéger les données spécifiques à l’opération que l’ISR enregistre lorsqu’il s’exécute sur un processeur d’être remplacées par l’ISR lorsqu’une interruption d’appareil se produit sur un autre processeur avant l’exécution de la routine DpcForIsr ou CustomDpc .
Bien qu’un pilote puisse essayer de sérialiser toutes les opérations d’E/S pilotées par interruption pour conserver les données collectées par l’ISR, ce pilote ne s’exécuterait pas beaucoup plus rapidement dans un ordinateur SMP que dans un ordinateur monoprocesseur. Pour obtenir les meilleures performances de pilote possibles tout en restant portable sur les plateformes monoprocesseur et multiprocesseur, les pilotes doivent utiliser une autre technique pour enregistrer les données spécifiques à l’opération obtenues par l’ISR en vue d’un traitement ultérieur par le DpcForIsr.
Par exemple, un ISR peut enregistrer des données spécifiques à l’opération dans l’IRP qu’il transmet à DpcForIsr. Une amélioration de cette technique consiste à implémenter un DpcForIsr qui consulte un nombre isr augmenté, traite le nombre d’IRP du nombre à l’aide des données fournies par ISR et réinitialise le nombre à zéro avant de revenir. Bien entendu, le nombre doit être protégé par le verrou de rotation d’interruption du pilote, car son ISR et un SynchCritSection conservent sa valeur de manière dynamique.