驅動開發之 設備讀寫方式:直接方式
上一節介紹了緩沖區方式讀寫,這一節咱們來看看直接方式讀寫設備。
1.
直接方式讀寫設備,操作系統會將用戶模式下的緩沖區鎖住,然后操作系統將這段緩沖區在內核模式地址再次映射一遍。這樣,用戶模式的緩沖區和內核模式的緩沖區指向的是同一區域的物理內存。無論操作系統如何切換進程,內核模式地址都保持不變。
創建好設備IoCreateDevice后,需要設置DO_DIRECT_IO, pDevObj->Flags |= DO_DIRECT_IO.
2.
這里涉及到內存描述符表(MDL)
MDL結構的聲明如下:
typedef struct _MDL {
struct _MDL *Next;
CSHORT Size;
CSHORT MdlFlags;
struct _EPROCESS *Process;
PVOID MappedSystemVa;
PVOID StartVa; //給出了用戶緩沖區的虛擬地址,第一個頁地址,這個地址僅在擁有數據緩沖區的用戶模式進程上下文中才有效
ULONG ByteCount; //是緩沖區的字節長度
ULONG ByteOffset; //是緩沖區起始位置在一個頁幀中的偏移值,那么緩沖區的首地址是mdl->StartVa+mdl->ByteOffset
} MDL, *PMDL;
用圖表示內存描述符表(MDL)結構為:
由圖可知用戶模式的這段緩沖區在虛擬內存上是連續的,但在物理內存上可能是離散的。
3.下面來看一些MDL相關的函數
IoAllocateMdl | 創建MDL |
IoBuildPartialMdl | 創建一個已存在MDL的子MDL |
IoFreeMdl | 銷毀MDL |
MmBuildMdlForNonPagedPool | 修改MDL以描述內核模式中一個非分頁內存區域 |
MmGetMdlByteCount | 取緩沖區字節大小(得到mdl->ByteCount) |
MmGetMdlByteOffset | 取緩沖區在第一個內存頁中的偏移(得到mdl->ByteOffset) |
MmGetMdlVirtualAddress | 取虛擬地址((PVOID)(PCHAR)(mdl->StartVa+mdl->ByteOffset)) |
MmGetSystemAddressForMdl | 創建映射到同一內存位置的內核模式虛擬地址 |
MmGetSystemAddressForMdlSafe | 與MmGetSystemAddressForMdl相同,但Windows 2000首選 |
MmInitializeMdl | (再)初始化MDL以描述一個給定的虛擬緩沖區 |
MmPrepareMdlForReuse | 再初始化MDL |
MmProbeAndLockPages | 地址有效性校驗后鎖定內存頁 |
MmSizeOfMdl | 取為描述一個給定的虛擬緩沖區的MDL所占用的內存大小 |
MmUnlockPages | 為該MDL解鎖內存頁 |
4.下面以readfile為例介紹直接方式讀取設備
用戶模式調用readfile:
UCHAR OutputBuffer[10];
DWORD RetLen = 0;
readfile(hDevice,OutputBuffer,sizeof(OutputBuffer),&RetLen,NULL);
內核模式得到要讀取的字節數:(與以緩沖區讀寫方式一樣)
//得到當前堆棧
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
//得到readfile要讀取的字節數
ULONG cbread = stack->Parameters..Read.Length;
另外,通過IRP的pIrp->MdlAddress得到MDL數據結構,這個結構描述了被鎖定的緩沖區的內存。
下面是一個IRP_MJ_READ的派遣函數,僅供參考。
NTSTATUS DispathRead(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp) { KdPrint(("Enter DispathRead\n")); PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension; NTSTATUS status = STATUS_SUCCESS; PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp); ULONG ulReadLength = stack->Parameters.Read.Length;//得到讀取的長度 KdPrint(("ulReadLength:%d\n",ulReadLength)); ULONG mdl_length = MmGetMdlByteCount(pIrp->MdlAddress); //mdl虛擬內存的長度 PVOID mdl_address = MmGetMdlVirtualAddress(pIrp->MdlAddress); //虛擬內存的起始地址 ULONG mdl_offset = MmGetMdlByteOffset(pIrp->MdlAddress); //虛擬內存首地址在第一頁的偏移量 KdPrint(("mdl_address:0X%08X\n",mdl_address)); KdPrint(("mdl_length:%d\n",mdl_length)); KdPrint(("mdl_offset:%d\n",mdl_offset)); if (mdl_length!=ulReadLength) { //MDL的長度應該和讀長度相等,否則該操作應該設為不成功 pIrp->IoStatus.Information = 0; status = STATUS_UNSUCCESSFUL; }else { //用MmGetSystemAddressForMdlSafe得到MDL在內核模式下的映射,被映射到內核模式下的內存地址,必定在0X80000000-0XFFFFFFFF之間 PVOID kernel_address = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,NormalPagePriority); KdPrint(("kernel_address:0X%08X\n",kernel_address)); memset(kernel_address,0XAA,ulReadLength); //對內核模式下的內存地址進行操作 pIrp->IoStatus.Information = ulReadLength; //設置實際操作字節數 } pIrp->IoStatus.Status = status; IoCompleteRequest( pIrp, IO_NO_INCREMENT ); KdPrint(("Leave DispatchRead\n")); return status; }