Threads de travail 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 ces threads de travail système, qui sont des threads système qui traitent chaque élément de travail à 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 qui sont différents des paramètres utilisés par WorkItem .
Les routines WorkItem et WorkItemEx s’exécutent dans un contexte de thread système. Si une routine de répartition de pilote peut s’exécuter dans un contexte de thread en mode utilisateur, cette routine peut appeler une routine WorkItem ou WorkItemEx pour effectuer des opérations qui nécessitent un contexte de thread système.
Pour utiliser un élément de travail, un pilote effectue les étapes suivantes :
Allouer et initialiser 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.)
Associez une routine de rappel à l’élément de travail et mettez en file d’attente l’élément de travail afin qu’il soit traité par un thread de travail 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 une routine WorkItemEx à l’élément de travail et mettre en file d’attente l’élément de travail, le pilote doit appeler IoQueueWorkItemEx.
Une fois que l’élément de travail n’est plus requis, 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 non initialisé ou libéré que lorsque l’élément de travail n’est pas actuellement mis en file d’attente. Le système effectue une file d’attente de l’élément de travail avant d’appeler la routine de rappel de l’élément de travail, afin qu’IoFreeWorkItem et IoUninitializeWorkItem puissent être appelés à partir du rappel.
Un DPC qui doit lancer une tâche de traitement qui nécessite 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. Lorsqu’un 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 worker système peut attendre sur un objet de répartiteur.
Étant donné que le pool de threads de travail système est une ressource limitée, les routines WorkItem et WorkItemEx ne peuvent être utilisées que pour les opérations qui prennent une courte période de temps. 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 se 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 qui se trouve 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 l’élément de travail en file d’attente une deuxième fois s’il se trouve déjà dans la file d’attente :
- Le pilote conserve une liste de tâches pour la routine worker.
- Cette liste de tâches est disponible dans le contexte fourni à la routine worker. La routine de travail et toutes les routines de pilotes 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 worker. Ainsi, un thread de pilote peut mettre en file d’attente l’élément de travail en toute sécurité dès que le thread de travail commence à s’exécuter.