当前位置: 首页 > news >正文

开发外包网站网站建设制作设计seo优化湖北

开发外包网站,网站建设制作设计seo优化湖北,免费咨询更多详情,vs2017做的网站在学习内核过滤驱动的过程中#xff0c;遇到了大量的涉及IRP操作的代码#xff0c;这里有必要对IRP的数据结构和与之相关的API函数做一下笔记。 1. 相关阅读资料 《深入解析 windows 操作系统(第4版,中文版)》 --- 9章 《windows driver kit 帮助文档》 http://support.mi…在学习内核过滤驱动的过程中遇到了大量的涉及IRP操作的代码这里有必要对IRP的数据结构和与之相关的API函数做一下笔记。 1. 相关阅读资料 《深入解析 windows 操作系统(第4版,中文版)》 --- 9章 《windows driver kit 帮助文档》 http://support.microsoft.com/kb/115758/zh-cn  IRP 结构中各地址字段的含义 http://www.programlife.net/io_stack_location-irp.html    代码疯子对IRP的研究 2. IRP的数据结构 IRP是一个数据结构其中包含了用来描述一个IO请求的完整信息。IO管理器创建一个IRP来代表一个IO操作并且将该IRP传递给正确的驱动程序当此IO操作完成时再处理该请求包。相对的驱动程序(上层的虚拟设备驱动或者底层的真实设备驱动)接收一个IRP执行该IRP指定的操作然后将IRP传回给IO管理器告诉它该操作已经完成或者应该传给另一个驱动以进行进一步处理。 谈到IRPIRP是个总的概念本质上IRP由IRP Header和IRP Sub-Request组成 从数据结构的角度上来说其实数据结构 IRP 只是I/O 请求包IRP的头部在 IRP 数据结构的后面还有一个IO_STACK_LOCATION 数据结构的数组数组的大小则取决于 IRP 数据结构中的StackCount(我们之后会详细分析)其数值来自设备堆栈中顶层设备对象的 StackSize 字段。 这样就在 IRP 中为目标设备对象设备堆栈中的每一层即每个模块(每个驱动)都准备好了一个 IO_STACK_LOCATION 数据结构。而CurrentLocation则是用于该数组的下标说明目前是在堆叠中的哪一层因而正在使用哪一个 IO_STACK_LOCATION 数据结构。 那这两个结构是怎么来的呢有两种渠道: 1. 程序员在代码中手工地创建一个IRP(广义的IRP) PIRP IoAllocateIrp(IN CCHAR StackSize,IN BOOLEAN ChargeQuota); 任何内核模式程序在创建一个IRP时同时还创建了一个与之关联的 IO_STACK_LOCATION 结构数组数组中的每个堆栈单元都对应一个将处理该IRP的驱动程序堆栈单元中包含该IRP的类型代码和参数信息以及完成函数的地址。 2. I/O管理器在接收到应用层的设备读写请求后将请求封装为一个IRP请求(包括IRP头部和IRP STACK_LOCATINO数组)发往对应的设备的设备栈的最顶层的那个设备驱动。 我们先从IRP的头结构开始学起: 下面是WDK上搬下来的解释我们来一条一条的学习。 IRPtypedef struct _IRP {..PMDL MdlAddress;ULONG Flags;union {struct _IRP *MasterIrp;..PVOID SystemBuffer;} AssociatedIrp;..IO_STATUS_BLOCK IoStatus;KPROCESSOR_MODE RequestorMode;BOOLEAN PendingReturned;..BOOLEAN Cancel;KIRQL CancelIrql;..PDRIVER_CANCEL CancelRoutine;PVOID UserBuffer;union {struct {..union {KDEVICE_QUEUE_ENTRY DeviceQueueEntry;struct {PVOID DriverContext[4];};};..PETHREAD Thread;..LIST_ENTRY ListEntry;..} Overlay;..} Tail; } IRP, *PIRP; MSDN 说IRP是一个半透明结构开发者只能访问其中透明的部分所以其中的..代表是我们不能访问的部分所以我们在编程中基本上也不会用到它们我们集中精力来观察这些暴露出来的数据结构。 1. IRP中的三种缓冲区 PMDL MdlAddress; union {struct _IRP *MasterIrp;..PVOID SystemBuffer;} AssociatedIrp; PVOID UserBuffer; IRP这个数据结构中有3个地方可以描述缓冲区。  1) irp-AssociatedIrp.SystemBuffer 2) irp-MdlAddress 3) irp-UserBuffer 不同的IO类别IRP的缓冲区不同。1) AssociatedIrp.SystemBuffer一般用于比较简单且不追求效率情况下的解决方案 把R3层中的内存中的缓冲数据拷贝到内核空间中。注意是直接拷贝过来有的书上会说这是直接方式不过我们要重点记住的是它是直接拷贝过来。2) MdlAddress通过构造MDL就能实现这个R3到R0的地址映射功能。MDL可以翻译为内存描述符链本质上就是一个指针从这个MDL中可以读出一个内核空间的虚拟地址。这就弥补了UserBuffer的不足同时比SystemBuffer的完全拷贝方法要轻量因为这个内存实际上还是在老地方没有拷贝。 3) UserBuffer最追求效率的解决方案 R3的缓冲区地址直接放在UserBuffer里在内核空间中直接访问。在当前进程和发送进程一致的情况下内核访问应用层的内存空间当然是没错的。但是一 旦内核进程已经切换这个访问就结束了访问UserBuffer当然是跳到其他进程空间去了(我们还是访问同一个地址但是这个时候因为进程的上下文切换了同一个地址对应的内容自然不同了)。因为在windows中内核空间是所有进程共享的而应 用层空间则是各个进程隔离的。当然还有一个更简单的做法是把应用层的地址空间映射到内核空间这需要在页表中增加一个映射。 在驱动在获取这三种缓冲区的方法。 //获得缓冲区 PUCHAR buf NULL; if(irp-MdlAddress ! NULL) {buffer (PUCHAR)MmGetSystemAddressForMdlSafe(irp-MdlAddress, NormalPagePriority); } else {buffer (PUCHAR)irp-UserBuffer; } if(buffer NULL) {buffer (PUCHAR)irp-AssociatedIrp.SystemBuffer; } 看到这里就产生另一个问题了这三种缓冲区是操作系统帮我们自动填好的吗那在什么样的IRP请求会使用到不同的缓冲区类型呢 这里就要涉及到两种内存访问方式: 直接方式DO_DIRECT_IO / 非直接方式(缓冲方式)DO_BUFFERD_IO 1) 在buffered(AssociatedIrp.SystemBuffer)方式中I/O管理器先创建一个与用户模式数据缓冲区大小相等的系统缓冲区。而你的驱动程序将使用这个系统缓冲区工作。I/O管理器负责在系统缓冲区和用户模式缓冲区之间复制数据。2) 在direct(MdlAddress)方式中I/O管理器锁定了包含用户模式缓冲区的物理内存页并创建一个称为MDL(内存描述符表)的辅助数据结构来描述锁定页。因此你的驱动程序将使用MDL工作。3) 在neither(UserBuffer)方式中I/O管理器仅简单地把用户模式的虚拟地址传递给你。而使用用户模式地址的驱动程序应十分小心。 继续思考我们之前的问题在IRP中具体是使用哪种缓冲方式呢由谁来决定 答案是在增加设备的时候就决定的了。即我们在新增一个设备的时候就要决定这个设备的缓冲区读写方式。 DRIVER_ADD_DEVICE AddDevice;NTSTATUSAddDevice(__in struct _DRIVER_OBJECT *DriverObject,__in struct _DEVICE_OBJECT *PhysicalDeviceObject ){...}NTSTATUS IoCreateDevice(IN PDRIVER_OBJECT DriverObject,IN ULONG DeviceExtensionSize,IN PUNICODE_STRING DeviceName OPTIONAL,IN DEVICE_TYPE DeviceType,IN ULONG DeviceCharacteristics,IN BOOLEAN Exclusive,OUT PDEVICE_OBJECT *DeviceObject);typedef struct _DEVICE_OBJECT {CSHORT Type;USHORT Size;LONG ReferenceCount;PDRIVER_OBJECT DriverObject;PDEVICE_OBJECT NextDevice;PDEVICE_OBJECT AttachedDevice;PIRP CurrentIrp;PIO_TIMER Timer;ULONG Flags;//noticeULONG Characteristics;__volatile PVPB Vpb;PVOID DeviceExtension;DEVICE_TYPE DeviceType;CCHAR StackSize;union {LIST_ENTRY ListEntry;WAIT_CONTEXT_BLOCK Wcb;} Queue;ULONG AlignmentRequirement;KDEVICE_QUEUE DeviceQueue;KDPC Dpc;ULONG ActiveThreadCount;PSECURITY_DESCRIPTOR SecurityDescriptor;KEVENT DeviceLock;USHORT SectorSize;USHORT Spare1;PDEVOBJ_EXTENSION DeviceObjectExtension;PVOID Reserved; } DEVICE_OBJECT, *PDEVICE_OBJECT;NTSTATUS AddDevice(DriverObject, PhysicalDeviceObject) { PDEVICE_OBJECT fdo; IoCreateDevice(..., fdo); fdo-Flags | DO_BUFFERED_IO; //或者是下面的代码这三句任取其一fdo-Flags | DO_DIRECT_IO; //or fdo-Flags | 0; } 总结一下在新增设备的时候这个物理设备的数据缓冲方式就被决定了。这之后你不能该变缓冲方式的设置因为过滤器驱动程序将复制这个标志设置并且如果你改变了设置过滤器驱动程序没有办法知道这个改变。 接下来解决目前为止的最后一个疑问: 系统是如何来根据设备对象中的缓冲类型码来进行不同缓冲区类型的的缓冲区数据的实际填充过程的(即实际的缓冲区填充过程是什么) 1) Buffered方式(有一个复制过程)当I/O管理器创建IRP_MJ_READ或IRP_MJ_WRITE请求时(读写请求中会用到数据缓冲区普通的文件属性查询请求或设置中最多用到一个指定的数据结构大小的内存空间即可)它探测设备的缓冲标志(在创建设备时就决定的了)以决定如何描述新IRP中的数据缓冲区。如果DO_BUFFERED_IO(UserBuffer)标志设置I/O管理器将分配与用户缓冲区大小相同的非分页内存(不会产生缺页中断的内存空间)。注意它把缓冲区的地址和长度保存到两个十分不同的地方下面是一段模拟代码。你可以假定I/O管理器执行下面代码(注意这并不是Windows NT的源代码): PVOID uva; // user-mode virtual buffer address ULONG 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联合中。I/O管理器把用户模式虚拟地址(uva变量)保存到IRP的UserBuffer域中这样一来内核驱动代码就可以找到这个地址。 2) Direct方式如果你在设备对象中指定DO_DIRECT_IO(MDL)方式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; StartVa成员给出了用户缓冲区的虚拟地址这个地址仅在拥有数据缓冲区的用户模式进程上下文中才有效(即用户模式中的虚拟地址空间)。ByteOffset是缓冲区起始位置在一个页帧中的偏移值ByteCount是缓冲区的字节长度。Pages数组没有被正式地声明为MDL结构的一部分在内存中它跟在MDL的后面包含用户模式虚拟地址映射为物理页帧的个数。 要注意的是我们不可以直接访问MDL的任何成员。应该使用宏或访问函数: 宏或函数 描述 IoAllocateMdl 创建MDL(在文件系统驱动的透明加密中你可能需要创建自己的临时MDL以暂时替换系统的原始的MDL)IoBuildPartialMdl 创建一个已存在MDL的子MDLIoFreeMdl 销毁MDLMmBuildMdlForNonPagedPool 修改MDL以描述内核模式中一个非分页内存区域MmGetMdlByteCount 取缓冲区字节大小MmGetMdlByteOffset 取缓冲区在第一个内存页中的偏移MmGetMdlVirtualAddress 取虚拟地址MmGetSystemAddressForMdl 创建映射到同一内存位置的内核模式虚拟地址MmGetSystemAddressForMdlSafe 与MmGetSystemAddressForMdl相同但Windows 2000首选MmInitializeMdl (再)初始化MDL以描述一个给定的虚拟缓冲区MmPrepareMdlForReuse 再初始化MDLMmProbeAndLockPages 地址有效性校验后锁定内存页MmSizeOfMdl 取为描述一个给定的虚拟缓冲区的MDL所占用的内存大小MmUnlockPages 为该MDL解锁内存页 对于I/O管理器执行的Direct方式的读写操作其过程可以想象为下面代码 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 IRPMmUnlockPages(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); 3) 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; elsestack-Parameters.Write.Length length; code to send and await IRP 至此我们目前的疑问就全部解决了我们知道了IRP中的三种不同的缓冲区是怎么来的(设备添加的时候决定的)是由谁填充的(操作系统自动地把用户模式地址空间的数据填充到IRP中的缓冲区中/或者直接给出用户空间地址)。 接下来就可以引出IRP中(准备说是IRP头部的另一个成员域) 2. ULONG  Flags File system drivers use this field, which is read-only for all drivers. Network and, possibly, highest-level device drivers also might read this field, which can be set with one or more of the following system-defined masks: 在文件系统驱动程序的编程中将使用到这个数据域这对所有的驱动程序来说是只读的它指示了这个IRP的操作类型。 IRP_NOCACHE IRP_PAGING_IO IRP_MOUNT_COMPLETION IRP_SYNCHRONOUS_API IRP_ASSOCIATED_IRP IRP_BUFFERED_IO IRP_DEALLOCATE_BUFFER IRP_INPUT_OPERATION IRP_SYNCHRONOUS_PAGING_IO IRP_CREATE_OPERATION IRP_READ_OPERATION IRP_WRITE_OPERATION IRP_CLOSE_OPERATION IRP_DEFER_IO_COMPLETION 关于这个Flags字段我们常常要注意的是我们如果在做过滤/绑定/捕获类型的驱动类型的编程中我们新创建的上层过滤设备的Flags字段的值一定要和下层的真实设备或底层驱动的Flags保持一致。 比如我在做文件系统卷的过滤设备编程的时候就遇到这样的代码: //设备标志的复制 if (FlagOn( DeviceObject-Flags, DO_BUFFERED_IO )) {SetFlag( SFilterDeviceObject-Flags, DO_BUFFERED_IO ); } if (FlagOn( DeviceObject-Flags, DO_DIRECT_IO )) {SetFlag( SFilterDeviceObject-Flags, DO_DIRECT_IO ); } 3.  IO_STATUS_BLOCK  IoStatus typedef struct _IO_STATUS_BLOCK {union {NTSTATUS Status;PVOID Pointer;};ULONG_PTR Information; } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; IoStatus(IO_STATUS_BLOCK)是一个仅包含两个域的结构驱动程序在最终完成请求时设置这个结构。IoStatus.Status 表示IRP完成状态ntdef.hntstatus.hWDK的这两个头文件中定了所有的系统级的返回信息在驱动中函数的返回值大多数情况下就是这样结果状态信息当然也有通过引用的方式获得函数执行的结果的但是函数还是返回个执行结果。IoStatus.information的值与请求相关如果是数据传输请求则将该域设置为传输的字节数(在windows编程中基本上涉及到数据读写的函数一般都是返回这一类的结果即操作的字节数)。 4. KPROCESSOR_MODE  RequestorMode RequestorMode将等于一个枚举常量UserMode或KernelMode 指定原始I/O请求的来源。驱动程序有时需要查看这个值来决定是否要信任某些参数。 5. BOOLEAN PendingReturned PendingReturned(BOOLEAN)如果为TRUE则表明处理该 IRP的最低级派遣例程返回了STATUS_PENDING。完成例程通过参考该域来避免自己与派遣例程间的潜在竞争。 If set to TRUE, a driver has marked the IRP pending. Each IoCompletion routine should check the value of this flag. If the flag is TRUE, and if the IoCompletion routine will not return STATUS_MORE_PROCESSING_REQUIRED, the routine should call IoMarkIrpPending to propagate the pending status to drivers above it in the device stack. 这段话是什么意思呢这和内核驱动中的多层设备栈有关系。为了解释这个问题我们先来看一段代码demo: .... Kevent event; KeInitializeEvent(event, NotificatinoEvent, FALSE); IoCopyCurrentIrpStackLocationToNext(Irp); //设置完成回调函数 IoSetCompletionRoutine(Irp,IrpComplete, //回调函数event,TRUE,TRUE,TRUE ); status IoCallDriver(DeviceObject, Irp); if(status STATUS_PENDING) {//code to handle asynchronous response//异步 status KeWaitForSingleObject( waitEvent,Executive,KernelMode,FALSE,NULL );} ...//这是一个IRP完成回调函数的原型 NTSTATUS IrpComplete(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp,IN PVOID Context ) {...if(Irp-PendingReturned){//这个函数等价于: Irp-IoStatus.Status STATUS_PENDING 即表名这个IRP处理流程依旧没有结束IoMarkIrpPending(Irp);}return Irp-IoStatus.Status; } 请原谅我没头没脑的给这出这段看起来不知所云的代码。这是因为IRP机制是一种基础机制往往是配合一些具体的过滤驱动的编程而使用的如果要给出完整的例程那这篇文章的篇幅就会无穷无尽了。 所以接下来我尽我最大的能力来解释这段代码的意思并给出它的利用场景。 IRP作为一个线程无关的调用栈进行一个设备的I/O操作通常需要调用这一设备相关的不止一个驱动。每一个和这个设备相关的驱动都会创建一个设备对象(Device Object)并且这些设备对象会垂直压入(排列进)一个设备栈(Device Stack)。IRP会在设备栈中从上到下的一个个被传递进去(从顶层的设备驱动一直往下到底层的真实设备)。对于栈中的每一个驱动IRP都会用一个指针标识一个栈位置(IO_STACK_LOCATION和设备栈上的设备驱动的一一对应关系用这个指针来绑定)。由于驱动可以异步地处理请求因此IRP就像是一个线程无关的调用栈一样 仔细看这张图每一级驱动程序都使用下一级驱动程序的堆栈单元保存自己完成例程指针。最底层的驱动程序不应该安装一个完成例程它应该总是返回一个真实的硬件操作结果。即我们在当前位置的设备驱动中设置一个下层驱动的完成回调函数时本质上是在下层驱动的栈空间中布置一个了一个回调函数地址。 将IRP传递到下一级驱动程序(又被称作转发IRP)是指IRP等价于一个子例程调用。当驱动转发一个IRP这个驱动程序必须向IRP参数组增加下一个I/O栈位置告知这一IRP栈的指针然后调用下一驱动的分发例程(dispatch routine)。基本来说就是驱动向下调用IRP栈(calling down the IRP stack)传递一个IRP驱动通常会采取以下几种步骤1) . 建立下一个I/O栈位置的参数。1.1) 调用IoGetNextIrpStackLocation例程来得到一个指针指向下一个I/O栈位置然后将请求参数数组复制到那个得到的位置1.2) 调用CopyCurrentIrpStackLocationToNext例程(如果驱动设置了IoCompletion例程)或者1.3) 调用IoSkipCurrentIrpStackLocation例程(没有设置IoCompletion例程)来传递当前位置所使用的同样的参数组。2). 如果需要的话调用IoSetCompletionRoutine例程为后期处理(post-processing)设置一个IoCompletion例程。如果驱动设置了IoCompletion例程那么他在上一步中必须使用IoCopyCurrentIrpStackLocationToNext。3). 通过调用IoCallDriver例程将请求传递到下一个驱动。这个例程会自动通告IRP栈指针并且调用下一个驱动的分发例程。 在驱动程序将IRP传递给下一个驱动之后就不再拥有这个IRP并且不能视图再去访问这个IRP否则会导致系统崩溃。那个IRP会被其他的驱动或者线程释放或这直接完成。 理解这句话非常重要这个IRP中的核心思想也就是说一旦你调用了IoCallDriver()把IRP传递给了下层的驱动这个IRP就和你没关系了。如果驱动需要访问一个已经在栈里传下去的IRP那么这个驱动必须实现(设置)IoCompletion例程。当I/O管理器(I/O Manager)调用IoCompletion例程时(当下层完成处理后自动调用了回调函数)这个驱动(之前把IRP下发的那个上层驱动)就能够在IoCompletion例程执行期间重新获得对这一IRP的所有权。如此IoCompletion例程就能够访问IRP中的域。 设置异步完成回调函数的方法上面的代码已给出这是一个经典的模型即创建一个事件对象-初始化这个事件对象-上层对这个事件进行阻塞等 待-将IRP下发给下层驱动-下层驱动完成处理逻辑后设置设置这个事件(即触发一个完成信号解除这个事件的互斥)-上层驱动获 得这个事件的释放-上层驱动继续代码逻辑并根据下层驱动的返回结构来做进一步的操作。若是驱动的分发例程(上层驱动)也必须在IRP被后面的驱动(下层驱动)处理完成之后再处理它这个IoCompletion例程(上层驱动设置的完成回调函数)必须返回STATUS_MORE_PROCESSING_REQUIRED以将IRP的所有权返回给分发例程(上层驱动)。如此依赖I/O管理器会停止IRP的处理(这指回卷处理之后会解释)将最终完成IRP的任务(调用IoCompleteRequest来完成这个IRP)留给分发例程。分发例程能够在之后调用IoCompleteRequest来完成这个IRP或者还能将这个IRP标记为等待进一步处理继续回传给它之上的驱动。 当输入、输出操作(I/O)完成时完成这个I/O操作的驱动会调用IoCompleteRequest例程这个例程将IRP栈指针移到指向IRP栈的前一个(更上面)的位置。如果一个驱动在设备栈中向下传递IRP时设定了IoCompletion例程I/O管理器就会在IRP栈指针再次指向这一驱动的这个I/O栈位置的时候调用此例程IoCompletion例程就表现为: 当IRP在设备栈中传递时操作IRP的那些驱动的返回地址。 当每一个驱动都完成了它对应的子请求I/O请求就完成了。I/O管理器从Irp-IoStatus.Status域取回请求的状态信息并且从Irp-IoStatus.Information域取回传输的字节数。 这我们要理清一下思路: 我们知道IRP在设备栈中逐级向下传递并根据情况可能逐级的设置回调函数 一直到最底层的真实设备完成了请求。 之后系统会有一个回卷操作类似我们在C编程中的函数栈的调用迭代的模型。 逐级的回卷过程就是不断调用上层驱动设置的回调函数的过程。 也就是不断触发IoCompletion函数的过程上层驱动通过IoCompletion来获得对之前下发的IRP的重新控制权。一个IoCompletion例程能够返回两个状态值中的任一个: 1. STATUS_PENDING--继续向上完成特定IRP。I/O管理器提升IRP栈指针并且调用上一个驱动的IoCompletion例程(继续回卷) 2. STATUS_MORE_PROCESSING_REQUIRED--中断向上完成特定IRP的过程并且把IRP栈指针留在当前位置(暂时中断回卷)。 返回这一状态的驱动通常会在过后调用IoCompleteRequest例程来重新开始向上完成特定的IRP的过程。 完成IRP时是忽略还是拷贝当前栈空间(IO_STACK_LOCATION)返回什么状态 值以及完成函数中如何结束IRP我们这里做个总结。 1) 如果对IRP完成之后的事情无兴趣则直接忽略当前IO_STACK_LOCATION(即 调用内核API函数IoSkipCurrentIrpStackLocation)然后向下传递请求返回IoCallDriver所返回的状态。2) 不但对IRP完成之后的事情无兴趣而且不打算立刻返回成功或失败(即挡在当前这 一层的驱动这里不再往下传递了)。那么不用忽略或者拷贝当前IO_STACK_LOCATION填写IRP的状态参数后调用IoCompleteRequest 并返回自己想返回的结果。3) 如果对IRP完成之后的事情有兴趣并打算在完成函数中处理则应该首先拷贝当 前IO_STACK_LOCATION(IoCopyCurrentIrpStackLocationToNext)然后指定完成函数并返回IoCallDriver()所返回的状态。在完成函数中不需要调用 IoCompleteRequest直接返回IRP的当前状态即可4) 如果对IRP完成之后的事情有兴趣并打算在完成函数中处理有时候会把任务塞 进系统工作线程或者希望在另外的线程中去完成IRP那么完成函数中应该返回STATUS_MORE_PROCESSING_REQUIRED此时完成IRP时应该调用 IoCompleteRequest。另一种类似的情况是在分发函数中等待完成函数中设置事件那么完成函数返回STATUS_MORE_PROCESSING_REQUIRED分发函数 在等待结束后调用IoCompleteRequest。 (这段话是我们自己根据MSDN和《寒江独钓》的研究后总结的说心理话不敢保证100%正确这块内容确实很复杂如果看到这篇文章的牛牛知道真实的详细细节的话希望不吝赐教分享一些好的思路) 至此我们知道了PendingReturned是用来判断下层的驱动返回的处理状态的。那它的利用场景是什么呢这通常见于一些文件系统驱动过滤的应用中: 如磁盘透明加密, NTFS透明加密的编程中我们的上层过滤驱动要先捕获到这个IRP_MJ_READ请求然后下放这个IRP让它去调用磁盘驱动读取数据然后在完成回调函数中对刚才读取到的数据进行加解密这是一个典型的应用。 6. IRP操作取消机制 BOOLEAN Cancel; KIRQL CancelIrql; . . PDRIVER_CANCEL CancelRoutine; 这三个字段同属于IRP取消机制的范凑我们一并研究。 Cancel(BOOLEAN)如果为TRUE则表明IoCancelIrp已被调用该函数用于取消这个请求。如果为FALSE则表明没有调用IoCancelIrp函数。 CancelIrql(KIRQL)是一个IRQL值表明那个专用的取消自旋锁是在 这个IRQL上获取的。当你在取消例程中释放自旋锁时应参考这个域。 CancelRoutine(PDRIVER_CANCEL)是驱动程序取消例程的地 址。你应该使用IoSetCancelRoutine函数设置这个域而不是直接修改该域 PDRIVER_CANCEL IoSetCancelRoutine(IN PIRP Irp,IN PDRIVER_CANCEL CancelRoutine); VOID IoAcquireCancelSpinLock(OUT PKIRQL Irql); IRP请求的最终结局无非有两个要么被完成了要么被取消了。完成IRP请求的过程已经在前面讲过了这里仔细讲一个IRP请求的取消。为什么要取消IRP请求呢一般来讲原因不外乎是本请求操作超时或设备故障导致的。具体理解可以考虑如下两种情形情形1驱动发送一个请求到下级驱动下级驱动由于忙将它放到自己的请求队中去下级驱动一直忙请求一直没有得到处理而这个请求又比较重要如果一直得不到处理就会造成系统处于死锁。于是驱动就会给这个请求加上超时机制若超过一定的时间还没有得到处理结果就通知下级驱动直接取消该请求。情形1驱动发送很多请求到下级驱动去处理下级驱动返回了一个请求的结果。可是这个结果是个错误而且是个很严重的错误比如设备出故障了。这时就要将设备进行错误恢复如重启设备同时其它送下去的请求都要同时取消掉。驱动如何被取消 一般来讲取消的发起者一定是上层的驱动而取消的实际执行者则是下层的驱动。 1. 上层的驱动通过调用 IoCancelIrp(Irp)来取消某个请求。这个请求应该是被某个下层驱动放在缓冲队列中等待处理或正在处理。2. 下层驱动为了能支持驱动取消机制一般都在收到IRP请求时就马上注册一个取消回调例程 CancelRoutine注册的函数是 IoSetCancelRoutine(Irp, CancelRoutine)3. IoCancelIrp 调用中就会去先调用 IoAcquireCancelSpinLock再调用这个设置好的CancelRoutine4. CancelRoutine中驱动一般会根据当前Irp的状态来做相应的操作。如果Irp是在请 求队列中就会先将它移出队列设置好返回状态STATUS_CANCELLED然后再调用IoCompleteRequest来直接完成这个请求最后千万不要忘记调用 IoReleaseCancelSpinLock(Irp的CancelIrql)来释放自旋锁。 7. Tail(一个很大的联合体) union {struct {..union {KDEVICE_QUEUE_ENTRY DeviceQueueEntry;struct {PVOID DriverContext[4];};};..PETHREAD Thread;..LIST_ENTRY ListEntry;..} Overlay;.. } Tail; Tail.Overlay是Tail联合中的一种 结构它含有几个对WDM驱动程序有潜在用途的成员。 在这个图中以水平方向从左到右是这个联合的三个可 选成员在垂直方向是每个结构的成员描述。 Tail.Overlay.DeviceQueueEntry(KDEVICE_QUEUE_ENTRY) 和 Tail.Overlay.DriverContext(PVOID[4])是Tail.Overlayare内 一个未命名联合的两个可选成员(只能出现一个)。 I/O管理器把DeviceQueueEntry作为设备标准请求队列中的连接域。当IRP还没有进入某 个队列时如果你拥有这个IRP你可以使用这个域你可以任意使用DriverContext中的四个指针。 Tail.Overlay.ListEntry(LIST_ENTRY) 仅能作为你自己实现的私有队列的连接域。我们在做文件系统驱动过滤的时候往往对读写请求进行串行化处理这个时候就需要这个ListEntry来构建一个IO请求的队列以解决并行请求的串行化问题。 至此我们把IRP头部的数据结构分析完了接下继续学习下IRP Sub-Requst子请求部分的数据结构(即设备栈中的每层驱动对应的IO_STACK_LOCATION结构) 我们知道在IRP头部后面跟有很多个相同的结构。 PIO_STACK_LOCATION IoGetCurrentIrpStackLocation(IN PIRP Irp); 使用这个函数可以获得本层所对应的那个IO_STACK_LOCATION。 我们来分析一下这个IO_STACK_LOCATION的数据结构。 typedef struct _IO_STACK_LOCATION {UCHAR MajorFunction;UCHAR MinorFunction;UCHAR Flags;UCHAR Control;union {//// Parameters for IRP_MJ_CREATE //struct {PIO_SECURITY_CONTEXT SecurityContext;ULONG Options;USHORT POINTER_ALIGNMENT FileAttributes;USHORT ShareAccess;ULONG POINTER_ALIGNMENT EaLength;} Create;//// Parameters for IRP_MJ_READ //struct {ULONG Length;ULONG POINTER_ALIGNMENT Key;LARGE_INTEGER ByteOffset;} Read;//// Parameters for IRP_MJ_WRITE //struct {ULONG Length;ULONG POINTER_ALIGNMENT Key;LARGE_INTEGER ByteOffset;} Write;//// Parameters for IRP_MJ_QUERY_INFORMATION //struct {ULONG Length;FILE_INFORMATION_CLASS POINTER_ALIGNMENT FileInformationClass;} QueryFile;//// Parameters for IRP_MJ_SET_INFORMATION //struct {ULONG Length;FILE_INFORMATION_CLASS POINTER_ALIGNMENT FileInformationClass;PFILE_OBJECT FileObject;union {struct {BOOLEAN ReplaceIfExists;BOOLEAN AdvanceOnly;};ULONG ClusterCount;HANDLE DeleteHandle;};} SetFile;//// Parameters for IRP_MJ_QUERY_VOLUME_INFORMATION //struct {ULONG Length;FS_INFORMATION_CLASS POINTER_ALIGNMENT FsInformationClass;} QueryVolume;//// Parameters for IRP_MJ_DEVICE_CONTROL and IRP_MJ_INTERNAL_DEVICE_CONTROL //struct {ULONG OutputBufferLength;ULONG POINTER_ALIGNMENT InputBufferLength;ULONG POINTER_ALIGNMENT IoControlCode;PVOID Type3InputBuffer;} DeviceIoControl;//// Nonsystem service parameters.//// Parameters for IRP_MN_MOUNT_VOLUME //struct {PVOID DoNotUse1;PDEVICE_OBJECT DeviceObject;} MountVolume;//// Parameters for IRP_MN_VERIFY_VOLUME //struct {PVOID DoNotUse1;PDEVICE_OBJECT DeviceObject;} VerifyVolume;//// Parameters for Scsi using IRP_MJ_INTERNAL_DEVICE_CONTROL //struct { struct _SCSI_REQUEST_BLOCK *Srb;} Scsi;//// Parameters for IRP_MN_QUERY_DEVICE_RELATIONS //struct {DEVICE_RELATION_TYPE Type;} QueryDeviceRelations;//// Parameters for IRP_MN_QUERY_INTERFACE //struct {CONST GUID *InterfaceType;USHORT Size;USHORT Version;PINTERFACE Interface;PVOID InterfaceSpecificData;} QueryInterface;//// Parameters for IRP_MN_QUERY_CAPABILITIES //struct {PDEVICE_CAPABILITIES Capabilities;} DeviceCapabilities;//// Parameters for IRP_MN_FILTER_RESOURCE_REQUIREMENTS //struct {PIO_RESOURCE_REQUIREMENTS_LIST IoResourceRequirementList;} FilterResourceRequirements;//// Parameters for IRP_MN_READ_CONFIG and IRP_MN_WRITE_CONFIG //struct {ULONG WhichSpace;PVOID Buffer;ULONG Offset;ULONG POINTER_ALIGNMENT Length;} ReadWriteConfig;//// Parameters for IRP_MN_SET_LOCK //struct {BOOLEAN Lock;} SetLock;//// Parameters for IRP_MN_QUERY_ID //struct {BUS_QUERY_ID_TYPE IdType;} QueryId;//// Parameters for IRP_MN_QUERY_DEVICE_TEXT //struct {DEVICE_TEXT_TYPE DeviceTextType;LCID POINTER_ALIGNMENT LocaleId;} QueryDeviceText;//// Parameters for IRP_MN_DEVICE_USAGE_NOTIFICATION //struct {BOOLEAN InPath;BOOLEAN Reserved[3];DEVICE_USAGE_NOTIFICATION_TYPE POINTER_ALIGNMENT Type;} UsageNotification;//// Parameters for IRP_MN_WAIT_WAKE //struct {SYSTEM_POWER_STATE PowerState;} WaitWake;//// Parameter for IRP_MN_POWER_SEQUENCE //struct {PPOWER_SEQUENCE PowerSequence;} PowerSequence;//// Parameters for IRP_MN_SET_POWER and IRP_MN_QUERY_POWER //struct {ULONG SystemContext;POWER_STATE_TYPE POINTER_ALIGNMENT Type;POWER_STATE POINTER_ALIGNMENT State;POWER_ACTION POINTER_ALIGNMENT ShutdownType;} Power;//// Parameters for IRP_MN_START_DEVICE //struct {PCM_RESOURCE_LIST AllocatedResources;PCM_RESOURCE_LIST AllocatedResourcesTranslated;} StartDevice;//// Parameters for WMI Minor IRPs //struct {ULONG_PTR ProviderId;PVOID DataPath;ULONG BufferSize;PVOID Buffer;} WMI;//// Others - driver-specific//struct {PVOID Argument1;PVOID Argument2;PVOID Argument3;PVOID Argument4;} Others;} Parameters;PDEVICE_OBJECT DeviceObject;PFILE_OBJECT FileObject;... } IO_STACK_LOCATION, *PIO_STACK_LOCATION; 1. IRP请求类型 UCHAR MajorFunction; UCHAR MinorFunction; 在每个驱动的入口函数DriverEntry中我们经常要做的是就是当前驱动的分发函数进行赋值。即上层应用会很多种不同的调用请求这些请求被windows以IRP_MJ_XX这样的主功能号进行了分类。例如下面的代码。 NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath) {...DriverObject-MajorFunction[IRP_MJ_CREATE] SfCreate;DriverObject-MajorFunction[IRP_MJ_CREATE_NAMED_PIPE] SfCreate;DriverObject-MajorFunction[IRP_MJ_CREATE_MAILSLOT] SfCreate;DriverObject-MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] SfFsControl;DriverObject-MajorFunction[IRP_MJ_CLEANUP] SfCleanupClose;DriverObject-MajorFunction[IRP_MJ_CLOSE] SfCleanupClose;... } 从面向接口编程的角度来理解windows已经在接口中实现了一个分发例程的原型并以数组的形式把接口(即函数地址)放了出来我们在写代码的时候如果想在本驱动中对指定的IRP类型进行处理就必须去对分发例程(就是这个数组)进行赋值并对我们提供的例程函数进行代码实现。 IRP Major Function Codes IRP_MJ_CREATE IRP_MJ_PNP IRP_MJ_POWER IRP_MJ_READ IRP_MJ_WRITE IRP_MJ_FLUSH_BUFFERS IRP_MJ_QUERY_INFORMATION IRP_MJ_SET_INFORMATION IRP_MJ_DEVICE_CONTROL IRP_MJ_INTERNAL_DEVICE_CONTROL IRP_MJ_SYSTEM_CONTROL IRP_MJ_CLEANUP IRP_MJ_CLOSE IRP_MJ_SHUTDOWN 除了主功能号之外还有子功能号这是在一些PnP manager(PnP类型操作的IRP), the power manager(电源管理), file system drivers(文件系统)中需要通过子功能号来进一步对IRP的操作类型进行区分。所以每个类型的IRP操作(PnP/Power/filesystem)的子功能号都是不一样的。我们以文件系统的子功能号为例子。 switch (irpSp-MinorFunction) { //磁盘卷挂载case IRP_MN_MOUNT_VOLUME: return SfFsControlMountVolume( DeviceObject, Irp ); //磁盘卷加载case IRP_MN_LOAD_FILE_SYSTEM: return SfFsControlLoadFileSystem( DeviceObject, Irp );//磁盘的请求case IRP_MN_USER_FS_REQUEST:{switch (irpSp-Parameters.FileSystemControl.FsControlCode) { case FSCTL_DISMOUNT_VOLUME:{..}}break;}} 总之这个功能号是用来对IRP请求的类型进行区分的它们只是一些代号而已。 2. UNION Parameters 接下来是一个很大的联合体我们仔细观察其实这个联合体还是很有规律的而且也很简单因为有规律的东西往往会相对简单。 里面包含了每种IRP请求所需要的参数可以参考MSDN上的解释。 http://msdn.microsoft.com/en-us/library/ff550659 3. PDEVICE_OBJECT  DeviceObject 指向这个IO_STACK_LOCATINO所对应的设备对象。可能是中间的过滤设备也可能是底层的真实设备。 4. PFILE_OBJECT  FileObject 指向一个这个IRP对应的文件对象这个文件对象是一个广义的概念在内核中磁盘/文件/目录都算是一种文件。 typedef struct _FILE_OBJECT {CSHORT Type;CSHORT Size;PDEVICE_OBJECT DeviceObject;PVPB Vpb;PVOID FsContext;PVOID FsContext2;PSECTION_OBJECT_POINTERS SectionObjectPointer;PVOID PrivateCacheMap;NTSTATUS FinalStatus;struct _FILE_OBJECT *RelatedFileObject;BOOLEAN LockOperation;BOOLEAN DeletePending;BOOLEAN ReadAccess;BOOLEAN WriteAccess;BOOLEAN DeleteAccess;BOOLEAN SharedRead;BOOLEAN SharedWrite;BOOLEAN SharedDelete;ULONG Flags;UNICODE_STRING FileName;LARGE_INTEGER CurrentByteOffset;ULONG Waiters;ULONG Busy;PVOID LastLock;KEVENT Lock;KEVENT Event;PIO_COMPLETION_CONTEXT CompletionContext;KSPIN_LOCK IrpListLock;LIST_ENTRY IrpList;PVOID FileObjectExtension; } FILE_OBJECT, *PFILE_OBJECT; 在文件系统的过滤驱动的编程中我们经常要使用到这个参数来获取这次操作所涉及到的文件对象以此得到这个文件的相关信息。由于本次笔记重点是数据结构的学习相关的使用场景打算在后续的学习笔记中进行应用。
http://www.hkea.cn/news/14364270/

相关文章:

  • 哪个网站做外链视频好网页设计师工作室
  • 模板网站seo模块建筑
  • 能在家做的兼职的网站本人有五金件外发加工
  • 江苏建站系统青岛网站备案
  • 网站开发中设置会员等级网站开发项目答辩主持词
  • 东莞市公司网站建设服务机构官网建设费用入什么科目
  • 天猫网站的建设建e网客厅效果图
  • 广州建设网站的公司简介wordpress登陆图标修改
  • 网站用花生壳nas做存储怎样用自己电脑做网站
  • 网站推广只能使用在线手段进行。网站建设0doit
  • 寻花问柳一家专注做男人喜爱的网站企业网站建设怎么做
  • 图片1600px做网站wordpress 函数手册
  • 百度网站入口网络营销工具分析
  • 大丰区城乡和住房建设局网站烟台seo
  • 昆明高端网站设计企业注册名字查询
  • 网站建设的基础是什么意思网站登录注册页面模板下载
  • lanyun网站开发wordpress老版编辑
  • 青岛网站关键词推广一个电商网站开发需要多久
  • 做网站卖装备wordpress wechat pay
  • 整站排名服务网站建设贴吧
  • 建设银行网站怎么登陆密码错误百度小程序给网站做链接
  • 柳州商城网站开发阿里云服务器是干什么用的
  • 从0开始做网站龙口网站建设哪家好
  • 公司网站设计案例成都网推公司
  • 做网站app要多钱网站建设和网站设计公司在哪里
  • 广州南沙区建设和交通局网站注册深圳公司代理
  • 网站seo检测工具wordpress评论回复邮件通知
  • 怎样做自己的摄影网站百度联盟怎么做自己的网站
  • 网站建设在线建站那些提卡网站是怎么做的
  • 网站内部优化有哪些内容网站建设与管理多选题