Introduction
Un driver ne peut pas considérer qu'un pointeur provenant d'un processus utilisateur est toujours valide, accessible ou verrouillé en mémoire. Windows fournit donc plusieurs méthodes pour transporter les données entre User Mode et Kernel Mode.
Les trois modèles classiques sont : Buffered I/O, Direct I/O et Neither I/O.
1. Pourquoi Windows ne donne pas toujours directement le pointeur utilisateur
Un pointeur User Mode dépend du contexte du processus courant. La mémoire peut être paginée, libérée, remappée ou devenir invalide entre le moment où la requête est créée et celui où le driver l'utilise.
Le noyau doit donc garantir que le driver travaille avec une représentation sûre du buffer.
2. Buffered I/O
Le driver active cette méthode avec :
Code: Select all
DeviceObject->Flags |= DO_BUFFERED_IO;
Le driver accède aux données via :
Code: Select all
Irp->AssociatedIrp.SystemBuffer
Code: Select all
PUCHAR input = (PUCHAR)Irp->AssociatedIrp.SystemBuffer;
Avantages
- Simple à utiliser.
- Pas de MDL à manipuler directement.
- Pratique pour les petits buffers.
- Copie supplémentaire.
- Moins intéressant pour de gros transferts.
Le driver active le mode direct avec :
Code: Select all
DeviceObject->Flags |= DO_DIRECT_IO;
Le MDL est disponible dans :
Code: Select all
Irp->MdlAddress
4. Qu'est-ce qu'un MDL ?
Un MDL est une structure utilisée par Windows pour décrire une région mémoire potentiellement répartie sur plusieurs pages physiques.
Conceptuellement :
Code: Select all
Buffer virtuel
|
v
+---------+---------+---------+
| page A | page B | page C |
+---------+---------+---------+
| | |
v v v
pages physiques éventuellement non contiguës
- IoAllocateMdl
- IoFreeMdl
- MmProbeAndLockPages
- MmUnlockPages
- MmGetSystemAddressForMdlSafe
- MmGetMdlByteCount
- MmGetMdlByteOffset
- MmGetMdlVirtualAddress
Pour obtenir une adresse virtuelle utilisable depuis le noyau :
Code: Select all
PVOID buffer = MmGetSystemAddressForMdlSafe(
Irp->MdlAddress,
NormalPagePriority
);
Code: Select all
if (!buffer)
{
Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_INSUFFICIENT_RESOURCES;
}
Code: Select all
ULONG length = MmGetMdlByteCount(Irp->MdlAddress);
ULONG offset = MmGetMdlByteOffset(Irp->MdlAddress);
PVOID va = MmGetMdlVirtualAddress(Irp->MdlAddress);
7. Verrouiller soi-même des pages
Dans certains scénarios, le driver construit lui-même un MDL.
Code: Select all
PMDL mdl = IoAllocateMdl(
userBuffer,
length,
FALSE,
FALSE,
NULL
);
Code: Select all
__try
{
MmProbeAndLockPages(
mdl,
UserMode,
IoReadAccess
);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
IoFreeMdl(mdl);
return GetExceptionCode();
}
Code: Select all
MmUnlockPages(mdl);
IoFreeMdl(mdl);
Si le driver n'utilise ni DO_BUFFERED_IO ni DO_DIRECT_IO, l'I/O Manager peut laisser le driver travailler avec une adresse User Mode.
Cette méthode est plus dangereuse car l'adresse appartient au processus appelant.
Avant de lire :
Code: Select all
__try
{
ProbeForRead(userBuffer, length, 1);
// accès au buffer
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
status = GetExceptionCode();
}
Code: Select all
__try
{
ProbeForWrite(userBuffer, length, 1);
// écriture dans le buffer
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
status = GetExceptionCode();
}
ProbeForRead et ProbeForWrite ne rendent pas un pointeur utilisateur éternellement sûr. Le contexte, l'IRQL et la durée de validité du buffer doivent toujours être pris en compte.
9. Comparatif
Code: Select all
Buffered I/O
-> copie dans SystemBuffer
-> simple
-> adapté aux petits transferts
Direct I/O
-> MDL
-> pages verrouillées
-> adapté aux transferts plus importants
Neither I/O
-> pointeur utilisateur direct
-> très peu d'abstraction
-> plus risqué
Code: Select all
NTSTATUS DispatchRead(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
ULONG requested = stack->Parameters.Read.Length;
if (!Irp->MdlAddress)
{
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_INVALID_PARAMETER;
}
PUCHAR buffer = (PUCHAR)MmGetSystemAddressForMdlSafe(
Irp->MdlAddress,
NormalPagePriority
);
if (!buffer)
{
Irp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_INSUFFICIENT_RESOURCES;
}
ULONG mdlLength = MmGetMdlByteCount(Irp->MdlAddress);
ULONG length = min(requested, mdlLength);
RtlFillMemory(buffer, length, 0x41);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = length;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
Les MDL deviennent particulièrement importants avec le DMA. Le périphérique ne travaille pas avec l'adresse virtuelle User Mode : Windows utilise la description des pages pour construire les adresses utilisables par le périphérique.
Le chemin devient :
Code: Select all
Buffer utilisateur
|
v
MDL
|
v
pages physiques
|
v
DMA / scatter-gather
La compréhension des MDL est essentielle pour les drivers de stockage, réseau, audio, vidéo et plus généralement pour les drivers réalisant de gros transferts de données.
