驱动程序访问用户模式数据缓冲区的三种方式
来源:互联网 发布:华为定时开关机软件 编辑:程序博客网 时间:2024/06/15 04:16
当应用程序发起一个读或写操作时,通过给出一个用户模式虚拟地址和长度,应用程序向I/O管理器提供了一个数据缓冲区。正如我在第三章中提到的,内核模式驱动程序几乎从不使用用户模式虚拟地址访问内存,因为你不能把线程上下文确定下来。Windows 2000为驱动程序访问用户模式数据缓冲区提供了三种方法:
- 在buffered方式中,I/O管理器先创建一个与用户模式数据缓冲区大小相等的系统缓冲区。而你的驱动程序将使用这个系统缓冲区工作。I/O管理器负责在系统缓冲区和用户模式缓冲区之间复制数据。
- 在direct方式中,I/O管理器锁定了包含用户模式缓冲区的物理内存页,并创建一个称为MDL(内存描述符表)的辅助数据结构来描述锁定页。因此你的驱动程序将使用MDL工作。
- 在neither方式中,I/O管理器仅简单地把用户模式的虚拟地址传递给你。而使用用户模式地址的驱动程序应十分小心。
指定缓冲方式
为了指定设备读写的缓冲方式,你应该在AddDevice函数中,在创建设备对象后,立即设置其中的标志位:
NTSTATUS AddDevice(...){ PDEVICE_OBJECT fdo; IoCreateDevice(..., &fdo); fdo->Flags |= DO_BUFFERED_IO; <or> fdo->Flags |= DO_DIRECT_IO; <or> fdo->Flags |= 0; // i.e., neither direct nor buffered}
这之后你不能该变缓冲方式的设置,因为过滤器驱动程序将复制这个标志设置,并且,如果你改变了设置,过滤器驱动程序没有办法知道这个改变。
Buffered方式
当I/O管理器创建IRP_MJ_READ或IRP_MJ_WRITE请求时,它探测设备的缓冲标志以决定如何描述新IRP中的数据缓冲区。如果DO_BUFFERED_IO标志设置,I/O管理器将分配与用户缓冲区大小相同的非分页内存。它把缓冲区的地址和长度保存到两个十分不同的地方,见下面代码片段中用粗体字表示的语句。你可以假定I/O管理器执行下面代码(注意这并不是Windows NT的源代码):
PVOID uva; // user-mode virtual buffer addressULONG length; // length of user-mode bufferPVOID sva = ExAllocatePoolWithQuota(NonPagedPoolCacheAligned, length);if (writing) RtlCopyMemory(sva, uva, length);Irp->AssociatedIrp.SystemBuffer = sva; PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(Irp);if (reading) stack->Parameters.Read.Length = length; else stack->Parameters.Write.Length = length; <code to send and await IRP>if (reading) RtlCopyMemory(uva, sva, length);ExFreePool(sva);
可以看出,系统缓冲区地址被放在IRP的AssociatedIrp.SystemBuffer域中,而数据的长度被放到stack->Parameters联合中。在这个过程中还包含作为驱动程序开发者不必了解的额外细节。例如,读操作之后的数据复制工作实际发生一个APC期间,在原始线程的上下文中,由一个与构造该IRP完全不同的子例程执行。I/O管理器把用户模式虚拟地址(uva变量)保存到IRP的UserBuffer域中,这样一来复制操作就可以找到这个地址。但你不要使代码依赖这些事实,因为它们有可能会改变。IRP最终完成后,I/O管理器将释放系统缓冲区所占用的内存。
Direct方式
如果你在设备对象中指定DO_DIRECT_IO方式,I/O管理器将创建一个MDL用来描述包含该用户模式数据缓冲区的锁定内存页。MDL结构的声明如下:
typedef struct _MDL { struct _MDL *Next; CSHORT Size; CSHORT MdlFlags; struct _EPROCESS *Process; PVOID MappedSystemVa; PVOID StartVa; ULONG ByteCount; ULONG ByteOffset;} MDL, *PMDL;
图7-3显示了MDL扮演的角色。StartVa成员给出了用户缓冲区的虚拟地址,这个地址仅在拥有数据缓冲区的用户模式进程上下文中才有效。ByteOffset是缓冲区起始位置在一个页帧中的偏移值,ByteCount是缓冲区的字节长度。Pages数组没有被正式地声明为MDL结构的一部分,在内存中它跟在MDL的后面,包含用户模式虚拟地址映射为物理页帧的个数。
图7-3. 内存描述符表(MDL)结构
顺便说一下,我们不可以直接访问MDL的任何成员。应该使用宏或访问函数,见表7-2。
表7-2. 用于访问MDL的宏和访问函数
对于I/O管理器执行的Direct方式的读写操作,其过程可以想象为下面代码:
KPROCESSOR_MODE mode; // either KernelMode or UserModePMDL mdl = IoAllocateMdl(uva, length, FALSE, TRUE, Irp);MmProbeAndLockPages(mdl, mode, reading ? IoWriteAccess : IoReadAccess); <code to send and await IRP>MmUnlockPages(mdl);ExFreePool(mdl);
I/O管理器首先创建一个描述用户缓冲区的MDL。IoAllocateMdl的第三个参数(FALSE)指出这是一个主数据缓冲区。第四个参数(TRUE)指出内存管理器应把该内存充入进程配额。最后一个参数(Irp)指定该MDL应附着的IRP。在内部,IoAllocateMdl把Irp->MdlAddress设置为新创建MDL的地址,以后你将用到这个成员,并且I/O管理器最后也使用该成员来清除MDL。
这段代码的关键地方是调用MmProbeAndLockPages(以粗体字显示)。该函数校验那个数据缓冲区是否有效,是否可以按适当模式访问。如果我们向设备写数据,我们必须能读缓冲区。如果我们从设备读数据,我们必须能写缓冲区。另外,该函数锁定了包含数据缓冲区的物理内存页,并在MDL的后面填写了页号数组。在效果上,一个锁定的内存页将成为非分页内存池的一部分,直到所有对该页内存加锁的调用者都对其解了锁。
在Direct方式的读写操作中,对MDL你最可能做的事是把它作为参数传递给其它函数。例如,DMA传输的MapTransfer步骤需要一个MDL。另外,在内部,USB读写操作总使用MDL。所以你应该把读写操作设置为DO_DIRECT_IO方式,并把结果MDL传递给USB总线驱动程序。
顺便提一下,I/O管理器确实在stack->Parameters联合中保存了读写请求的长度,但驱动程序应该直接从MDL中获得请求数据的长度。
ULONG length = MmGetMdlByteCount(mdl);
Neither方式
如果你在设备对象中同时忽略了DO_DIRECT_IO和DO_BUFFERED_IO标志设置,你将得到默认的neither方式。对于这种方式,I/O管理器将简单地把用户模式虚拟地址和字节计数(以粗体显示的代码)交给你,其余的工作由你去做。
Irp->UserBuffer = uva; PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(Irp);if (reading) stack->Parameters.Read.Length = length; else stack->Parameters.Write.Length = length; <code to send and await IRP>
- 驱动程序访问用户模式数据缓冲区的三种方式
- 授权用户访问数据字典三种方式
- 注册Jdbc驱动程序的三种方式
- 注册jdbc驱动程序的三种方式
- 注册Jdbc驱动程序的三种方式
- 注册jdbc驱动程序的三种方式
- 注册jdbc驱动程序的三种方式
- 注册jdbc驱动程序的三种方式
- 注册jdbc驱动程序的三种方式
- 注册jdbc驱动程序的三种方式
- 注册jdbc驱动程序的三种方式
- 注册jdbc驱动程序的三种方式
- 注册jdbc驱动程序的三种方式
- 注册jdbc驱动程序的三种方式
- JDBC注册驱动程序的三种方式
- 注册jdbc驱动程序的三种方式
- 注册jdbc驱动程序的三种方式
- 注册jdbc驱动程序的三种方式
- Android高手进阶教程(二十一)之---Android中创建与几种解析xml的方法!
- opensolaris如何让网络设置持久化
- JavaScript Array(数组)对象
- 自定义类LoggerClass代码
- andriod git repo 使用方法
- 驱动程序访问用户模式数据缓冲区的三种方式
- 常用电脑检测软件列表!提供下载!
- sql手记
- curl使用简介
- ORA-22992 cannot use LOB locators selected from remote tables(转)
- WebService调用Session
- Ubuntu Linux系统下Apt-get命令参数详解
- RAID
- 去掉窗体滚动条,并在自定义DIV中显示垂直滚动条