上层和驱动通信

上层驱动通信DeviceIoControl函数,这是一个Win32 API,在SDK中定义。这个函数都会产生一个IRP_MJ_DEVICE_CONTROL包,如果驱动中注册过相应的例程,那么这个包就会引发该例程的工作。如果是驱动和驱动间的通信,那么用IoBuildDeviceControlRequest函数,该函数在DDK中定义,会产生一个IRP_INTERNAL_DEVICE_CONTROL包,并引发相应的例程。这两个IRP包中都有一个非常重要的结构叫IOCTLio control code),用于指定通信中的各类细节。该数据结构是一个32比特的数据块,有6个区域,每个区域包含一类信息。IOCTL的结构如下图所示


上层和驱动通信_第1张图片

DDK中有一个CTL_CODE宏,用这个宏我们可以很方便的定义IOCTL。不管是IRP_MJ_DEVICE_CONTROL还是IRP_INTERNAL_DEVICE_CONTROL包,IOCTL都用如下形式定义:

#define IOCTL_Device_Function CTL_CODE(DeviceType, Function, Method, Access)

DeviceType:设备类型,和DEVICE_OBJECT结构中的DeviceType必须一致。注意:0x8000以下的数字被微软占用了。

Function Code:功能代码,可以自定义,用来区分操作类型。注意:0x800以下的数字被微软占用了。

MethodIO缓冲类型,有METHOD_BUFFEREDMETHOD_IN_DIRECTMETHOD_OUT_DIRECTMETHOD_NEITHER四种类型。

METHOD_BUFFERED表明输入输出都用系统缓冲,这种策略下输入输出指向的是同一个内存块,该内存块有IO Manager管理。输入的时候把数据拷贝到缓冲中,然后缓冲再拷贝到驱动;输出的时候数据拷贝到缓冲中,然后缓冲拷贝到用户空间。由于用的是同一块缓冲,所以调用者自己得管理好里面的数据,防止弄混。缓冲区地址存放在IRP.AssociatedIrp.SystemBuffer中,输入数据大小为Parameter.DeviceIoControl.InputBufferLength,输出数据大小为Parameter.DeviceIoControl.OutputBufferLength,两者都在IO_STACK_LOCATION结构中。

METHOD_IN_DIRECT表明输出用缓冲,输入用直接IO。这种策略下输出和上面的方法一致,而输入则是直接访问指定的内存区域,不通过缓冲。IOManager先把输入数据的内存块锁定,然后把地址存放在IRP.MdlAddress中。输入输出数据块的大小和上面一致。

METHOD_OUT_DIRECT表明输入用缓冲,输出直接IOIO Manager把输出数据的内存快锁定,存放在IRP.MdlAddress中,驱动直接通过该地址访问数据,输入数据通过系统缓冲,存放在IRP.AssociatedIrp.SystemBuffer中。输入输出数据块的大小和上面一致。

METHOD_NEITHER表明输入输出都不用缓冲,I/O Manager把调用者的输入缓冲区的地址放到IRP当前I/O堆栈单元的Parameters.Devi ceIoControl.TypeInputBuffer域中,把输出缓冲 区的地址存放到IRPUserBuffer域中。这两个地址都是用户空间地 址。

从上面的说明可以看出,在执行缓冲I/O时,I/O管理器将在非份页池 中分配内存,如果调用者的缓冲区比较大时,分配的非份页池也将 比较大。非份页池是系统比较宝贵的资源,因此,如果调用者的缓 冲区比较大时,我们一般采用直接I/O的方式(例如磁盘读写请求等) 这样不仅节省系统资源,另一方面由于省去了I/O管理器在系统缓冲 区和调用者缓冲区之间的数据拷贝,也提高了效率,这对存在大量 数据传送的驱动程序尤其明显。不过需要注意的是,直接io要求驱动和IOCTL的发起者运行在同一个线程里。

Access:指明调用者的访问权限,有FILE_ANY_ACCESSFILE_READ_DATAFILE_WRITE_DATA三个选项可选。FILE_ANY_ACCESS表明用户拥有所有的权限,FILE_READ_DATA表明权限为只读,FILE_WRITE_DATA表明权限为可写。FILE_WRITE_DATA | FILE_READ_DATA表明权限为可读可写,但还没达到FILE_ANY_ACCESS的权限。

用户定义IOCTL时要注意以下几条原则:

1. FunctionCode总是定义成0x800以上的数字,因为0x800以下的数字被微软占用了。

2. 仔细考虑访问权限,如果指定了你不具备的权限,那么IO Manager会忽略IOCTL

3. 仔细考虑要访问的内存区域,如果去读写一个关键内存,那么系统会重启

驱动内部执行IOCTL时要注意以下几条原则:

1. 接收到IOCTL时,要先检查整个32比特的数据完整性

2. IoValidateDeviceIoControlAccess检查访问权限是否有效

3. 严格遵照Parameter.DeviceIoControl.InputBufferLengthParameter.DeviceIoControl.OutputBufferLength指定的大小访问输入输出区域,否则系统会重启

4. 驱动中申请一块内存后,总是先用RtlZeroMemory清空区域

5. 直接io策略中,用MmGetSystemAddressForMdlSafe获取相应内存区域时,要判断是否为NULL

6. 直接io中,用ProbeForRead ProbeForWrite检查内存是否可以访问。

////////////////////////////////////////
Windows NT 4.0 DDK (可通过 MSDN Professional 成员) 说明如何执行此映射中没有示例 (MAPMEM)。 下面是大纲方法是:
1. 获取转换物理地址的适配器内存 (HalTranslateBusAddress)。
2. 打开到物理内存设备 Device\PhysicalMemory 句柄 (ZwOpenSection) 部分。
3. 引用对象句柄 (ObReferenceObjectByHandle) 以防止它被删除。
4. 映射 (ZwMapViewOfSection) 内存。
仅从 ZwMapViewOfSection 得到虚拟地址映射其进程的上下文中有效。 如果要访问您驾驶延迟过程调用 (DPC) 中内存或中断服务例程 (ISR), 在任意进程上下文, 运行您应该还映射内存系统地址空间 (使用下方法) 中。 取消映射 (ZwUnMapViewOfSection) 在同一内存进程上下文, 映射之前退出。
//////////////////////////////////////

MmMapIoSpace 方法

此方法说明如何映射内存进程用户地址空间和系统进程地址空间中。
1. 获取转换物理地址的适配器内存 (HalTranslateBusAddress)。
2. 将内存映射到非分系统地址空间如下:
SystemVirtualAddress = MmMapIoSpace(PhysicalAddress, SizeofMemory,
         CacheEnable);
3. 分配一个 Mdl:
Mdl = IoAllocateMdl(SystemVirtualAddress, SizeOfMemory, FALSE, FALSE,
         NULL);
4. 生成 MDL 来描述内存页:
MmBuildMdlForNonPagedPool(Mdl);
5. 映射到进程内存空间用户是使用 MmMapLockedPages。 因为有处于一致之间此函数返回值是 Windows NT SP 4 以前版本 SP 和释放, 使用下列语句以使此代码与所有版本的 WindowsNT 兼容:
UserVirtualAddress = (PVOID)(((ULONG)PAGE_ALIGN(MmMapLockedPages(Mdl,
         Mode))) + MmGetMdlByteOffset(Mdl));
////////////////////////////////////////////////
取消如果映射到系统地址空间, 您应映射如下:
MmUnmapIoSpace(SystemVirtualAddress, SizeofMemory);
取消如果映射到用户地址空间, 您应映射如下: 中映射内存进程的上下文中运行时仅
MmUnmapLockedPages(UserVirtualAddress, Mdl); 

Finally, free the MDL by calling:

IoFreeMdl(Mdl);

你可能感兴趣的:(function,IO,manager,File,Access,DDK)