Windows驱动之IRP PENDING
文章目录
- Windows驱动之IRP PENDING
- 1. IRP的发起
- 2. IoMarkIrpPending
- 3. IoCompleteRequest
- 4. 总结
Windows驱动之IRP PENDING
我们知道Windows是一个异步操作系统,那么异步具体是怎么实现的呢?这就依赖设备驱动程序的实现。例如当我们应用程序发起IRP请求,IRP请求到达驱动的时候,这个时候设备并没有产生数据;作为异步操作,这个时候驱动立即返回给应用程序,应用程序可以继续其他操作,当设备准备好数据之后,再完成IRP,此时通知应用程序,IO请求完成。
当设备并没有真实完成数据,只是挂起IRP的时候,给上层返回的状态就叫PENDING状态,例如:
NTSTATUS DispatchXxx(...)
{//...IoMarkIrpPending(Irp);IoStartPacket(device, Irp, NULL, NULL);return STATUS_PENDING;
}
那么对于PENDING的IRP应用程序做了什么事情呢?本文来分析一下整个过程原理。
1. IRP的发起
对于操作系统响应应用层发起的请求,都是通过IopSynchronousServiceTail
调用IoCallDriver
向设备发送IRP响应的,这个函数的代码如下:
NTSTATUS
IopSynchronousServiceTail(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp,IN PFILE_OBJECT FileObject,IN BOOLEAN DeferredIoCompletion,IN KPROCESSOR_MODE RequestorMode,IN BOOLEAN SynchronousIo,IN TRANSFER_TYPE TransferType)
{//...status = IoCallDriver(DeviceObject, Irp);if (DeferredIoCompletion) {if (status != STATUS_PENDING) {PKNORMAL_ROUTINE normalRoutine;PVOID normalContext;KIRQL irql = PASSIVE_LEVEL; ASSERT(!Irp->PendingReturned);if (!SynchronousIo) {KeRaiseIrql(APC_LEVEL, &irql);}IopCompleteRequest(&Irp->Tail.Apc,&normalRoutine,&normalContext,(PVOID *)&FileObject,&normalContext);if (!SynchronousIo) {KeLowerIrql(irql);}}}if (SynchronousIo) {if (status == STATUS_PENDING) {status = KeWaitForSingleObject(&FileObject->Event,Executive,RequestorMode,(BOOLEAN)((FileObject->Flags & FO_ALERTABLE_IO) != 0),(PLARGE_INTEGER)NULL);if (status == STATUS_ALERTED || status == STATUS_USER_APC) {IopCancelAlertedRequest(&FileObject->Event, Irp);}status = FileObject->FinalStatus;}IopReleaseFileObjectLock(FileObject);}return status;
}
这里有两个重要的标记DeferredIoCompletion
和SynchronousIo
,下面详细解释一下这个标记的意义:
DeferredIoCompletion
这个是一个标记,有些IRP会设置标记IRP_DEFER_IO_COMPLETION
, 这个标记的一个用途是在调用IoCompleteRequest
的时候不会完成IRP,直接返回,提交给IopSynchronousServiceTail
来完成,例如:
VOID
FASTCALL
IopfCompleteRequest(IN PIRP Irp,IN CCHAR PriorityBoost)
{//...if (Irp->Flags & IRP_DEFER_IO_COMPLETION && !Irp->PendingReturned) {if ((Irp->IoStatus.Status == STATUS_REPARSE ) &&(Irp->IoStatus.Information == IO_REPARSE_TAG_MOUNT_POINT)) {Irp->Tail.Overlay.AuxiliaryBuffer = saveAuxiliaryPointer;}return;}//...
}
- 所以,类似
NtReadFile
和NtWriteFile
发起的IRP就会标记IRP_DEFER_IO_COMPLETION
这个标记,并且DeferredIoCompletion
这个参数设置成为TRUE,因此对于IRP_MJ_READ
和IRP_MJ_WRITE
如果返回一个非STATUS_PENDING
在这里就会判断失败(if (status != STATUS_PENDING)
),导致调用IopCompleteRequest
直接完成IRP。因此有很多的开发朋友在IRP_MJ_READ
和IRP_MJ_WRITE
分发函数中错误的返回了一个非STATUS_PENDING
,导致返回一个IRP就被释放而出现莫名其妙的问题的现象。 - 对于
if (SynchronousIo)
是判断是否是同步完成这个请求,如果是同步,那么如果返回STATUS_PENDING
的时候,就会引起Wait操作,等待底层IRP的完成。从这里也可以发现,其实上层都是默认底层是异步完成的IRP的,只是上层针对是否应用层是同步请求还是异步请求来判断是否等待IRP完成。
2. IoMarkIrpPending
对于驱动层,异步完成IRP的请求的流程是:
IoMarkIrpPending(Irp);
标记IRP异步。- 排队IRP,等待完成。
- 返回
STATUS_PENDING
, 这个比较重要(只能返回这个值)。
例如StartIO的异步函数如下:
NTSTATUS DispatchXxx(...)
{//...IoMarkIrpPending(Irp);IoStartPacket(device, Irp, NULL, NULL);return STATUS_PENDING;
}
从上面的分析,我们知道了,return STATUS_PENDING;
这条语句的重要性,但是IoMarkIrpPending
这个是干什么用途的呢?
#define IoMarkIrpPending( Irp ) ( \IoGetCurrentIrpStackLocation( (Irp) )->Control |= SL_PENDING_RETURNED )
这里只是在设备栈上面设置了一个控制标记SL_PENDING_RETURNED
这个标记的具体用途我们需要从IoCompleteRequest
中才能知道。
3. IoCompleteRequest
这个函数是用来完成一个IRP使用的,我们分析这个函数之前,先提出一个问题,就是如果我们在设备栈上面设置了完成例程的话,那么完成例程就应该是这样的:
NTSTATUS CompletionRoutine(PDEVICE_OBJECT device, PIRP Irp, PVOID context)
{if (Irp->PendingReturned)IoMarkIrpPending(Irp);//...//不返回 STATUS_MORE_PROCESSING_REQUIRED
}
也就是说所有不返回STATUS_MORE_PROCESSING_REQUIRED
状态的完成例程都需要判断Irp->PendingReturned
并调用IoMarkIrpPending(Irp);
设置IRP PENDING状态。
为什么需要这样呢?我们看下IoCompleteRequest
的流程就清楚了:
VOID
FASTCALL
IopfCompleteRequest(IN PIRP Irp,IN CCHAR PriorityBoost
)
{//...//从当前设备开开始遍历所有设备栈到顶层for (stackPointer = IoGetCurrentIrpStackLocation( Irp ),Irp->CurrentLocation++,Irp->Tail.Overlay.CurrentStackLocation++;Irp->CurrentLocation <= (CCHAR) (Irp->StackCount + 1);stackPointer++,Irp->CurrentLocation++,Irp->Tail.Overlay.CurrentStackLocation++) {//设置IRP是否是PENDING返回的Irp->PendingReturned = stackPointer->Control & SL_PENDING_RETURNED;if (!NT_SUCCESS(Irp->IoStatus.Status)) {if (Irp->IoStatus.Status != errorStatus) {errorStatus = Irp->IoStatus.Status;stackPointer->Control |= SL_ERROR_RETURNED;bottomSp->Parameters.Others.Argument4 = (PVOID)(ULONG_PTR)errorStatus;bottomSp->Control |= SL_ERROR_RETURNED; // Mark that there is status in this location}}if ( (NT_SUCCESS( Irp->IoStatus.Status ) &&stackPointer->Control & SL_INVOKE_ON_SUCCESS) ||(!NT_SUCCESS( Irp->IoStatus.Status ) &&stackPointer->Control & SL_INVOKE_ON_ERROR) ||(Irp->Cancel &&stackPointer->Control & SL_INVOKE_ON_CANCEL)) {//调用完成例程ZeroIrpStackLocation( stackPointer );if (Irp->CurrentLocation == (CCHAR) (Irp->StackCount + 1)) {deviceObject = NULL;}else {deviceObject = IoGetCurrentIrpStackLocation( Irp )->DeviceObject;}status = stackPointer->CompletionRoutine( deviceObject,Irp,stackPointer->Context );if (status == STATUS_MORE_PROCESSING_REQUIRED) {return;}} else {//如果不调用完成例程,那么IoMarkIrpPendingif (Irp->PendingReturned && Irp->CurrentLocation <= Irp->StackCount) {IoMarkIrpPending( Irp );}ZeroIrpStackLocation( stackPointer );}}//...
}
对于IopfCompleteRequest
这个函数,实在过于复杂,这里我们只看对于PENDING 状态设置的代码。
Irp->PendingReturned = stackPointer->Control & SL_PENDING_RETURNED;
: 我们需要根据设备栈中释放PENDING返回,来标记IRP的PendingReturned
状态。- 然后针对IRP的返回值,判断
SL_INVOKE_ON_SUCCESS
,SL_INVOKE_ON_ERROR
和SL_INVOKE_ON_CANCEL
状态,来调用完成例程。 - 如果没有调用完成例程,那么判断
Irp->PendingReturned
并设置IoMarkIrpPending( Irp );
状态,这样下次循环的时候Irp->PendingReturned = stackPointer->Control & SL_PENDING_RETURNED;
这个语句继续可以设置PendingReturned
了。
为什么需要这样操作呢?这个流程是比较复杂的,这里只是大概解释一下原因:
- 如果有中间的设备有一个设备是异步完成的,这个时候标记了
stackPointer->Control & SL_PENDING_RETURNED
,然后IRP的请求线程就返回了,这个时候如果是同步函数,那么就存在一个问题,发起IRP的IopSynchronousServiceTail
处于KeWaitForSingleObject(&FileObject->Event
等待状态,那么这个是在什么时候设置的呢?其实大概会有如下的判断:
if (irp->PendingReturned && fileObject) {(VOID) KeSetEvent( &fileObject->Event, 0, FALSE );
}
- 那么这里的
irp->PendingReturned
这个标记就非常重要了,因为我们顶层设备栈可能并没有异步完成IRP,只是简单的调用了IoCallDriver
传递到了底层设备,一旦底层返回了STATUS_PENDING
之后,那么就需要设置irp->PendingReturned
这个标记了(需要设置完成的EVENT)。因为在完成IRP的时候PendingReturned
是和IoMarkIrpPending
设置的状态息息相关的(Irp->PendingReturned = stackPointer->Control & SL_PENDING_RETURNED;
),所以一旦底层有设备栈设置了stackPointer->Control & SL_PENDING_RETURNED
,那么在上次设备栈一定也要调用IoMarkIrpPending
设置标记。在设备栈没有完成回调函数的时候,IoCompleteRequest
自己调用了了IoMarkIrpPending
(如上面IopfCompleteRequest
的分析),但是如果有完成回调函数,那么这个操作是没有的,所以这个操作交给了回调函数来完成。
因此我们的完成例程应该具有如下代码:
NTSTATUS CompletionRoutine(PDEVICE_OBJECT device, PIRP Irp, PVOID context)
{if (Irp->PendingReturned)IoMarkIrpPending(Irp);//...//不返回 STATUS_MORE_PROCESSING_REQUIRED
}
4. 总结
因此从上面的分析,我们大概可以知道IoMarkIrpPending
就是告诉系统,我异步返回了,上面可能在等待这个IRP的完成,请你在IRP完成的时候告诉我IRP完成了。
Windows驱动之IRP PENDING相关推荐
- Windows驱动之IRP结构
文章目录 Windows驱动之IRP结构 1. IRP 2. IO_STACK_LOCATION 3. IRP 和 IO_STACK_LOCATION 的交互 3.1 IoAllocateIrp 3. ...
- 【IRP】Windows 驱动之IRP
什么是IRP: I/O request packets,简称IRP.即输入输出请求包.它是WINDOWS内核中的一种非常重要的数据结构.上层应用程序与底层驱动程序通信时,应用程序会发出I/O请求.操作 ...
- windows 驱动开发基础(二)事件通知---关于irp处理,DPC,链表等
代码来源及参考:wdk 7,路径:C:\WinDDK\7600.16385.1\src\general\event 这个例子演示了两种关于当硬件事件发生时,驱动如何通知应用程序的方法.一种是基于eve ...
- 《Windows驱动开发技术详解》学习笔记
Abstract 如果推荐 Windows 驱动开发的入门书,我强烈推荐<Windows驱动开发技术详解>.但是由于成书的时间较早,该书中提到的很多工具和环境都已不可用或找不到,而本文 ...
- 键盘过滤驱动之IRP劫持
参考资料: [1] <Rootkits--Windows内核的安全与防护> [2] 让一切输入都难逃法眼(驱动级键盘过滤钩子) 本文主要介绍通过劫持IRP(IRP_MJ_READ)实现键盘 ...
- c语言windows驱动编程入门,Windows驱动开发技术详解 PDF扫描版[175MB]
Windows驱动开发技术详解由浅入深.循序渐进地介绍了windows驱动程序的开发方法与调试技巧.本书共分23章,内容涵盖了windows操作系统的基本原理.nt驱动程序与wdm驱动程序的构造.驱动 ...
- 楚狂人Windows驱动编程基础教程
版权声明 本书是免费电子书.作者保留一切权利.但在保证本书完整性(包括版权声明.前言.正文内容.后记.以及作者的信息),并不增删.改变其中任何文字内容的前提下,欢迎任何读者以任何形式(包括各种 ...
- 《Windows驱动开发技术详解》读书笔记(一)
首先需要安装DDK,这里我选择Microsoft Windows Server 2003 SP1 DDK Windows驱动分成两类,一类是不支持即插即用的NT式驱动,一类是支持即插即用的WDM驱动. ...
- Windows驱动开发要点总结一
1 概述 驱动程序大体可分为两类三种: 第一类:传统型驱动 传统型驱动的特点就是所有的IRP都需要自己去处理,自己实现针对不同IRP的派发函数.其可以分 为以下两种: 1. Nt式驱动 ...
最新文章
- go语言 rlock() defer runlock()_Go并发编程之美-读写锁
- php对象+this,PHP $this:当前对象
- C语言中Static和Const关键字的的作用
- SpringCloudGateway起步
- 编写程序,使用一维数组,模拟栈数据结构。 要求: 1、这个栈可以存储java中的任何引用类型的数据。 2、在栈中提供push方法模拟压栈。(栈满了,要有
- 无心剑随感《程序人生乐无穷》
- cygwin sshd服务启动不了的解决方案(转)
- CCF2018-3-2 碰撞的小球
- openstack nova ×××
- 保存的离线网页总是自动跳转怎么办???
- Heckman 两阶段法及与工具变量法的区别
- flash加载脚本文件导致IE脚本错误 ,行53 ,字符3,缺少对象,代码0 , 怎么解决?
- 总纲篇:塑胶材料选型、模具工艺、注塑工艺指导
- Effective C++改善程序与设计的55个具体的做法
- 微前端框架 之 qiankun
- 4070显卡相当于什么水平 4070显卡参数 rtx4070显卡功耗
- 《魂斗罗:归来》子弹中没中,没你想得那么简单!
- nginx启动失败nginx: [emerg] bind() to 0.0.0.0:7001 failed (98: Address already in use)
- 系统架构师考试-案例
- MyBatis的参数传递