Utilisation de la synchronisation automatique
Presque tout le code d’un pilote basé sur l’infrastructure réside dans des fonctions de rappel d’événements. Le framework synchronise automatiquement la plupart des fonctions de rappel d’un pilote, comme suit :
L’infrastructure synchronise toujours les fonctions de rappel d’événement d’objet d’appareil général, d’objet d’appareil fonctionnel (FDO) et d’objet d’appareil physique (PDO) afin qu’une seule des fonctions de rappel (à l’exception d’EvtDeviceSurpriseRemoval, EvtDeviceQueryRemove et EvtDeviceQueryStop) puisse être appelée à la fois pour chaque appareil. Ces fonctions de rappel prennent en charge les événements de Plug-and-Play (PnP) et de gestion de l’alimentation, et sont appelées à l’adresse IRQL = PASSIVE_LEVEL.
Si vous le souhaitez, l’infrastructure peut synchroniser l’exécution des fonctions de rappel qui gèrent les demandes d’E/S d’un pilote, afin que ces fonctions de rappel s’exécutent une par une. Plus précisément, l’infrastructure peut synchroniser les fonctions de rappel pour les objets file d’attente, d’interruption, d’appel de procédure différée (DPC),de minuteur, d’élément de travail et de fichier , ainsi que la fonction de rappel EvtRequestCancel de l’objet de requête. L’infrastructure appelle la plupart de ces fonctions de rappel dans IRQL = DISPATCH_LEVEL, mais vous pouvez forcer les fonctions de rappel de file d’attente et d’objet de fichier à s’exécuter à IRQL = PASSIVE_LEVEL. (Les fonctions de rappel d’élément de travail s’exécutent toujours à PASSIVE_LEVEL.)
L’infrastructure implémente cette synchronisation automatique à l’aide d’un ensemble de verrous de synchronisation internes. L’infrastructure garantit que deux threads ou plus ne peuvent pas appeler la même fonction de rappel en même temps, car chaque thread doit attendre qu’il puisse acquérir un verrou de synchronisation avant d’appeler une fonction de rappel. (Si vous le souhaitez, les pilotes peuvent également acquérir ces verrous de synchronisation si nécessaire. Pour plus d’informations, consultez Utilisation de verrous d’infrastructure.)
Votre pilote doit stocker des données spécifiques à l’objet dans l’espace contextuel de l’objet. Si votre pilote utilise uniquement des interfaces définies par l’infrastructure, seules les fonctions de rappel qui reçoivent un handle à l’objet peuvent accéder à ces données. Si l’infrastructure synchronise les appels aux fonctions de rappel du pilote, une seule fonction de rappel est appelée à la fois et l’espace de contexte de l’objet n’est accessible qu’à une seule fonction de rappel à la fois.
À moins que votre pilote implémente la gestion des interruptions de niveau passif, le code qui gère les données d’interruption et y accède doit s’exécuter à l’IRQL (DIRQL) de l’appareil et nécessite une synchronisation supplémentaire. Pour plus d’informations, consultez Synchronisation du code d’interruption.
Si votre pilote active la synchronisation automatique des fonctions de rappel qui gèrent les demandes d’E/S, l’infrastructure synchronise ces fonctions de rappel afin qu’elles s’exécutent une par une. Le tableau suivant répertorie les fonctions de rappel que l’infrastructure synchronise.
Type d’objet | Fonctions de rappel synchronisées |
---|---|
Objet Queue |
Gestionnaires de requêtes, EvtIoQueueState, EvtIoResume, EvtIoStop |
File (objet) |
Toutes les fonctions de rappel |
Objet Requête |
Si vous le souhaitez, l’infrastructure peut également synchroniser ces fonctions de rappel avec toutes les fonctions de rappel d’objet d’interruption, DPC, élément de travail et minuteur que votre pilote fournit pour l’appareil (à l’exclusion de la fonction de rappel EvtInterruptIsr de l’objet d’interruption). Pour activer cette synchronisation supplémentaire, le pilote doit définir le membre AutomaticSerialization des structures de configuration de ces objets sur TRUE.
En résumé, la fonctionnalité de synchronisation automatique de l’infrastructure fournit les fonctionnalités suivantes :
L’infrastructure synchronise toujours les fonctions pnP et de rappel de gestion de l’alimentation de chaque appareil.
Si vous le souhaitez, l’infrastructure peut synchroniser les gestionnaires de requêtes d’une file d’attente d’E/S et quelques fonctions de rappel supplémentaires (voir le tableau précédent).
Un pilote peut demander à l’infrastructure de synchroniser les fonctions de rappel pour les objets d’interruption, DPC, élément de travail et minuteur.
Les pilotes doivent synchroniser le code qui services interrompt et accède aux données d’interruption à l’aide des techniques décrites dans Synchronisation du code d’interruption.
L’infrastructure ne synchronise pas les autres fonctions de rappel d’un pilote, telles que la fonction de rappel CompletionRoutine du pilote ou les fonctions de rappel que l’objet cible d’E/S définit. Au lieu de cela, l’infrastructure fournit des verrous supplémentaires que les pilotes peuvent utiliser pour synchroniser ces fonctions de rappel.
Choix d’une étendue de synchronisation
Vous pouvez choisir de faire en sorte que l’infrastructure synchronise toutes les fonctions de rappel associées à toutes les files d’attente d’E/S d’un appareil. Vous pouvez également choisir de faire en sorte que l’infrastructure synchronise séparément les fonctions de rappel pour chacune des files d’attente d’E/S d’un appareil. Les options de synchronisation disponibles pour votre pilote sont les suivantes :
Synchronisation au niveau de l’appareil
L’infrastructure synchronise les fonctions de rappel que contient le tableau précédent, pour toutes les files d’attente d’E/S de l’appareil, afin qu’elles s’exécutent une par une. L’infrastructure réalise cette synchronisation en acquérant le verrou de synchronisation de l’appareil avant d’appeler une fonction de rappel.
Synchronisation au niveau de la file d’attente
L’infrastructure synchronise les fonctions de rappel que contient le tableau précédent, pour chaque file d’attente d’E/S individuelle, afin qu’elles s’exécutent une par une. L’infrastructure réalise cette synchronisation en acquérant le verrou de synchronisation de la file d’attente avant d’appeler une fonction de rappel.
Aucune synchronisation
Le framework ne synchronise pas l’exécution des fonctions de rappel que contient la table précédente et n’acquiert pas de verrou de synchronisation avant d’appeler les fonctions de rappel. Si la synchronisation est requise, le pilote doit la fournir.
Pour spécifier si vous souhaitez que l’infrastructure fournisse une synchronisation au niveau du périphérique, une synchronisation au niveau de la file d’attente ou aucune synchronisation pour votre pilote, vous pouvez spécifier une étendue de synchronisation pour votre objet de pilote, vos objets de périphérique ou vos objets de file d’attente. Le membre SynchronizationScope de la structure WDF_OBJECT_ATTRIBUTES d’un objet identifie l’étendue de synchronisation de l’objet. Les valeurs d’étendue de synchronisation que votre pilote peut spécifier sont les suivantes :
WdfSynchronizationScopeDevice
L’infrastructure se synchronise en obtenant le verrou de synchronisation d’un objet d’appareil.
WdfSynchronizationScopeQueue
L’infrastructure se synchronise en obtenant le verrou de synchronisation d’un objet file d’attente.
WdfSynchronizationScopeNone
L’infrastructure ne se synchronise pas et n’obtient pas de verrou de synchronisation.
WdfSynchronizationScopeInheritFromParent
L’infrastructure obtient la valeur SynchronizationScope de l’objet à partir de l’objet parent de l’objet.
En général, nous vous déconseillons d’utiliser la synchronisation au niveau de l’appareil.
Pour plus d’informations sur les valeurs d’étendue de synchronisation, consultez WDF_SYNCHRONIZATION_SCOPE.
L’étendue de synchronisation par défaut pour les objets de pilote est WdfSynchronizationScopeNone. L’étendue de synchronisation par défaut pour les objets d’appareil et de file d’attente est WdfSynchronizationScopeInheritFromParent.
Si vous souhaitez que l’infrastructure fournisse une synchronisation au niveau de l’appareil pour tous les appareils, vous pouvez effectuer les étapes suivantes :
Définissez SynchronizationScope sur WdfSynchronizationScopeDevice dans la structure WDF_OBJECT_ATTRIBUTES de l’objet pilote du pilote .
Utilisez la valeur par défaut WdfSynchronizationScopeInheritFromParent pour chaque objet d’appareil .
Pour fournir une synchronisation au niveau de l’appareil pour des appareils individuels, vous pouvez également effectuer les étapes suivantes :
Utilisez la valeur par défaut WdfSynchronizationScopeNone pour l’objet driver .
Définissez SynchronizationScope sur WdfSynchronizationScopeDevice dans la structure WDF_OBJECT_ATTRIBUTES d’objets d’appareil individuels.
Si vous souhaitez que l’infrastructure fournisse une synchronisation au niveau de la file d’attente pour un appareil, les techniques suivantes sont disponibles :
Pour les versions de framework 1.9 et ultérieures, vous devez activer la synchronisation au niveau de la file d’attente pour les files d’attente individuelles en définissant WdfSynchronizationScopeQueue dans la structure WDF_OBJECT_ATTRIBUTES de l’objet file d’attente. Il s’agit de la technique préférée.
Vous pouvez également utiliser les étapes suivantes dans toutes les versions du framework :
- Définissez SynchronizationScope sur WdfSynchronizationScopeQueue dans la structure WDF_OBJECT_ATTRIBUTES de l’objet d’appareil .
- Utilisez la valeur par défaut WdfSynchronizationScopeInheritFromParent pour les objets de file d’attente de chaque appareil.
Si vous ne souhaitez pas que l’infrastructure synchronise les fonctions de rappel qui gèrent les demandes d’E/S de votre pilote, utilisez la valeur SynchronizationScope par défaut pour les objets de pilote, de périphérique et de file d’attente de votre pilote. Dans ce cas, l’infrastructure ne synchronise pas automatiquement les fonctions de rappel liées aux demandes d’E/S du pilote, et les fonctions de rappel peuvent être appelées à l’adresse IRQL <= DISPATCH_LEVEL.
Notez que la définition d’une valeur SynchronizationScope synchronise uniquement les fonctions de rappel que contient la table précédente. Si vous souhaitez que l’infrastructure synchronise également les fonctions de rappel d’objet d’interruption du pilote, DPC, élément de travail et minuteur, le pilote doit définir le membre AutomaticSerialization des structures de configuration de ces objets sur TRUE.
Toutefois, vous pouvez définir AutomaticSerialization sur TRUE uniquement si toutes les fonctions de rappel que vous souhaitez synchroniser s’exécutent au même IRQL. Le choix d’un niveau d’exécution, qui est décrit ci-dessous, peut entraîner des niveaux IRQL incompatibles. Dans ce cas, le pilote doit utiliser des verrous d’infrastructure au lieu de définir AutomaticSerialization. Pour plus d’informations sur les structures de configuration pour les objets d’interruption, DPC, élément de travail et minuteur, et pour plus d’informations sur les restrictions qui s’appliquent à la définition de l’automaticSérialisation dans ces structures, consultez WDF_INTERRUPT_CONFIG, WDF_DPC_CONFIG, WDF_WORKITEM_CONFIG et WDF_TIMER_CONFIG.
Si vous définissez AutomaticSerialization surTRUE, vous devez sélectionner synchronisation au niveau de la file d’attente.
Choix d’un niveau d’exécution
Lorsqu’un pilote crée certains types d’objets framework, il peut spécifier un niveau d’exécution pour l’objet. Le niveau d’exécution spécifie l’IRQL auquel l’infrastructure appellera les fonctions de rappel d’événements de l’objet qui gèrent les demandes d’E/S d’un pilote.
Si un pilote fournit un niveau d’exécution, le niveau fourni affecte les fonctions de rappel pour les objets file d’attente et de fichier. En règle générale, si le pilote utilise la synchronisation automatique, l’infrastructure appelle ces fonctions de rappel à l’adresse IRQL = DISPATCH_LEVEL. En spécifiant un niveau d’exécution, le pilote peut forcer l’infrastructure à appeler ces fonctions de rappel à l’adresse IRQL = PASSIVE_LEVEL. L’infrastructure utilise les règles suivantes lors de la définition de l’IRQL auquel les fonctions de rappel de file d’attente et d’objet de fichier sont appelées :
Si un pilote utilise la synchronisation automatique, ses fonctions de rappel de file d’attente et d’objet de fichier sont appelées à IRQL = DISPATCH_LEVEL, sauf si le pilote demande à l’infrastructure d’appeler ses fonctions de rappel à l’adresse IRQL = PASSIVE_LEVEL.
Si un pilote n’utilise pas la synchronisation automatique et ne spécifie pas de niveau d’exécution, les fonctions de rappel d’objet file d’attente et de fichier du pilote peuvent être appelées à l’adresse IRQL <= DISPATCH_LEVEL.
Notez que si votre pilote fournit des fonctions de rappel d’objet de fichier, vous voudrez probablement que l’infrastructure appelle ces fonctions de rappel à l’adresse IRQL = PASSIVE_LEVEL, car certaines données de fichier, telles que le nom du fichier, peuvent être paginées.
Pour fournir un niveau d’exécution, votre pilote doit spécifier une valeur pour le membre ExecutionLevel de la structure WDF_OBJECT_ATTRIBUTES d’un objet. Les valeurs de niveau d’exécution que votre pilote peut spécifier sont les suivantes :
WdfExecutionLevelPassive
L’infrastructure appelle les fonctions de rappel de l’objet à IRQL = PASSIVE_LEVEL.
WdfExecutionLevelDispatch
L’infrastructure peut appeler les fonctions de rappel de l’objet à l’adresse IRQL <= DISPATCH_LEVEL. (Si le pilote utilise la synchronisation automatique, l’infrastructure appelle toujours les fonctions de rappel à IRQL = DISPATCH_LEVEL.)
WdfExecutionLevelInheritFromParent
L’infrastructure obtient la valeur ExecutionLevel de l’objet à partir du parent de l’objet.
Le niveau d’exécution par défaut pour les objets de pilote est WdfExecutionLevelDispatch. Le niveau d’exécution par défaut pour tous les autres objets est WdfExecutionLevelInheritFromParent.
Pour plus d’informations sur les valeurs de niveau d’exécution, consultez WDF_EXECUTION_LEVEL.
Le tableau suivant montre le niveau IRQL auquel l’infrastructure peut appeler les fonctions de rappel d’un pilote pour les objets de file d’attente et les objets de fichier.
Étendue de la synchronisation | Niveau d’exécution | IRQL des fonctions de rappel de file d’attente et de fichier |
---|---|---|
WdfSynchronizationScopeDevice |
WdfExecutionLevelPassive |
PASSIVE_LEVEL |
WdfSynchronizationScopeDevice |
WdfExecutionLevelDispatch |
DISPATCH_LEVEL |
WdfSynchronizationScopeQueue |
WdfExecutionLevelPassive |
PASSIVE_LEVEL |
WdfSynchronizationScopeQueue |
WdfExecutionLevelDispatch |
DISPATCH_LEVEL |
WdfSynchronizationScopeNone |
WdfExecutionLevelPassive |
PASSIVE_LEVEL |
WdfSynchronizationScopeNone |
WdfExecutionLevelDispatch |
<= DISPATCH_LEVEL |
Vous pouvez définir le niveau d’exécution sur WdfExecutionLevelPassive ou WdfExecutionLevelDispatch pour les objets pilote, périphérique, fichier, file d’attente, minuteur et général. Pour les autres objets, seul WdfExecutionLevelInheritFromParent est autorisé.
Vous devez spécifier WdfExecutionLevelPassive si :
Les fonctions de rappel de votre pilote doivent appeler des méthodes d’infrastructure ou des routines WDM (Windows Driver Model) qui ne peuvent être appelées qu’à l’adresse IRQL = PASSIVE_LEVEL.
Les fonctions de rappel de votre pilote doivent accéder au code ou aux données paginables. (Par exemple, les fonctions de rappel d’objet de fichier accèdent généralement aux données paginables.)
Au lieu de définir WdfExecutionLevelPassive, votre pilote peut définir WdfExecutionLevelDispatch et fournir une fonction de rappel qui crée des éléments de travail s’il doit gérer certaines opérations à IRQL = PASSIVE_LEVEL.
Avant de décider si votre pilote doit définir le niveau d’exécution d’un objet sur WdfExecutionLevelPassive, vous devez déterminer l’IRQL auquel votre pilote et d’autres pilotes de la pile de pilotes sont appelés. Considérez les situations suivantes :
Si votre pilote se trouve en haut de la pile de pilotes en mode noyau, le système appelle généralement le pilote à l’adresse IRQL = PASSIVE_LEVEL. Le client d’un tel pilote peut être un pilote UMDF ou une application en mode utilisateur. La spécification de WdfExecutionLevelPassive n’affecte pas les performances du pilote, car l’infrastructure n’a pas besoin de mettre en file d’attente les appels de votre pilote pour les éléments de travail appelés à l’adresse IRQL = PASSIVE_LEVEL.
Si votre pilote n’est pas en haut de la pile, le système n’appellera probablement pas votre pilote à l’adresse IRQL = PASSIVE_LEVEL. Par conséquent, l’infrastructure doit mettre en file d’attente les appels de votre pilote aux éléments de travail, qui sont appelés plus tard à IRQL = PASSIVE_LEVEL. Ce processus peut entraîner des performances de pilote médiocres, par rapport à l’appel des fonctions de rappel de votre pilote à l’adresse IRQL <= DISPATCH_LEVEL.
Pour les objets DPC et pour les objets minuteurs qui ne représentent pas des minuteurs de niveau passif, notez que vous ne pouvez pas définir le membre AutomaticSerialization de la structure de configuration sur TRUE si vous avez défini le niveau d’exécution de l’appareil parent sur WdfExecutionLevelPassive. En effet, l’infrastructure acquiert les verrous de synchronisation de rappel de l’objet d’appareil à IRQL = PASSIVE_LEVEL et les verrous ne peuvent donc pas être utilisés pour synchroniser les fonctions de rappel d’objet DPC ou de minuteur, qui doivent s’exécuter à IRQL = DISPATCH_LEVEL. Dans ce cas, votre pilote doit utiliser des verrous de rotation d’infrastructure dans toutes les fonctions de rappel d’objet d’appareil, DPC ou de minuteur qui doivent être synchronisées les unes avec les autres.
Notez également que pour les objets minuteurs qui représentent des minuteurs de niveau passif, vous pouvez définir le membre AutomaticSerialization de la structure de configuration sur TRUE uniquement si le niveau d’exécution de l’appareil parent est défini sur WdfExecutionLevelPassive.