驱动程序的主要功能是负责处理I/O请求,其中大部分I/O请求是在派遣函数中处理的。用户模式下所有对驱动程序的I/O请求,全部由操作系统转化为一个叫做IRP的数据结构,不同的IRP数据会被“派遣”到不同的派遣函数中。
- IRP与派遣函数
IRP的全称是输入输出请求包。
其部分结构如下:
1 typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _IRP { 2 CSHORT Type; 3 USHORT Size; 4 5 6 // 7 // Define the common fields used to control the IRP. 8 // 9 10 // 11 // Define a pointer to the Memory Descriptor List (MDL) for this I/O 12 // request. This field is only used if the I/O is "direct I/O". 13 // 14 15 PMDL MdlAddress; 16 17 // 18 // Flags word - used to remember various flags. 19 // 20 21 ULONG Flags; 22 23 // 24 // The following union is used for one of three purposes: 25 // 26 // 1. This IRP is an associated IRP. The field is a pointer to a master 27 // IRP. 28 // 29 // 2. This is the master IRP. The field is the count of the number of 30 // IRPs which must complete (associated IRPs) before the master can 31 // complete. 32 // 33 // 3. This operation is being buffered and the field is the address of 34 // the system space buffer. 35 // 36 37 union { 38 struct _IRP *MasterIrp; 39 __volatile LONG IrpCount; 40 PVOID SystemBuffer; 41 } AssociatedIrp; 42 43 // 44 // Thread list entry - allows queueing the IRP to the thread pending I/O 45 // request packet list. 46 // 47 48 LIST_ENTRY ThreadListEntry; 49 50 // 51 // I/O status - final status of operation. 52 // 53 54 IO_STATUS_BLOCK IoStatus; 55 56 // 57 // Requestor mode - mode of the original requestor of this operation. 58 // 59 60 KPROCESSOR_MODE RequestorMode; 61 62 // 63 // Pending returned - TRUE if pending was initially returned as the 64 // status for this packet. 65 // 66 67 BOOLEAN PendingReturned; 68 69 // 70 // Stack state information. 71 // 72 73 CHAR StackCount; 74 CHAR CurrentLocation; 75 76 // 77 // Cancel - packet has been canceled. 78 // 79 80 BOOLEAN Cancel; 81 82 // 83 // Cancel Irql - Irql at which the cancel spinlock was acquired. 84 // 85 86 KIRQL CancelIrql; 87 88 // 89 // ApcEnvironment - Used to save the APC environment at the time that the 90 // packet was initialized. 91 // 92 93 CCHAR ApcEnvironment; 94 95 // 96 // Allocation control flags. 97 // 98 99 UCHAR AllocationFlags; 100 101 // 102 // User parameters. 103 // 104 105 PIO_STATUS_BLOCK UserIosb; 106 PKEVENT UserEvent; 107 union { 108 struct { 109 union { 110 PIO_APC_ROUTINE UserApcRoutine; 111 PVOID IssuingProcess; 112 }; 113 PVOID UserApcContext; 114 } AsynchronousParameters; 115 LARGE_INTEGER AllocationSize; 116 } Overlay; 117 118 // 119 // CancelRoutine - Used to contain the address of a cancel routine supplied 120 // by a device driver when the IRP is in a cancelable state. 121 // 122 123 __volatile PDRIVER_CANCEL CancelRoutine; 124 125 // 126 // Note that the UserBuffer parameter is outside of the stack so that I/O 127 // completion can copy data back into the user's address space without 128 // having to know exactly which service was being invoked. The length 129 // of the copy is stored in the second half of the I/O status block. If 130 // the UserBuffer field is NULL, then no copy is performed. 131 // 132 133 PVOID UserBuffer; 134 135 // 136 // Kernel structures 137 // 138 // The following section contains kernel structures which the IRP needs 139 // in order to place various work information in kernel controller system 140 // queues. Because the size and alignment cannot be controlled, they are 141 // placed here at the end so they just hang off and do not affect the 142 // alignment of other fields in the IRP. 143 // 144 145 union { 146 147 struct { 148 149 union { 150 151 // 152 // DeviceQueueEntry - The device queue entry field is used to 153 // queue the IRP to the device driver device queue. 154 // 155 156 KDEVICE_QUEUE_ENTRY DeviceQueueEntry; 157 158 struct { 159 160 // 161 // The following are available to the driver to use in 162 // whatever manner is desired, while the driver owns the 163 // packet. 164 // 165 166 PVOID DriverContext[4]; 167 168 } ; 169 170 } ; 171 172 // 173 // Thread - pointer to caller's Thread Control Block. 174 // 175 176 PETHREAD Thread; 177 178 // 179 // Auxiliary buffer - pointer to any auxiliary buffer that is 180 // required to pass information to a driver that is not contained 181 // in a normal buffer. 182 // 183 184 PCHAR AuxiliaryBuffer; 185 186 // 187 // The following unnamed structure must be exactly identical 188 // to the unnamed structure used in the minipacket header used 189 // for completion queue entries. 190 // 191 192 struct { 193 194 // 195 // List entry - used to queue the packet to completion queue, among 196 // others. 197 // 198 199 LIST_ENTRY ListEntry; 200 201 union { 202 203 // 204 // Current stack location - contains a pointer to the current 205 // IO_STACK_LOCATION structure in the IRP stack. This field 206 // should never be directly accessed by drivers. They should 207 // use the standard functions. 208 // 209 210 struct _IO_STACK_LOCATION *CurrentStackLocation; 211 212 // 213 // Minipacket type. 214 // 215 216 ULONG PacketType; 217 }; 218 }; 219 220 // 221 // Original file object - pointer to the original file object 222 // that was used to open the file. This field is owned by the 223 // I/O system and should not be used by any other drivers. 224 // 225 226 PFILE_OBJECT OriginalFileObject; 227 228 } Overlay; 229 230 // 231 // APC - This APC control block is used for the special kernel APC as 232 // well as for the caller's APC, if one was specified in the original 233 // argument list. If so, then the APC is reused for the normal APC for 234 // whatever mode the caller was in and the "special" routine that is 235 // invoked before the APC gets control simply deallocates the IRP. 236 // 237 238 KAPC Apc; 239 240 // 241 // CompletionKey - This is the key that is used to distinguish 242 // individual I/O operations initiated on a single file handle. 243 // 244 245 PVOID CompletionKey; 246 247 } Tail; 248 249 } IRP;
用图表示数据结构如下:
对里边的部分成员进行说明。
MdlAddress(PMDL)域指向一个内存描述符表(MDL),该表描述了一个
与该请求关联的用户模式缓冲区。如果顶级设备对象的Flags域为DO_DIRECT_IO,则I/O管理器为IRP_MJ_READ或
IRP_MJ_WRITE请求创建这个MDL。如果一个IRP_MJ_DEVICE_CONTROL请求的控制代码指定METHOD_IN_DIRECT
或METHOD_OUT_DIRECT操作方式,则I/O管理器为该请求使用的输出缓冲区创建一个MDL。MDL本身用于描述用户模式虚拟缓冲区,但它同
时也含有该缓冲区锁定内存页的物理地址。为了访问用户模式缓冲区,驱动程序必须做一点额外工作。
Flags(ULONG)域包含一些对驱动程序只读的标志。但这些标志与WDM驱动程序无关。
AssociatedIrp(union)域是一个三指针联合。其中,与WDM驱动程序相关的指针是AssociatedIrp.SystemBuffer。
SystemBuffer指针指向一个数据缓冲区,该缓冲区位于内核模式的非分页内存中。对于IRP_MJ_READ和IRP_MJ_WRITE操作,如
果顶级设备指定DO_BUFFERED_IO标志,则I/O管理器就创建这个数据缓冲区。对于IRP_MJ_DEVICE_CONTROL操作,如果
I/O控制功能代码指出需要缓冲区(见第九章),则I/O管理器就创建这个数据缓冲区。I/O管理器把用户模式程序发送给驱动程序的数据复制到这个缓冲
区,这也是创建IRP过程的一部分。这些数据可以是与WriteFile调用有关的数据,或者是DeviceIoControl调
用中所谓的输入数据。对于读请求,设备驱动程序把读出的数据填到这个缓冲区,然后I/O管理器再把缓冲区的内容复制到用户模式缓冲区。对于指定了
METHOD_BUFFERED的I/O控制操作,驱动程序把所谓的输出数据放到这个缓冲区,然后I/O管理器再把数据复制到用户模式的输出缓冲区。
IoStatus(IO_STATUS_BLOCK)是一个仅包含两个域的结构,驱动程序在最终完成请求时设置这个结构。IoStatus.Status域将收到一个NTSTATUS代码,而IoStatus.Information的
类型为ULONG_PTR,它将收到一个信息值,该信息值的确切含义要取决于具体的IRP类型和请求完成的状态。Information域的一个公认用法
是用于保存数据传输操作,如IRP_MJ_READ,的流量总计。某些PnP请求把这个域作为指向另外一个结构的指针,这个结构通常包含查询请求的结果。
RequestorMode将等于一个枚举常量UserMode或KernelMode,指定原始I/O请求的来源。驱动程序有时需要查看这个值来决定是否要信任某些参数。
PendingReturned(BOOLEAN)如果为TRUE,则表明处理该IRP的最低级派遣例程返回了STATUS_PENDING。完成例程通过参考该域来避免自己与派遣例程间的潜在竞争。
Cancel(BOOLEAN)如果为TRUE,则表明IoCancelIrp已被调用,该函数用于取消这个请求。如果为FALSE,则表明没有调用IoCancelIrp函数。取消IRP是一个相对复杂的主题,我将在本章的最后详细描述它。
CancelIrql(KIRQL)是一个IRQL值,表明那个专用的取消自旋锁是在这个IRQL上获取的。当你在取消例程中释放自旋锁时应参考这个域。
CancelRoutine(PDRIVER_CANCEL)是驱动程序取消例程的地址。你应该使用IoSetCancelRoutine函数设置这个域而不是直接修改该域。
UserBuffer(PVOID)
对于METHOD_NEITHER方式的IRP_MJ_DEVICE_CONTROL请求,该域包含输出缓冲区的用户模式虚拟地址。该域还用于保存读写请
求缓冲区的用户模式虚拟地址,但指定了DO_BUFFERED_IO或DO_DIRECT_IO标志的驱动程序,其读写例程通常不需要访问这个域。当处理
一个METHOD_NEITHER控制操作时,驱动程序能用这个地址创建自己的MDL。
Tail.Overlay是Tail联合中的一种结构,它含有几个对WDM驱动程序有潜在用途的成员。图5-2是Tail联合的组成图。在这个图中,以水平方向从左到右是这个联合的三个可选成员,在垂直方向是每个结构的成员描述。Tail.Overlay.DeviceQueueEntry(KDEVICE_QUEUE_ENTRY)和Tail.Overlay.DriverContext(PVOID[4])是Tail.Overlayare内一个未命名联合的两个可选成员(只能出现一个)。I/O管理器把DeviceQueueEntry作为设备标准请求队列中的连接域。当IRP还没有进入某个队列时,如果你拥有这个IRP你可以使用这个域,你可以任意使用DriverContext中的四个指针。Tail.Overlay.ListEntry(LIST_ENTRY)仅能作为你自己实现的私有队列的连接域。
CurrentLocation(CHAR)和Tail.Overlay.CurrentStackLocation(PIO_STACK_LOCATION)没有公开为驱动程序使用,因为你完全可以使用象IoGetCurrentIrpStackLocation这样的函数获取这些信息。但意识到CurrentLocation就是当前I/O堆栈单元的索引以及CurrentStackLocation就是指向它的指针,会对驱动程序调试有一些帮助。
在DriverEntry中注册派遣函数的代码如下:
对派遣函数做简单的处理
这个派遣函数仅仅是设置了IRP的完成状态,并通过IoCompleteReques结束请求。
以ReadFile为例,其内部操作大概是这样的:
(1)ReadFile调用ntdll中的NtReadFile。其中ReadFile是Win32 API,而NtReadFile是Native API。
(2)ntdll中的NtReadFile进入到内核模式,调用系统服务中的NtReadFile函数。
(3)系统服务函数NtReadFile创建IRP_MJ_READ类型的IRP,然后将这个IRP发送到某个驱动程序的派遣函数中。NtReadFile去等待一个事件,进入“睡眠”(阻塞/挂起)状态。
(4)在派遣函数中一般会将IRP结束,这是通过IoCompleteReques函数实现的。IoCompleteRequest会设置刚才等待的时间,“睡眠”的线程被恢复运行。
通过设备链接打开设备:
在R0层编写驱动程序的设备所对应的符号链接是:
而当你在R3层编写程序要打开某个设备对象时,设备链接就要改写为:
假设我们已经加载了这个驱动程序,然后运行一个R3应用程序,代码如下:
运行结果如下:
要编写一个更通用的派遣函数,这里不得不提及一个更重要的数据结构——IO_STACK_LOCATION,即I/O堆栈。
驱动对象会创建一个个的设备对象,并将这些设备对象“叠”成一个垂直的结构。这种垂直的结构很像栈,因此被称为“设备栈”。IRP会被操作系统发送到设备栈的顶层,如果顶层的设备对象的派遣函数结束了IRP的请求,这次I/O请求就结束。如果没有将IRP的请求结束,那么操作系统将IRP转发到设备栈的下一层设备处理。如果这个设备的派遣函数依然不能结束IRP请求,则会继续向下一层设备转发。因此,一个IRP可能会被转发多次,为了记录IRP在每层设备中的操作,IRP会有一个IO_STACK_LOCATION数组。对于本层设备对应的IO_STACK_LOCATION可以通过IoGetCurrentIrpStackLocation函数得到。
IO_STACK_LOCATION的结构如下:
1 typedef struct _IO_STACK_LOCATION { 2 UCHAR MajorFunction; 3 UCHAR MinorFunction; 4 UCHAR Flags; 5 UCHAR Control; 6 // 7 // The following user parameters are based on the service that is being 8 // invoked. Drivers and file systems can determine which set to use based 9 // on the above major and minor function codes. 10 // 11 union { 12 // 13 // System service parameters for: NtCreateFile 14 // 15 struct { 16 PIO_SECURITY_CONTEXT SecurityContext; 17 ULONG Options; 18 USHORT POINTER_ALIGNMENT FileAttributes; 19 USHORT ShareAccess; 20 ULONG POINTER_ALIGNMENT EaLength; 21 } Create; 22 23 // 24 // System service parameters for: NtReadFile 25 // 26 struct { 27 ULONG Length; 28 ULONG POINTER_ALIGNMENT Key; 29 LARGE_INTEGER ByteOffset; 30 } Read; 31 // 32 // System service parameters for: NtWriteFile 33 // 34 struct { 35 ULONG Length; 36 ULONG POINTER_ALIGNMENT Key; 37 LARGE_INTEGER ByteOffset; 38 } Write; 39 40 // 41 // System service parameters for: NtQueryInformationFile 42 // 43 struct { 44 ULONG Length; 45 FILE_INFORMATION_CLASS POINTER_ALIGNMENT FileInformationClass; 46 } QueryFile; 47 // 48 // System service parameters for: NtSetInformationFile 49 // 50 struct { 51 ULONG Length; 52 FILE_INFORMATION_CLASS POINTER_ALIGNMENT FileInformationClass; 53 PFILE_OBJECT FileObject; 54 union { 55 struct { 56 BOOLEAN ReplaceIfExists; 57 BOOLEAN AdvanceOnly; 58 }; 59 ULONG ClusterCount; 60 HANDLE DeleteHandle; 61 }; 62 } SetFile; 63 64 // 65 // System service parameters for: NtQueryVolumeInformationFile 66 // 67 struct { 68 ULONG Length; 69 FS_INFORMATION_CLASS POINTER_ALIGNMENT FsInformationClass; 70 } QueryVolume; 71 72 // 73 // System service parameters for: NtFlushBuffersFile 74 // 75 // No extra user-supplied parameters. 76 // 77 78 // 79 // System service parameters for: NtDeviceIoControlFile 80 // 81 // Note that the user's output buffer is stored in the UserBuffer field 82 // and the user's input buffer is stored in the SystemBuffer field. 83 // 84 struct { 85 ULONG OutputBufferLength; 86 ULONG POINTER_ALIGNMENT InputBufferLength; 87 ULONG POINTER_ALIGNMENT IoControlCode; 88 PVOID Type3InputBuffer; 89 } DeviceIoControl; 90 // end_wdm 91 // 92 // System service parameters for: NtQuerySecurityObject 93 // 94 struct { 95 SECURITY_INFORMATION SecurityInformation; 96 ULONG POINTER_ALIGNMENT Length; 97 } QuerySecurity; 98 // 99 // System service parameters for: NtSetSecurityObject 100 // 101 struct { 102 SECURITY_INFORMATION SecurityInformation; 103 PSECURITY_DESCRIPTOR SecurityDescriptor; 104 } SetSecurity; 105 // begin_wdm 106 // 107 // Non-system service parameters. 108 // 109 // Parameters for MountVolume 110 // 111 struct { 112 PVPB Vpb; 113 PDEVICE_OBJECT DeviceObject; 114 } MountVolume; 115 // 116 // Parameters for VerifyVolume 117 // 118 struct { 119 PVPB Vpb; 120 PDEVICE_OBJECT DeviceObject; 121 } VerifyVolume; 122 // 123 // Parameters for Scsi with internal device contorl. 124 // 125 struct { 126 struct _SCSI_REQUEST_BLOCK *Srb; 127 } Scsi; 128 129 // 130 // Parameters for IRP_MN_QUERY_DEVICE_RELATIONS 131 // 132 struct { 133 DEVICE_RELATION_TYPE Type; 134 } QueryDeviceRelations; 135 // 136 // Parameters for IRP_MN_QUERY_INTERFACE 137 // 138 struct { 139 CONST GUID *InterfaceType; 140 USHORT Size; 141 USHORT Version; 142 PINTERFACE Interface; 143 PVOID InterfaceSpecificData; 144 } QueryInterface; 145 // end_ntifs 146 // 147 // Parameters for IRP_MN_QUERY_CAPABILITIES 148 // 149 struct { 150 PDEVICE_CAPABILITIES Capabilities; 151 } DeviceCapabilities; 152 // 153 // Parameters for IRP_MN_FILTER_RESOURCE_REQUIREMENTS 154 // 155 struct { 156 PIO_RESOURCE_REQUIREMENTS_LIST IoResourceRequirementList; 157 } FilterResourceRequirements; 158 // 159 // Parameters for IRP_MN_READ_CONFIG and IRP_MN_WRITE_CONFIG 160 // 161 struct { 162 ULONG WhichSpace; 163 PVOID Buffer; 164 ULONG Offset; 165 ULONG POINTER_ALIGNMENT Length; 166 } ReadWriteConfig; 167 // 168 // Parameters for IRP_MN_SET_LOCK 169 // 170 struct { 171 BOOLEAN Lock; 172 } SetLock; 173 // 174 // Parameters for IRP_MN_QUERY_ID 175 // 176 struct { 177 BUS_QUERY_ID_TYPE IdType; 178 } QueryId; 179 // 180 // Parameters for IRP_MN_QUERY_DEVICE_TEXT 181 // 182 struct { 183 DEVICE_TEXT_TYPE DeviceTextType; 184 LCID POINTER_ALIGNMENT LocaleId; 185 } QueryDeviceText; 186 // 187 // Parameters for IRP_MN_DEVICE_USAGE_NOTIFICATION 188 // 189 struct { 190 BOOLEAN InPath; 191 BOOLEAN Reserved[3]; 192 DEVICE_USAGE_NOTIFICATION_TYPE POINTER_ALIGNMENT Type; 193 } UsageNotification; 194 // 195 // Parameters for IRP_MN_WAIT_WAKE 196 // 197 struct { 198 SYSTEM_POWER_STATE PowerState; 199 } WaitWake; 200 // 201 // Parameter for IRP_MN_POWER_SEQUENCE 202 // 203 struct { 204 PPOWER_SEQUENCE PowerSequence; 205 } PowerSequence; 206 // 207 // Parameters for IRP_MN_SET_POWER and IRP_MN_QUERY_POWER 208 // 209 struct { 210 ULONG SystemContext; 211 POWER_STATE_TYPE POINTER_ALIGNMENT Type; 212 POWER_STATE POINTER_ALIGNMENT State; 213 POWER_ACTION POINTER_ALIGNMENT ShutdownType; 214 } Power; 215 // 216 // Parameters for StartDevice 217 // 218 struct { 219 PCM_RESOURCE_LIST AllocatedResources; 220 PCM_RESOURCE_LIST AllocatedResourcesTranslated; 221 } StartDevice; 222 // begin_ntifs 223 // 224 // Parameters for Cleanup 225 // 226 // No extra parameters supplied 227 // 228 // 229 // WMI Irps 230 // 231 struct { 232 ULONG_PTR ProviderId; 233 PVOID DataPath; 234 ULONG BufferSize; 235 PVOID Buffer; 236 } WMI; 237 // 238 // Others - driver-specific 239 // 240 struct { 241 PVOID Argument1; 242 PVOID Argument2; 243 PVOID Argument3; 244 PVOID Argument4; 245 } Others; 246 } Parameters; 247 // 248 // Save a pointer to this device driver's device object for this request 249 // so it can be passed to the completion routine if needed. 250 // 251 PDEVICE_OBJECT DeviceObject; 252 // 253 // The following location contains a pointer to the file object for this 254 // 255 PFILE_OBJECT FileObject; 256 // 257 // The following routine is invoked depending on the flags in the above 258 // flags field. 259 // 260 PIO_COMPLETION_ROUTINE CompletionRoutine; 261 // 262 // The following is used to store the address of the context parameter 263 // that should be passed to the CompletionRoutine. 264 // 265 PVOID Context; 266 } IO_STACK_LOCATION, *PIO_STACK_LOCATION;
任何内核模式程序在创建一个IRP时,同时还创建了一个与之关联的 IO_STACK_LOCATION结构数组:数组中的每个堆栈单元都对应一个将处理该IRP的驱动程序,而每一个I/O堆栈单元(它是一个数组)对应着这个驱动所关联的多个设备对象(数组中的每个元素对应着一个设备对象):
IO_STACK_LOCATION结构简化表示如下:
其成员介绍如下:
MajorFunction(UCHAR)是该IRP的主功能码。这个代码应该为类似 IRP_MJ_READ一样的值,并与驱动程序对象中MajorFunction表的某个派遣函数指针相对应。如果该代码存在于某个特殊驱动程序的I/O堆栈单元中,它有可能一开始是,例如IRP_MJ_READ,而后被驱动程序转换成其它代码,并沿着驱动程序堆栈发送到低层驱动程序。我将在第十一章(USB总线)中举一个这样的例子,USB驱动程序把标准的读或写请求转换成内部控制操作,以便向USB总线驱动程序提交请求。
MinorFunction(UCHAR)是该IRP的副功能码。它进一步指出该IRP属于哪个主功能类。例如,IRP_MJ_PNP请求就有约一打的副功能码,如IRP_MN_START_DEVICE、IRP_MN_REMOVE_DEVICE,等等。
Parameters(union)是几个子结构的联合,每个请求类型都有自己专用的参数,而每个子结构就是一种参数。这些子结构包括Create(IRP_MJ_CREATE请求)、Read(IRP_MJ_READ请求)、StartDevice(IRP_MJ_PNP的IRP_MN_START_DEVICE子类型),等等。
DeviceObject(PDEVICE_OBJECT)是与该堆栈单元对应的设备对象的地址。该域由IoCallDriver函数负责填写。
FileObject(PFILE_OBJECT)是内核文件对象的地址,IRP的目标就是这个文件对象。驱动程序通常在处理清除请求(IRP_MJ_CLEANUP)时使用FileObject指针,以区分队列中与该文件对象无关的IRP。
CompletionRoutine(PIO_COMPLETION_ROUTINE)是一个I/O完成例程的地址,该地址是由与这个堆栈单元对应的驱动程序的更上一层驱动程序设置的。你绝对不要直接设置这个域,应该调用IoSetCompletionRoutine函数,该函数知道如何参考下一层驱动程序的堆栈单元。设备堆栈的最低一级驱动程序并不需要完成例程,因为它们必须直接完成请求。然而,请求的发起者有时确实需要一个完成例程,但通常没有自己的堆栈单元。这就是为什么每一级驱动程序都使用下一级驱动程序的堆栈单元保存自己完成例程指针的原因。
Context(PVOID)是一个任意的与上下文相关的值,将作为参数传递给完成例程。你绝对不要直接设置该域;它由IoSetCompletionRoutine函数自动设置,其值来自该函数的某个参数。
IRP结合IO_STACK_LOCATION的结构如下:
举个演示派遣函数获取IRP的类型,代码如下:
1 NTSTATUS HelloDDKDispatchRoutine(PDEVICE_OBJECT pDevObj, PIRP pIrp){ 2 UNREFERENCED_PARAMETER(pDevObj); 3 DbgPrint("HelloDDKDispatchRoutine!\n"); 4 PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp); 5 static char* irpname[] = { 6 "IRP_MJ_CREATE", 7 "IRP_MJ_CREATE_NAMED_PIPE", 8 "IRP_MJ_CLOSE", 9 "IRP_MJ_READ", 10 "IRP_MJ_WRITE", 11 "IRP_MJ_QUERY_INFORMATION" 12 }; 13 UCHAR type = stack->MajorFunction; 14 if (type >= ARRAYSIZE(irpname)){ 15 DbgPrint("Unknown IRP!"); 16 } 17 else{ 18 DbgPrint("IRP:%s", irpname[type]); 19 } 20 NTSTATUS status = STATUS_SUCCESS; 21 pIrp->IoStatus.Status = status; 22 pIrp->IoStatus.Information = 0; 23 IoCompleteRequest(pIrp, IO_NO_INCREMENT); 24 return status; 25 }
创建了设备对象之后,再用之前的R3层应用去打开这个设备对象,就会输出