iniwf

风是温柔的,雨是伤心的,云是快乐的,月是多情的,爱是迷失的,恋是醉人的,情是难忘的,天是长久的,地是永恒的

驱动开发之五 --- TDI之十一 【译文】

转自http://hi.baidu.com/combojiang/blog/item/d556aacf00260638f9dc6136.html

处理和完成

在这里你简单的按步处理请求和完成它。如果你没有返回STATUS_PENDING,那是最好的。在前面多数的例子中,我们就是这样处理所有的驱动请求的。我们处理他们,当我们做完时,我们简单的调用IoCompleteRequest。

创建IRP


在前面的文章中,对于如何创建和发送IRP有简短的描述。我们会再详细的回顾一下那些步骤。我们也会学习到不同的创建IRP的API.。

步骤1:创建IRP

有很少的api可以用于创建IRP。正如我们已经了解的。我们必须了解这些API之间的不同。

IRP分为异步和同步两种。 如果你使用IoAllocateIrp或者IoBuildAsynchronousFsdRequest创建irp,那么你创建的是一个异步的irp. 这意味着你应该设置一个完成例程,并且当IRP完成时,你需要调用IoFreeIrp。你负责管理这些IRP,并且你需要在适当的时候处理他们。


如果你使用IoBuildDeviceIoControlRequest或者IoBuildSynchronousFsdRequest来创建IRP。那么你创建的是一个同步的IRP. 记住,TdiBuildInternalDeviceControlIrp是一个宏,它创建的是同步的irp. 这些irp是由I
/O管理器来拥有和管理的。不要释放他们。这些irp一定要使用IoCompleteRequest来完成。如果你传递这样的IRP给IoCallDriver,你不需要完成它,下层的驱动会替你完成它。如果你使用完成例程中途拦截,那么你需要在你用完之后调用IoCompleteRequest来完成。


需要注意,在你考虑创建一个IRP之前,需要确认你已经了解了你的代码将会在怎样的IRQL下被调用。使用IoAllocateIrp的好处是,它可以用用在DISPATCH_LEVEL级别。而IoBuildDeviceIoControlRequest却不能。


步骤2: 设置IRP参数

就拿TDI例子来讲,它很简单。宏TdiBuildSend展示了怎样处理它。我们使用IoGetNextIrpStackLocation,然后设置参数。我们也可以设置我们在处理IRP中需要的MDL和其他属性。

步骤3:发送到驱动栈


这个很简单,我们前面一遍遍的用过。我们使用IoCallDriver来将IRP沿着栈向下传递。


步骤4:等待和清除


如果你做的驱动返回除
"STATUS_PENDING“之外的任何状态,如果你创建了一个异步IRP。你要么在完成例程中释放IRP,要么设置它返回更多的处理并且使用IoFreeIrp释放它。


如果你创建一个同步IRP, 要么让I
/O管理器处理它,要么你设置完成例程返回更多的处理。


如果返回状态是STATUS_PENDING
",你的选择会比较少。 你或者在这里等待IRP,或者你离开并异步完成它。这些都依赖于你的架构。如果你异步创建了IRP,在你设置的完成例程中,你必须检查这个IRP是否被设置成"Pending",然后设置你的事件。这也是为什么你不需要在事件上等待除非返回STATUS_PENDING。想象下如果所有的调用都等待这个事件,会多么慢?



如果IRP是同步创建的,I
/O管理器会为你设置事件。你不需要做任何事情,除非你设置了完成例程,希望从中对返回更多的处理。


非分页驱动代码


如果你还记得,在第一节中,我们学过使用#pragma 把我们的驱动代码放到不同的节中。有INIT节(这个节加载后会被释放),还有分页的节(把代码放进了可分页内存区域)。代码怎样获取自旋锁呢? 如果代码必须要在非分页内存加载,我们怎么做呢?我们只需要不用#pragma指定就行了。默认的驱动加载就是在非分页内存。当它不需要在非分页内存中时,我们使用#pragma是为了强制让他从系统的物理内存中转移出来。


如果你看下面这些代码,你就会注意到一些#pragma语句被注释了。这些函数当他们使用自旋锁并且运行在大于APC_LEVEL的层级时,他们需要运行在非分页区。


/* #pragma alloc_text(PAGE, HandleIrp_FreeIrpListWithCleanUp) */ 

/* #pragma alloc_text(PAGE, HandleIrp_AddIrp)                 */ 

/* #pragma alloc_text(PAGE, HandleIrp_RemoveNextIrp)          */ 

#pragma alloc_text(PAGE, HandleIrp_CreateIrpList) 

#pragma alloc_text(PAGE, HandleIrp_FreeIrpList) 

/* #pragma alloc_text(PAGE, HandleIrp_PerformCancel)          */ 

完成例程怎样工作的?


每一个设备的STACK LOCATION或许都有一个相关联的完成例程。目前被调用的完成例程是上层驱动的,而不是当前驱动的。当前驱动完成irp后,他自己知道什么时候完成的。所以当驱动完成完成后,就会在当前栈域中查看完成例程,如果存在就调用。在调用之前,当前的IO_STACK_LOCATION就会移到指向上层驱动的域。这是很重要的,稍候我们将会看到。如果驱动没有完成IRP,他就会调用IoMarkIrpPending向上传递未决的状态。这是因为驱动返回STATUS_PENDING,他一定要标记IRP为未决的。上层驱动如果不是返回与底层驱动同样的状态,就不需要标记IRP为未决状态。或许他会中途拦截STATUS_PENDING,并且等待完成。然后它会停止IRP的完成,当返回状态不是STATUS_PENDING.时,再完成它。


现在如果你的驱动创建IRP,你不用被迫把IRP标记为未决的。你知道这是为什么?因为你没有IO_STACK_LOCATION,你没有在设备栈上。实际上如果你这么做了,你会破毁内存。


注意这个例子代码会展示一个的完成例程,它会调用IoMarkIrpPending,即便是完成例程中创建的IRP。这也是不应该发生的。实际上,如果你看了真实的代码,如果创建了同步的IRP,通常完成例程是不存在的,或者存在而仅仅返回更多处理的状态。


我在TDI客户端实现了一个完成例程,我们这里创建了同步的IRP。你可以象下面这样察看调试信息。


kd
> kb 

ChildEBP RetAddr Args to Child               

fac8ba90 804e4433 
00000000 80d0c9b8 00000000 

    netdrv
!TdiFuncs_CompleteIrp [.\tdifuncs.c @ 829

fac8bac0 fbb20c54 80d1d678 80d0c9b8 
00000000 nt!IopfCompleteRequest+0xa0 

fac8bad8 fbb2bd9b 80d0c9b8 
00000000 00000000 tcpip!TCPDataRequestComplete+0xa4 

fac8bb00 fbb2bd38 80d0c9b8 80d0ca28 80d1d678 tcpip
!TCPDisassociateAddress+0x4b 

fac8bb14 804e0e0d 80d1d678 80d0c9b8 c000009a 

    tcpip
!TCPDispatchInternalDeviceControl+0x9b 

fac8bb24 fc785d65 ffaaa3b0 80db4774 
00000000 nt!IofCallDriver+0x3f 

fac8bb50 fc785707 ff9cdc20 80db4774 fc786099 

    netdrv
!TdiFuncs_DisAssociateTransportAndConnection+0x94 [.\tdifuncs.c @ 772

fac8bb5c fc786099 80db4774 ffaaa340 ff7d1d98 

  netdrv
!TdiFuncs_FreeHandles+0xd [.\tdifuncs.c @ 112

fac8bb74 804e0e0d 80d33df0 ffaaa340 ffaaa350 

    netdrv
!TdiExample_CleanUp+0x6e [.\functions.c @ 459

fac8bb84 80578ce9 
00000000 80cda980 00000000 nt!IofCallDriver+0x3f 

fac8bbbc 8057337c 00cda998 
00000000 80cda980 nt!IopDeleteFile+0x138 

fac8bbd8 804e4499 80cda998 
00000000 000007dc nt!ObpRemoveObjectRoutine+0xde 

fac8bbf4 8057681a ffb3e6d0 000007dc e1116fb8 nt
!ObfDereferenceObject+0x4b 

fac8bc0c 
80591749 e176a118 80cda998 000007dc nt!ObpCloseHandleTableEntry+0x137 

fac8bc24 
80591558 e1116fb8 000007dc fac8bc60 nt!ObpCloseHandleProcedure+0x1b 

fac8bc40 805916f5 e176a118 8059172e fac8bc60 nt
!ExSweepHandleTable+0x26 

fac8bc68 8057cfbe ffb3e601 ff7eada0 c000013a nt
!ObKillProcess+0x64 

fac8bcf0 80590e70 c000013a ffa25c98 804ee93d nt
!PspExitThread+0x5d9 

fac8bcfc 804ee93d ffa25c98 fac8bd48 fac8bd3c nt
!PsExitSpecialApc+0x19 

fac8bd4c 804e7af7 
00000001 00000000 fac8bd64 nt!KiDeliverApc+0x1c3 

kd
> dds esp 

fac8ba94 804e4433 nt
!IopfCompleteRequest+0xa0 

fac8ba98 
00000000 ; This is the PDEVICE_OBJECT, it's NULL!! 

fac8ba9c 80d0c9b8 ; This 
is IRP 

fac8baa0 
00000000 ; This is our context (NULL) 

kd
> !irp 80d0c9b8 

Irp 
is active with 1 stacks 2 is current (= 0x80d0ca4c

No Mdl Thread ff7eada0: Irp 
is completed. Pending has been returned 

     cmd flg cl Device   File     Completion
-Context 

[ f, 
0]   0 0 80d1d678 00000000 fc786579-00000000     

           \Driver\Tcpip    netdrv
!TdiFuncs_CompleteIrp 

            Args: 
00000000 00000000 00000000 00000000


你看到现在我们在IO_STACK_LOCATION #2位置,这个是不存在的。所以实际上这个IRP从一个不存在的高的IO_STACK_LOCATION位置开始。是否你还记得,我们需要调用IoGetNextIrpStackLocation来设置参数。这就是说,如果我们在这里调用IoMarkIrpPending,实际上我们访问的不是我们应该访问的内存。因为IoMarkIrpPending实际上是在IO_STACK_LOCATION上设置。无独有偶,设备对象也是NULL。这是因为我们的栈域不存在。既然我们并不是设备栈中的一部分,所以我们不会有关联的设备对象。


什么是STATUS_PENDING?

如果我还没有使你困惑,我们就继续谈谈STATUS_PENDING和IoMarkIrpPending。 他们的用途是什么
? 用途就是我们可以处理异步IRP并且让上层驱动和I/O管理器了解。 第一部分STATUS_PENDING返回是最佳的,所以如果我们想等,我们仅仅需要为异步操作返回它。第二部分是IoMarkIrpPending实际上是传播IRP上的未决返回状态。这种方式我们不需要总是调用KeSetEvent,只需要做的就是在返回STATUS_PENDING的情况下处理。


另外的用途就是中间层的驱动可以改变STATUS_PENDING状态为STATUS_SUCCESS ,不需要自始至终地沿着驱动栈传递全部未决的状态。



重叠I
/O


STATUS_PENDING体系是如何实现重叠I
/O的本质。本篇例子使用ReadFileEx和WriteFileEx,并不是说ReadFile和WriteFile不能用在这里。他们也可以。如果你看CreateFile这个API,我添加了一个使能重叠I/O的标志。如果你去掉这个标志,I/O管理器就会阻塞在STATUS_PENDING而不是返回到应用。这需要设置一个事件,直到I/O完成。这是用户程序使用异步I/O的本质。


 

posted on 2009-03-23 22:11 iniwf 阅读(567) 评论(0)  编辑 收藏 引用 所属分类: 驱动


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


导航

统计

常用链接

留言簿(2)

随笔分类

随笔档案

收藏夹

IT技术

积分与排名

最新评论

阅读排行榜

评论排行榜