对驱动程序采用 Direct I/O 方式进行数据读的测试
采用这种方式进行读数据时, I/O Manager 调用 MmProbeAndLockPages 将 ReadFile 参数提供的用户空间缓冲区对应的物理页面锁定为不可换出,然后将得到的MDL放在Irp->MdlAddress里,将IRP传递给相应驱动程序的DispatchRead。根据Walter Oney在书中的描述,此时I/O Manager的行为可以用下面的代码来描述:
KPROCESSOR_MODE mode; // <== either KernelMode or UserMode PMDL mdl = IoAllocateMdl(uva, length, FALSE, TRUE, Irp); MmProbeAndLockPages(mdl, mode, reading ? IoWriteAccess : IoReadAccess); <code to send and await IRP> MmUnlockPages(mdl); IoFreeMdl(mdl);
这里主要关注的地方是MmProbeAndLockPages有没有进行实际的虚拟地址的映射,即将物理页面映射到内核地址空间中。我们用下面的驱动代码来测试这一行为。
NTSTATUS DispatchRead(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) { PVOID pSysAddr; PMDL pMDL = pIrp->MdlAddress; DbgPrint("******************DispatchRead******************/n"); DbgPrint("Before MmGetSystemAddressForMdlSafe/n"); OutputMDL(pMDL); pSysAddr = MmGetSystemAddressForMdlSafe(pMDL,LowPagePriority); if(!pSysAddr) { DbgPrint("MmGetSystemAddressForMdlSafe failed./n"); return STATUS_SUCCESS; } DbgPrint("After MmGetSystemAddressForMdlSafe/n"); OutputMDL(pMDL); pIrp->IoStatus.Status = STATUS_SUCCESS; pIrp->IoStatus.Information = MmGetMdlByteCount(pMDL); IoCompleteRequest(pIrp,IO_NO_INCREMENT); return STATUS_SUCCESS; }
再写一个应用程序来发起一个读操作:
void TestMDLDriver() { HANDLE hDevice; BOOL bRet; DWORD dwRead; BYTE buf[10000] = {'S','Q','U','I'}; hDevice = CreateFile(_T("////.//MDLTest"),GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if(hDevice==INVALID_HANDLE_VALUE) { fprintf(stderr,"CreateFile error : %d/n",GetLastError()); return; } //issue a read request bRet = ReadFile(hDevice,buf,sizeof(buf),&dwRead,NULL); if(!bRet) { fprintf(stderr,"ReadFile error : %d/n",GetLastError()); return; } printf("Read bytes:%d/n",dwRead); // CloseHandle(hDevice); }
导致的内核输出如下:
00000009 4.27463436 ******************DispatchRead****************** 00000010 4.27464771 Before MmGetSystemAddressForMdlSafe 00000011 4.27465439 MDL_TEST: Size=40 00000012 4.27466011 MDL_TEST: MdlFlags=0x008a 00000013 4.27466583 MDL_TEST: Process=0x86ca7b58 00000014 4.27467155 MDL_TEST: MappedSystemVa=0x92b1f000 00000015 4.27467775 MDL_TEST: StartVa=0x001ad000 00000016 4.27468348 MDL_TEST: ByteCount=10000 00000017 4.27468824 MDL_TEST: ByteOffset=1148 00000018 4.27469397 MDL_TEST: p[0]=0x00064429 00000019 4.27469969 MDL_TEST: p[1]=0x000619fc 00000020 4.27470541 MDL_TEST: p[2]=0x000618ee 00000021 4.27471066 MDL_TEST: p[3]=0x00060749 00000022 4.27471685 MDL_TEST: p[4]=0x86abca24 00000023 4.27472448 After MmGetSystemAddressForMdlSafe 00000024 4.27472973 MDL_TEST: Size=40 00000025 4.27473545 MDL_TEST: MdlFlags=0x008b 00000026 4.27474070 MDL_TEST: Process=0x86ca7b58 00000027 4.27474689 MDL_TEST: MappedSystemVa=0xb01e747c 00000028 4.27475214 MDL_TEST: StartVa=0x001ad000 00000029 4.27475786 MDL_TEST: ByteCount=10000 00000030 4.27476311 MDL_TEST: ByteOffset=1148 00000031 4.27476835 MDL_TEST: p[0]=0x00064429 00000032 4.27477455 MDL_TEST: p[1]=0x000619fc 00000033 4.27477980 MDL_TEST: p[2]=0x000618ee 00000034 4.27478504 MDL_TEST: p[3]=0x00060749 00000035 4.27479029 MDL_TEST: p[4]=0x86abca24
此时从VS的调试器中看到,应用程序中buf[10000]的地址为0x001ad47c
从输出可以得到如下结论:
1.MmProbeAndLockPages并不将物理页面映射到内核地址空间,而仅锁定物理页面;MappedSystemVa的变化可以显示这一点
2.buf的地址=StartVa+ByteOffset;
3.MmGetSystemAddressForMdlSafe进行实际的映射操作,并设置MdlFlags的MDL_MAPPED_TO_SYSTEM_VA标志。
4.MmProbeAndLockPages将MdlFlags=MDL_WRITE_OPERATION | MDL_ALLOCATED_FIXED_SIZE | MDL_PAGES_LOCKED