在驱动和应用程序间共享内存
在不同的场合,很多驱动编写人员需要在驱动和用户程序间共享内存。两种最容易的技术是:l 应用程序发送IOCTL给驱动程序,提供一个指向内存的指针,之后驱动程序和应用程序就可以共享内存。(应用程序分配共享内存)l 由驱动程序分配内存页,并映射这些内存页到指定用户模式进程的地址空间,并且将地址返回给应用程序。(驱动程序分配共享内存)使用IOCTL共享Buffer:使用一个IOCT描述的Buffer,在驱动和用户程序间共享内存是内存共享最简单的实现形式。毕竟,IOCTL也是驱动支持其他I/O请求最经典的方法。应用程序调用Win32函数DeviceIoControl(),要被共享的Buffer的基地址和长度被放入OutBuffer参数中。对于使用这种Buffer共享方式的驱动编写者需要确定的事情就是对于特定的IOCTL采取哪种Buffer method。既可以使用METHOD_XXX_DIRECT,也可以使用METHOD_NEITHER。(PS:在METHOD_XXX_DIRECT模式下,IO管理器为应用层指定的输出缓冲区(OutputBuffer)创建一个MDL锁住该应用层的缓冲区内存,然后,我们可以在内核层中使用MmGetSystemAddressForMdlSafe获得应用层输出缓冲区所对应的内核层地址。MDL地址被放在了Irp->MdlAddress中)。如果采用METHOD_XXX_DIRECT方式,那用户Buffer将被检查是否正确存取,检查过后用户Buffer将被锁进内存。驱动需要调用MmGetSystemAddressForMdlSafe将前述Buffer映射到内核地址空间。这种方式的一个优点就是驱动可以在任意进程上下文、任意IRQL优先级别上存取共享内存Buffer。如果只需要将数据传给驱动则使用METHOD_IN_DIRECT方式。如果从驱动返回数据给应用程序或者做双向数据交换则使用METHOD_OUT_BUFFER。(PS:这些检查都将由IO管理器来负责,并且,此时IO管理器将为用户层的缓冲区创建MDL。因为此时还未到设备驱动层,当前上下文还属于当前发起 DeviceIo调用的进程,用户模式缓冲区的内存有效。但是,经过IO管理器发送IRP到下层驱动时,就不能保证当前上下文,幸而有IO管理器为我们创建MDL。这样,我们就可以在内核层获得对应的内核地址,并且自由写入数据。)使用METHOD_NEITHER方式描述一个共享内存Buffer存在许多固有的限制和需要小心的地方。(基本上,在任何时候一个驱动使用这种方式都是一样的)。其中最主要的规则是驱动只能在发起请求进程的上下文中存取Buffer。这是因为要通过Buffer的用户虚拟地址存取共享内存Buffer。这也就意味着驱动必须要在设备栈的顶端,被用户应用程序经由IO Manager直接调用。期间不能存在中间层驱动或者文件系统驱动在我们的驱动之上。在实际情况下,WDM驱动将严格限制在其Dispatch例程中存储用户Buffer。而KMDF驱动则需要在EvtIoInCallerContext事件回调函数中使用。另外一个重要的固有限制就是使用METHOD_NEITHER方式的驱动要存取用户Buffer必须在PASSIVE_LEVEL的IRQL级别。这是因为 IO Manager没有把Buffer锁在内存中,因此驱动程序想要存取共享Buffer时,内存可能被换出去了。如果驱动不能满足这个要求,就需要驱动创建一个mdl,然后将其共享Buffer锁进到内存中。(PS: METHOD_NEITHER不建议使用,还是使用直接IO好。)另外,考虑到传输类型的选择,对于这种方式可能的非直接明显的限制是对于共享的内存必须被用户模式应用程序分配。如果考虑到配额限制,能够被分配的内存数量是有限的。另外,用户应用程序不能分配物理连续的内存和Non-cache内存。当然,如果驱动和用户模式应用所有要做的就是使用合理大小的数据 Buffer将数据传入和传出,这个技术可能是最简单和实用的。和它的简易一样,使用IOCTL在驱动和用户模式应用之间共享内存的也是最常被误解的方案。一个使用这种方案的新Windows驱动开发者常犯的错误就是当驱动已经查询到了Buffer的地址后就通知结束IOCTL。这是一个非常坏的事情。为什么?如果应用程序突然退出了,比如有一个意味,会发生什么情况。另外一个问题就是当使用METHOD_XXX_DIRECT,如果带有MDL的IRP被完成,Buffer将不再被映射到系统内核地址空间,一次试图对以前有效的内核虚拟地址空间的存取(MmGetSystemAddressForMdlSafe获取)将使系统崩溃。这通常要避免。一个针对这个问题的方案是应用程序使用FILE_FLAG_OVERLAPPED打开设备并且考虑IOCTL使用一个OVERLAPPED结构。一个驱动可以针对IRP设置cancel例程(使用IoSetCancelRoutine),将IRP标记为挂起(使用IoMakeIrpPending),并且返回给调用者STATUS_PENGDING前将IRP放进内部队列。当然,KMDF驱动对这类问题可以放心,只需要将请求设置为进行中并且可取消,就像 WDFQUEUE。(PS: 要小心使用MDL,防止应用层程序意外退出而造成MDL所描述的虚拟内存无效。)使用这种方法有两个优点:1、当应用程序从IOCTL调用中得到ERROR_IO_PENDING的返回结果时,知道Buffer被映射了。并且知道什么时候IOCTL最终完成并将Buffer取消映射。2、通过取消例程(WDM)或者一个EvtIoCancelOnQueue事件处理回调例程,驱动程序成功在应用程序退出或者取消IO命令时得到通知,所以它可以执行必要的操作来完成IOCTL。因而有MDL位置用于内存取消映射操作。分配并且映射页:现在剩下了前面提到的第二种方法:分配内存页并且映射这些页到特定进程的用户虚拟地址空间上。使用大多数Windows驱动编写者常见的API,这个方法令人惊讶的容易,同时也允许驱动对分配内存的类型具有最大的控制能力。驱动无论使用什么标准方法,都是希望分配内存来共享。例如,如果驱动需要一个适当的设备(逻辑)地址作DMA,就像内存块的内核虚拟地址,它能够使用 AllocateCommonBuffer来分配内存。如果没有要求特定的内存特性,要被共享的内存大小也是适度的,驱动可以将0填充、非分页物理内存页分配给Buffer。从主内存分配0填充、非分页的页面,使用MmAllocatePagesForMDL或者MmAllocatePagesForMdlEx。这些函数返回一个MDL描述内存的分配。驱动使用函数MmGetSystemAddressForMdlSafe映射MDL描述的页到内核虚拟地址空间。从主内存分配页比使用分页内存池或者非分页内存池得到的内存更加安全,后者不是一个好主意。PS:这种方式是内核来分配内存空间,但是是使用MmAllocatePagesForMDL从主内存池中分配,返回得到一个MDL,对于驱动如何使用该共享内存,采用MmGetSystemAddressForMdlSafe得到其内核地址。对于应用层使用该共享内存,采用 MmMapLockedPagesSpecifyCache映射到应用层进程地址空间中,返回用户层地址空间的起始地址,将其放在IOCTL中返回给用户应用程序。借助一个用来描述共享内存的MDL,驱动现在准备映射这些页到用户进程地址空间。这可以使用函数MmMapLockedPagesSpecifyCache来实现。你需要知道调用这个函数的窍门是:你必须在你希望映射Buffer的进程上下文中调用这个函数。PS:如果是在别的进程上下文中调用,就变成了映射到其他进程上下文中了,但是我如何保证在我希望映射Buffer的进程上下文调用呢?设定AccessMode参数为UserMode。对MmMapLockedPagesSpecifyCache函数调用返回值是MDL描述内存页映射的用户虚拟地址空间地址。驱动可以将其放在对应IOCTL的缓存中给用户应用程序 。你需要有一个方法,在不需要时将分配的内存清除掉。换句话说,你需要调用MmFreePageFromMdl来释放内存页。并且调用IoFreeMdl来释放由MmAllocatePageForMdl(Ex)创建的MDL。你几乎都是在你驱动的IRP_MJ_CLEANUP处理例程(WDM)或者 EvtFileCleanup事件处理回调(KMDF中作这个工作)。这是所要做的,综合起来,完成这个过程的代码见下面。PVOID CreateAndMapMemory(OUT PMDL* PMemMdl,OUT PVOID* UserVa){PMDL Mdl;PVOID UserVAToReturn;PHYSICAL_ADDRESS LowAddress;PHYSICAL_ADDRESS HighAddress;SIZE_T TotalBytes;// 初始化MmAllocatePagesForMdl需要的Physical AddressLowAddress.QuadPart = 0;MAX_MEM(HighAddress.QuardPart);TotalBytes.QuadPart = PAGE_SIZE;// 分配4K的共享缓冲区Mdl = MmAllocatePagesForMdl(LowAddress,HighAddress,LowAddress,TotalBytes);if(!Mdl){Return STATUS_INSUFFICIENT_RESOURCES;}// 映射共享缓冲区到用户地址空间UserVAToReturn = MmMapLockedPagesSpecifyCache(Mdl,UserMode,MmCached,NULL,FALSE,NormalPagePriority);if(!UserVAToReturn)补充:综合编程 , 其他综合 ,
上一个:关于pthread里面一些函数的使用心得!
下一个:文件监控(教学版)