Introduction
Une ISR doit rester très courte. Pourtant, après une interruption, le driver doit souvent effectuer davantage de travail : terminer un IRP, lancer l'opération suivante, traiter les données reçues, mettre à jour des structures internes, etc.
Windows fournit pour cela les Deferred Procedure Calls, ou DPC.
1. Principe
Code: Select all
matériel
|
v
interruption
|
v
ISR à DIRQL
|
v
mise en file d'un DPC
|
v
DPC à DISPATCH_LEVEL
|
v
traitement différé
2. DPC associé à un DEVICE_OBJECT
WDM fournit un DPC intégré au DEVICE_OBJECT.
Initialisation :
Code: Select all
IoInitializeDpcRequest(
DeviceObject,
DpcRoutine
);
Code: Select all
IoRequestDpc(
DeviceObject,
CurrentIrp,
Context
);
- IoInitializeDpcRequest
- IoRequestDpc
- KeInitializeDpc
- KeInsertQueueDpc
- KeRemoveQueueDpc
- KeSetImportanceDpc
- KeSetTargetProcessorDpc
Code: Select all
VOID DpcRoutine(
PKDPC Dpc,
PDEVICE_OBJECT DeviceObject,
PIRP Irp,
PVOID Context
);
4. IRQL d'un DPC
Un DPC s'exécute à DISPATCH_LEVEL.
Cela impose plusieurs contraintes :
- Pas de mémoire pageable.
- Pas d'attente bloquante sur des objets nécessitant PASSIVE_LEVEL.
- Temps d'exécution court.
- Attention aux spin locks et à la concurrence multi-CPU.
5. Terminer un IRP depuis un DPC
Un schéma classique :
Code: Select all
VOID DpcRoutine(
PKDPC Dpc,
PDEVICE_OBJECT DeviceObject,
PIRP Irp,
PVOID Context
)
{
UNREFERENCED_PARAMETER(Dpc);
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Context);
if (Irp)
{
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 128;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
}
}
Un objet DPC donné ne peut pas être présent plusieurs fois simultanément dans la file DPC.
Si plusieurs interruptions arrivent avant son exécution, le driver doit gérer la possibilité que plusieurs événements aient été accumulés.
Exemple :
Code: Select all
volatile LONG PendingEvents;
7. DPC custom avec KDPC
Le driver peut créer son propre objet KDPC.
Code: Select all
KDPC MyDpc;
KeInitializeDpc(
&MyDpc,
MyDpcRoutine,
context
);
Code: Select all
KeInsertQueueDpc(
&MyDpc,
arg1,
arg2
);
Code: Select all
KeRemoveQueueDpc(&MyDpc);
Windows permet de modifier l'importance d'un DPC :
Code: Select all
KeSetImportanceDpc(
&MyDpc,
HighImportance
);
9. Affinité CPU
Un DPC peut être ciblé vers un processeur particulier.
Historiquement :
Code: Select all
KeSetTargetProcessorDpc(...)
10. Synchronisation ISR / DPC
L'ISR et le DPC peuvent accéder aux mêmes données.
Il faut éviter :
Code: Select all
ISR écrit structure X
DPC lit structure X
au même moment sur un autre CPU
- Interlocked* pour des compteurs simples.
- Spin locks adaptés à l'IRQL.
- KeSynchronizeExecution pour les données partagées avec l'ISR.
- Organisation lock-free soigneuse pour certains chemins très rapides.
Code: Select all
BOOLEAN IsrRoutine(PKINTERRUPT Interrupt, PVOID Context)
{
UNREFERENCED_PARAMETER(Interrupt);
PDEVICE_EXTENSION devExt =
(PDEVICE_EXTENSION)Context;
ULONG status = READ_REGISTER_ULONG(
devExt->StatusRegister
);
if (!(status & INT_PENDING))
return FALSE;
WRITE_REGISTER_ULONG(
devExt->StatusRegister,
INT_ACK
);
IoRequestDpc(
devExt->DeviceObject,
devExt->CurrentIrp,
NULL
);
return TRUE;
}
VOID DpcRoutine(
PKDPC Dpc,
PDEVICE_OBJECT DeviceObject,
PIRP Irp,
PVOID Context
)
{
UNREFERENCED_PARAMETER(Dpc);
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Context);
if (!Irp)
return;
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 512;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
}
Si le traitement doit :
- Attendre.
- Accéder à du code pageable.
- Exécuter une opération nécessitant PASSIVE_LEVEL.
- Effectuer un traitement long.
Conceptuellement :
Code: Select all
ISR
|
v
DPC
|
v
Work Item / thread
Le DPC constitue le pont entre l'ISR très courte et le traitement de fond du driver.
Code: Select all
ISR = reconnaître et acquitter
DPC = traiter rapidement la conséquence
Work Item = traitement pouvant attendre / PASSIVE_LEVEL
