文章目录

  • 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;
}

这里有两个重要的标记DeferredIoCompletionSynchronousIo,下面详细解释一下这个标记的意义:

  1. 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;}//...
}
  1. 所以,类似NtReadFileNtWriteFile发起的IRP就会标记IRP_DEFER_IO_COMPLETION这个标记,并且DeferredIoCompletion这个参数设置成为TRUE,因此对于IRP_MJ_READIRP_MJ_WRITE如果返回一个非STATUS_PENDING在这里就会判断失败(if (status != STATUS_PENDING)),导致调用IopCompleteRequest直接完成IRP。因此有很多的开发朋友在IRP_MJ_READIRP_MJ_WRITE分发函数中错误的返回了一个非STATUS_PENDING,导致返回一个IRP就被释放而出现莫名其妙的问题的现象。
  2. 对于if (SynchronousIo)是判断是否是同步完成这个请求,如果是同步,那么如果返回STATUS_PENDING的时候,就会引起Wait操作,等待底层IRP的完成。从这里也可以发现,其实上层都是默认底层是异步完成的IRP的,只是上层针对是否应用层是同步请求还是异步请求来判断是否等待IRP完成。

2. IoMarkIrpPending

对于驱动层,异步完成IRP的请求的流程是:

  1. IoMarkIrpPending(Irp);标记IRP异步。
  2. 排队IRP,等待完成。
  3. 返回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 状态设置的代码。

  1. Irp->PendingReturned = stackPointer->Control & SL_PENDING_RETURNED; : 我们需要根据设备栈中释放PENDING返回,来标记IRP的PendingReturned状态。
  2. 然后针对IRP的返回值,判断SL_INVOKE_ON_SUCCESS, SL_INVOKE_ON_ERRORSL_INVOKE_ON_CANCEL状态,来调用完成例程。
  3. 如果没有调用完成例程,那么判断Irp->PendingReturned并设置IoMarkIrpPending( Irp ); 状态,这样下次循环的时候Irp->PendingReturned = stackPointer->Control & SL_PENDING_RETURNED; 这个语句继续可以设置PendingReturned 了。

为什么需要这样操作呢?这个流程是比较复杂的,这里只是大概解释一下原因:

  1. 如果有中间的设备有一个设备是异步完成的,这个时候标记了stackPointer->Control & SL_PENDING_RETURNED,然后IRP的请求线程就返回了,这个时候如果是同步函数,那么就存在一个问题,发起IRP的IopSynchronousServiceTail处于KeWaitForSingleObject(&FileObject->Event等待状态,那么这个是在什么时候设置的呢?其实大概会有如下的判断:
if (irp->PendingReturned && fileObject) {(VOID) KeSetEvent( &fileObject->Event, 0, FALSE );
}
  1. 那么这里的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相关推荐

  1. Windows驱动之IRP结构

    文章目录 Windows驱动之IRP结构 1. IRP 2. IO_STACK_LOCATION 3. IRP 和 IO_STACK_LOCATION 的交互 3.1 IoAllocateIrp 3. ...

  2. 【IRP】Windows 驱动之IRP

    什么是IRP: I/O request packets,简称IRP.即输入输出请求包.它是WINDOWS内核中的一种非常重要的数据结构.上层应用程序与底层驱动程序通信时,应用程序会发出I/O请求.操作 ...

  3. windows 驱动开发基础(二)事件通知---关于irp处理,DPC,链表等

    代码来源及参考:wdk 7,路径:C:\WinDDK\7600.16385.1\src\general\event 这个例子演示了两种关于当硬件事件发生时,驱动如何通知应用程序的方法.一种是基于eve ...

  4. 《Windows驱动开发技术详解》学习笔记

    Abstract   如果推荐 Windows 驱动开发的入门书,我强烈推荐<Windows驱动开发技术详解>.但是由于成书的时间较早,该书中提到的很多工具和环境都已不可用或找不到,而本文 ...

  5. 键盘过滤驱动之IRP劫持

    参考资料: [1] <Rootkits--Windows内核的安全与防护> [2] 让一切输入都难逃法眼(驱动级键盘过滤钩子) 本文主要介绍通过劫持IRP(IRP_MJ_READ)实现键盘 ...

  6. c语言windows驱动编程入门,Windows驱动开发技术详解 PDF扫描版[175MB]

    Windows驱动开发技术详解由浅入深.循序渐进地介绍了windows驱动程序的开发方法与调试技巧.本书共分23章,内容涵盖了windows操作系统的基本原理.nt驱动程序与wdm驱动程序的构造.驱动 ...

  7. 楚狂人Windows驱动编程基础教程

    版权声明     本书是免费电子书.作者保留一切权利.但在保证本书完整性(包括版权声明.前言.正文内容.后记.以及作者的信息),并不增删.改变其中任何文字内容的前提下,欢迎任何读者以任何形式(包括各种 ...

  8. 《Windows驱动开发技术详解》读书笔记(一)

    首先需要安装DDK,这里我选择Microsoft Windows Server 2003 SP1 DDK Windows驱动分成两类,一类是不支持即插即用的NT式驱动,一类是支持即插即用的WDM驱动. ...

  9. Windows驱动开发要点总结一

    1 概述 驱动程序大体可分为两类三种: 第一类:传统型驱动     传统型驱动的特点就是所有的IRP都需要自己去处理,自己实现针对不同IRP的派发函数.其可以分 为以下两种:     1. Nt式驱动 ...

最新文章

  1. go语言 rlock() defer runlock()_Go并发编程之美-读写锁
  2. php对象+this,PHP $this:当前对象
  3. C语言中Static和Const关键字的的作用
  4. SpringCloudGateway起步
  5. 编写程序,使用一维数组,模拟栈数据结构。 要求: 1、这个栈可以存储java中的任何引用类型的数据。 2、在栈中提供push方法模拟压栈。(栈满了,要有
  6. 无心剑随感《程序人生乐无穷》
  7. cygwin sshd服务启动不了的解决方案(转)
  8. CCF2018-3-2 碰撞的小球
  9. openstack nova ×××
  10. 保存的离线网页总是自动跳转怎么办???
  11. Heckman 两阶段法及与工具变量法的区别
  12. flash加载脚本文件导致IE脚本错误 ,行53 ,字符3,缺少对象,代码0 , 怎么解决?
  13. 总纲篇:塑胶材料选型、模具工艺、注塑工艺指导
  14. Effective C++改善程序与设计的55个具体的做法
  15. 微前端框架 之 qiankun
  16. 4070显卡相当于什么水平 4070显卡参数 rtx4070显卡功耗
  17. 《魂斗罗:归来》子弹中没中,没你想得那么简单!
  18. nginx启动失败nginx: [emerg] bind() to 0.0.0.0:7001 failed (98: Address already in use)
  19. 系统架构师考试-案例
  20. MyBatis的参数传递

热门文章

  1. 计算机二级前两周,知道这些,计算机二级两周够了
  2. java网络编程实用精解_Java网络编程实用精解
  3. OpenGL字体绘制
  4. 2022中国汽车测试及质量监控博览会
  5. How to Add a Dotted Underline Beneath HTML Text
  6. 计算机论文的致谢部分写什么,毕业论文致谢部分怎么写
  7. 盘点2019年经典营销案例
  8. 梳理原型结构的方法小结
  9. linux内核:时间与jiffes互相转换
  10. php 图片生成视频,图片转化为视频的方法 如何将照片制作成为视频