Un altro tipo di trasferimento dei dati, detto Direct I/O ed argomento di questa lezione, permette di avere gli stessi vantaggi del Buffered I/O evitando qualsiasi operazione di copia. Come nel caso precedente, si parte con un po' di teoria per analizzare e spiegare come funziona questo tipo di trasferimento sia per ReadFile\WriteFile che per DeviceIoControl.
Il client ha un buffer di input (richiesta in scrittura: WriteFile) o di output (richiesta in lettura: ReadFile) o entrambi (uno di input e uno di output oppure uno solo che sia di input e di output allo stesso tempo: DeviceIoControl).
Se il supporto dell'I/O Manager è attivo ed il tipo di trasferimento dati è Direct I/O, l'I/O Manager porta (se non già presente e con un page fault) il buffer del client in RAM e lo blocca rendendolo di fatto non-paged. Con questa mossa il problema dell'accesso alla memoria in contesti di esecuzione con IRQL $\ge 2$, dove è richiesto che la memoria sia non-paged, è risolto.
A questo punto l'I/O Manager crea una struttura dati chiamata MDL (Memory Descriptor List) che descrive in dettaglio come il buffer in user space è disposto in RAM: solitamente un blocco di memoria allocato appare contiguo nello spazio virtuale del processo ma non è detto che sia così in RAM (è molto probabile che nella memoria fisica questo stesso blocco sia frammentato in blocchi più piccoli non contigui). La struttura MDL serve proprio a descrivere come è disposto il buffer in RAM. Un puntatore a tale struttura è salvato nel campo MdlAddress della struttura IRP.
Con la descrizione del buffer in RAM (grazie alla struttura MDL) l'I/O Manager può creare un secondo mapping, questa volta in kernel space, del buffer che si trova in RAM (il primo mapping è quello che si trova in user space). Con questa mossa il problema dell'esecuzione nel contesto di un thread arbitrario è risolto dato che il driver accederà al buffer sempre tramite questo secondo mapping (condiviso da tutti i thread poiché in kernel space), rassicurato anche dal fatto che il buffer in RAM è bloccato. La chiamata che innesca tutta l'operazione è quella alla macro MmGetSystemAddressForMdlSafe a cui si passa l'indirizzo della struttura MDL come primo argomento e che restituisce un puntatore al mapping in kernel space. Per quanto riguarda DeviceIoControl, il supporto di tipo Direct I/O può essere attivato solo sul buffer di output: per il buffer di input sarà attivato automaticamente il supporto di tipo Buffered I/O.
A questo punto il driver può consumare il buffer di sistema (in lettura e/o scrittura, dipende dall'operazione) che, essendo solo un altro mapping al buffer bloccato in RAM, non necessita di essere copiato in user space per trasferire i dati.
Infine, quando l'operazione è completata, l'I/O Manager rimuove il secondo mapping creato in kernel space, libera la memoria della struttura MDL e sblocca il buffer in RAM. A questo punto il client può continuare a leggere e scrivere il suo buffer in user space. Fatta questa doverosa premessa teorica sul Buffered I/O, è arrivato il momento di analizzare quali sono le (poche) modifiche necessarie al codice delle lezioni precedenti.
ReadFile\WriteFile
Per attivare il Buffered I/O è necessario solo effettuare un OR tra il campo Flags dell'oggetto Device in DriverEntry ed il valore DO_DIRECT_IO
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); // ... DeviceObject->Flags |= DO_DIRECT_IO; // ... }
Nelle dispatch routine relative agli indici IRP_MJ_READ e IRP_MJ_WRITE le uniche due modifiche riguardano il recupero del buffer e la rimozione del blocco __try\__exception
NTSTATUS RWDriverRead(PDEVICE_OBJECT pDO, PIRP pIrp) { UNREFERENCED_PARAMETER(pDO); KeWaitForMutexObject(&mutex, Executive, KernelMode, FALSE, NULL); NTSTATUS status = STATUS_INVALID_USER_BUFFER; PIO_STACK_LOCATION stack; ULONG len = 0; __try { stack = IoGetCurrentIrpStackLocation(pIrp); len = stack->Parameters.Read.Length; if (len == 0) return CompleteIrp(pIrp, STATUS_INVALID_BUFFER_SIZE, 0); //PVOID buffer = pIrp->UserBuffer; PVOID buffer = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority); if (!buffer) return CompleteIrp(pIrp, STATUS_INSUFFICIENT_RESOURCES, 0); /*__try { RtlCopyMemory(buffer, pBuffer, len); status = STATUS_SUCCESS; } __except (EXCEPTION_EXECUTE_HANDLER) { status = STATUS_ACCESS_VIOLATION; }*/ RtlCopyMemory(buffer, pBuffer, len); status = STATUS_SUCCESS; } __finally { KeReleaseMutex(&mutex, FALSE); return CompleteIrp(pIrp, status, len); } }
NTSTATUS RWDriverWrite(PDEVICE_OBJECT pDO, PIRP pIrp) { UNREFERENCED_PARAMETER(pDO); KeWaitForMutexObject(&mutex, Executive, KernelMode, FALSE, NULL); NTSTATUS status = STATUS_INVALID_USER_BUFFER; PIO_STACK_LOCATION stack; ULONG len = 0; __try { stack = IoGetCurrentIrpStackLocation(pIrp); len = stack->Parameters.Write.Length; if (len == 0) return CompleteIrp(pIrp, STATUS_INVALID_BUFFER_SIZE, 0); //PVOID buffer = pIrp->UserBuffer; PVOID buffer = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority); if (!buffer) return CompleteIrp(pIrp, STATUS_INSUFFICIENT_RESOURCES, 0); /*__try { RtlCopyMemory(pBuffer, buffer, len); status = STATUS_SUCCESS; } __except (EXCEPTION_EXECUTE_HANDLER) { status = STATUS_ACCESS_VIOLATION; }*/ RtlCopyMemory(pBuffer, buffer, len); status = STATUS_SUCCESS; } __finally { KeReleaseMutex(&mutex, FALSE); return CompleteIrp(pIrp, status, len); } }
Il resto è esattamente uguale al codice della lezione precedente (si veda [2]) che non usava il supporto dell'I/O Manager. In particolare, il codice del client resta totalmente invariato.
DeviceIoControl
Per attivare il Direct I/O è necessario specificare il valore METHOD_OUT_DIRECT o METHOD_IN_DIRECT come terzo argomento della macro CTL_CODE. La differenza tra i due valori è che passando METHOD_IN_DIRECT si vuole che il buffer di output sia solo in lettura, trasformandolo di fatto in un secondo buffer di input (ecco perché c'è quel IN in mezzo). Passando METHOD_OUT_DIRECT, invece, si intende utilizzare i buffer nel modo classico (uno per l'input ed l'altro per l'output)
#define IOCTL_DICDRIVER_INC_VALUE CTL_CODE(DICDRIVER_DEVICE, 0x800, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
Nella dispatch routine relativa all'indice IRP_MJ_DEVICE_CONTROL le uniche due modifiche riguardano il recupero dei buffer di input e output e la rimozione del blocco __try\__exception. In particolare si noti che, diversamente dal caso precedente dove si era usato il supporto di tipo Buffered I/O (si veda [1]), si torna a lavora due buffer separati per l'input e l'output.
NTSTATUS DICDriverDeviceIoControl(PDEVICE_OBJECT pDO, PIRP Irp) { UNREFERENCED_PARAMETER(pDO); PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); NTSTATUS status = STATUS_SUCCESS; ULONG len = 0; switch (stack->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_DICDRIVER_INC_VALUE: { if (stack->Parameters.DeviceIoControl.InputBufferLength < sizeof(ULONG) || (len = stack->Parameters.DeviceIoControl.OutputBufferLength) < sizeof(ULONG)) { status = STATUS_BUFFER_TOO_SMALL; break; } //PULONG iBuffer = (PULONG)stack->Parameters.DeviceIoControl.Type3InputBuffer; PULONG iBuffer = (PULONG)Irp->AssociatedIrp.SystemBuffer; if (iBuffer == NULL) { status = STATUS_INVALID_PARAMETER; break; } //PULONG oBuffer = (PULONG)Irp->UserBuffer; PULONG oBuffer = (PULONG)MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority); if (oBuffer == NULL) { status = STATUS_INVALID_PARAMETER; break; } /*__try { if (*iBuffer < (ULONG)(-1)) { *oBuffer = ++(*iBuffer); break; } else status = STATUS_INVALID_USER_BUFFER; } __except (EXCEPTION_EXECUTE_HANDLER) { // something wrong with the buffer status = STATUS_ACCESS_VIOLATION; }*/ if (*iBuffer < (ULONG)(-1)) { ++(*iBuffer); break; } else status = STATUS_INVALID_USER_BUFFER; break; } default: { status = STATUS_INVALID_DEVICE_REQUEST; break; } } // Irp->IoStatus.Information ritornato da I/O Manager in lpBytesReturned fornito da client in DeviceIoControl return CompleteIrp(Irp, status, len); }
Il resto è esattamente uguale al codice della lezione precedente (si veda [3]) che non usava il supporto dell'I/O Manager. In particolare, il codice del client resta totalmente invariato.
Codice sorgente:
RWDIODriver.zip
DICDIODriver.zip
Riferimenti:
[1] 08 - Supporto all'accesso in memoria: Buffered I/O
[2] 06 - Comunicazione Client \ Driver: ReadFile e WriteFile
[3] 07 - Comunicazione Client / Driver: DeviceIoControl
[4] Windows Kernel Programming - Pavel Yosifovich
Nessun commento:
Posta un commento