Partager via


Threads de travailleur de système

Un pilote qui nécessite un traitement différé peut utiliser un élément de travail, qui contient un pointeur vers une routine de rappel de pilote qui effectue le traitement réel. Le pilote met en file d’attente l’élément de travail et un thread de travail système supprime l’élément de travail de la file d’attente et exécute la routine de rappel du pilote. Le système gère un pool de threads de travail du système, qui sont des threads traitant chaque élément de travail un à la fois.

Le pilote associe une routine de rappel WorkItem à l’élément de travail. Lorsque le thread de travail système traite l’élément de travail, il appelle la routine WorkItem associée. Dans Windows Vista et les versions ultérieures de Windows, un pilote peut à la place associer une routine WorkItemEx à un élément de travail. WorkItemEx accepte des paramètres différents des paramètres que WorkItem prend.

Les routines WorkItem et WorkItemEx s’exécutent dans un contexte de thread système. Si une routine de distribution de pilotes peut s’exécuter dans un contexte de thread en mode utilisateur, cette routine peut appeler une routine WorkItem ou WorkItemEx pour effectuer toutes les opérations nécessitant un contexte de thread système.

Pour utiliser un élément de travail, un pilote effectue les étapes suivantes :

  1. Allouez et initialisez un nouvel élément de travail.

    Le système utilise une structure IO_WORKITEM pour contenir un élément de travail. Pour allouer une nouvelle structure IO_WORKITEM et l’initialiser en tant qu’élément de travail, le pilote peut appeler IoAllocateWorkItem. Dans Windows Vista et les versions ultérieures de Windows, le pilote peut également allouer sa propre structure IO_WORKITEM et appeler IoInitializeWorkItem pour initialiser la structure en tant qu’élément de travail. (Le pilote doit appeler IoSizeofWorkItem pour déterminer le nombre d’octets nécessaires pour contenir un élément de travail.)

  2. Associez une fonction de rappel à l’élément de travail et mettez l’élément de travail en file d’attente afin qu’il soit traité par un thread de travail du système.

    Pour associer une routine WorkItem à l’élément de travail et mettre en file d’attente l’élément de travail, le pilote doit appeler IoQueueWorkItem. Pour associer plutôt une routine WorkItemEx à l’élément de travail et mettre en file d’attente l’élément de travail, le pilote doit appeler IoQueueWorkItemEx.

  3. Une fois que l’élément de travail n’est plus nécessaire, libérez-le.

    Un élément de travail qui a été alloué par IoAllocateWorkItem doit être libéré par IoFreeWorkItem. Un élément de travail initialisé par IoInitializeWorkItem doit être non initialisé par IoUninitializeWorkItem avant de pouvoir être libéré.

    L'élément de travail ne peut être désinitialisé ou libéré que lorsque l'élément de travail n'est pas actuellement en file d'attente. Le système défile l’élément de travail avant d’appeler la routine de rappel de l’élément de travail, donc IoFreeWorkItem et IoUninitializeWorkItem peuvent être appelés à partir du rappel.

Un DPC qui doit lancer une tâche de traitement nécessitant un traitement long ou qui effectue un appel bloquant doit déléguer le traitement de cette tâche à un ou plusieurs éléments de travail. Pendant qu’une DPC s’exécute, tous les threads ne peuvent pas s’exécuter. En outre, un DPC, qui s’exécute à IRQL = DISPATCH_LEVEL, ne doit pas effectuer d’appels bloquants. Toutefois, le thread de travail système qui traite un élément de travail s’exécute à IRQL = PASSIVE_LEVEL. Ainsi, l’élément de travail peut contenir des appels bloquants. Par exemple, un thread de travail système peut attendre un objet répartiteur.

Étant donné que le pool de threads de travail système est une ressource limitée, Les routines WorkItem et WorkItemEx peuvent être utilisées uniquement pour les opérations qui prennent un court délai. Si l’une de ces routines s’exécute trop longtemps (s’il contient une boucle indéfinie, par exemple) ou attend trop longtemps, le système peut bloquer. Par conséquent, si un pilote nécessite de longues périodes de traitement différé, il doit plutôt appeler PsCreateSystemThread pour créer son propre thread système.

N’appelez pas IoQueueWorkItem ou IoQueueWorkItemEx pour mettre en file d’attente un élément de travail déjà dans la file d’attente. Cela peut entraîner une altération des structures de données système. Si votre pilote met en file d’attente le même élément de travail chaque fois qu’une routine de pilote particulière s’exécute, vous pouvez utiliser la technique suivante pour éviter de mettre en file d’attente l’élément de travail une deuxième fois s’il se trouve déjà dans la file d’attente :

  • Le pilote gère une liste de tâches pour la routine du travailleur.
  • Cette liste de tâches est disponible dans le contexte fourni au processus de travail. La routine worker et toutes les routines de pilote qui modifient la liste des tâches synchronisent leur accès à la liste.
  • Chaque fois que la routine de travail s’exécute, elle effectue toutes les tâches de la liste et supprime chaque tâche de la liste à mesure que la tâche est terminée.
  • Lorsqu’une nouvelle tâche arrive, le pilote ajoute cette tâche à la liste. Le pilote met en file d’attente l’élément de travail uniquement si la liste des tâches était précédemment vide.

Le thread de travail système supprime l’élément de travail de la file d’attente avant d’appeler le thread de travail. Ainsi, un thread de pilote peut mettre à nouveau en file d’attente l’élément de travail dès que le thread de travail commence à s’exécuter.