[转&精]IO_STACK_LOCATION与IRP的一点笔记

IO_STACK_LOCATION和IRP算是驱动中两个很基础的东西,为了理解这两个东西,找了一点资料。

1. IRP可以看成是Win32窗口程序中的消息(Message),DEVICE_OBJECT可以看成是Win32窗口程序中的窗口(Window)

2. 任何内核模式程序在创建一个IRP时,同时还创建了一个与之关联的IO_STACK_LOCATION结构数组:数组中的每个堆栈单元都对应一个将处理该IRP的驱动程序。
IRP的头部有一个当前IO_STACK_LOCATION的数组索引,同时也有一个指向该IO_STACK_LOCATION的指针。索引是从1开始,
没有0。当驱动程序准备向次低层驱动程序传递IRP时可以调用IoCallDriver例程,它其中的一个工作是递减当前
IO_STACK_LOCATION的索引,使之与下一层的驱动程序匹配。但该索引不会设置成0,如果设置成0,系统将会崩溃。就是说,最底层的驱动程序
不会调用IoCallDriver例程。

3. IO_STACK_LOCATION中有一个PIO_COMPLETION_ROUTINE类型的成员CompletionRoutine,这是一个I/O完成例程的地址,该地址是由与这个堆栈单元对应的驱动程序的更上一层驱动程序设置的。你绝对不要直接设置这个域,应该调用IoSetCompletionRoutine函数,该函数知道如何参考下一层驱动程序的堆栈单元。设备堆栈的最低一级驱动程序并不需要完成例程,因为它们必须直接完成请求。然而,请求的发起者有时确实需要一个完成例程,但通常没有自己的堆栈单元。这就是为什么每一级驱动程序都使用下一级驱动程序的堆栈单元保存自己完成例程指针的原因。

 1 VOID
 2 IoSetCompletionRoutine(
 3     __in PIRP Irp,
 4     __in_opt PIO_COMPLETION_ROUTINE CompletionRoutine,
 5     __in_opt __drv_aliasesMem PVOID Context,
 6     __in BOOLEAN InvokeOnSuccess,
 7     __in BOOLEAN InvokeOnError,
 8     __in BOOLEAN InvokeOnCancel
 9     )
10 {
11     PIO_STACK_LOCATION irpSp;
12     ASSERT( (InvokeOnSuccess || InvokeOnError || InvokeOnCancel) ? (CompletionRoutine != NULL) : TRUE );
13     irpSp = IoGetNextIrpStackLocation(Irp);
14     irpSp->CompletionRoutine = CompletionRoutine;
15     irpSp->Context = Context;
16     irpSp->Control = 0;
17
18     if (InvokeOnSuccess) {
19         irpSp->Control = SL_INVOKE_ON_SUCCESS;
20     }
21
22     if (InvokeOnError) {
23         irpSp->Control |= SL_INVOKE_ON_ERROR;
24     }
25
26     if (InvokeOnCancel) {
27         irpSp->Control |= SL_INVOKE_ON_CANCEL;
28     }
29 }

 

总算解释了一下为什么IoSetCompletionRoutine中把完成例程设置在下一个IO堆栈之中。最底层的驱动程序不应该安装一个完成例程。

4. 完成例程的框架

 1 NTSTATUS CompletionRoutine(PDEVICE_OBJECT device, PIRP Irp, PVOID context)
 2 {
 3     if (Irp->PendingReturned)
 4     {
 5         IoMarkIrpPending(Irp);
 6     }
 7     // ...
 8
 9     return STATUS_SUCCESS /* or some other status code */ ;
10 }

 

如果Irp->PendingReturned为TRUE,那么任何不返回STATUS_MORE_PROCESSING_REQUIRED的完成例程都应该调用IoMarkIrpPending,这几乎完全是对的,但仍有例外。如果驱动程序分配了IRP,安装了完成例程,然后在未改变堆栈指针的情况下调用IoCallDriver,那么完成例程就不应该包含这两行代码,因为没有堆栈单元与你的驱动程序关联。(下划线部分理解不太清楚,可以先不做理解,实际情况注意下就是了。有些书甚至都没有提及这一点)

5. 如果你的驱动程序不用关心IRP传递到下层驱动程序之后的事情,没有必要花费处理器时间(调用 IoCopyCurrentIrpStackLocationToNext)去把你的堆栈单元内容复制到下一个堆栈单元,因为那个堆栈单元已经含有下一层 驱动程序要得到的参数,以及自己上一层驱动程序可能给出的任何完成例程指针。因此可以用下面的代码:

1 NTSTATUS ForwardAndForget(PDEVICE_OBJECT fdo, PIRP Irp)
2 {
3     PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
4     IoSkipCurrentIrpStackLocation(Irp);
5     return IoCallDriver(pdx->LowerDeviceObject, Irp);
6 }

 

这样的代码是不是在过滤驱动中经常见到呢?

6. IoCopyCurrentIrpStackLocationToNext和IoSkipCurrentIrpStackLocation
IoCopyCurrentIrpStackLocationToNext复制IO堆栈除了完成例程以及完成例程参数以为的内容。

 1 VOID
 2 IoCopyCurrentIrpStackLocationToNext(
 3     __inout PIRP Irp
 4 )
 5 {
 6     PIO_STACK_LOCATION irpSp;
 7     PIO_STACK_LOCATION nextIrpSp;
 8     irpSp = IoGetCurrentIrpStackLocation(Irp);
 9     nextIrpSp = IoGetNextIrpStackLocation(Irp);
10     RtlCopyMemory( nextIrpSp, irpSp, FIELD_OFFSET(IO_STACK_LOCATION, CompletionRoutine));
11     nextIrpSp->Control = 0;
12 }

 

IoSkipCurrentIrpStackLocation使堆栈指针少前进一步,而IoCallDriver函数会使堆栈指针向前一步,中和的 结果就是堆栈指针不变。当下一个驱动程序的派遣例程调用IoGetCurrentIrpStackLocation时,它将收到与我们正使用的完全相同的 IO_STACK_LOCATION指针。

1 VOID
2 IoSkipCurrentIrpStackLocation (
3     __inout PIRP Irp
4 )
5 {
6     ASSERT(Irp->CurrentLocation <= Irp->StackCount);
7     Irp->CurrentLocation++;
8     Irp->Tail.Overlay.CurrentStackLocation++;
9 }

 

原来我在这里是有疑问的:一个IRP对应一个IO_STACK_LOCATION,如果使用IoSkipCurrentIrpStackLocation那不是少了一个IO_STACK_LOCATION吗?是的,确实是这样,书上都没有说清楚~~看下面的图吧:
图中显示了这样一种情形:某设备堆栈有三个驱动程序,你的驱动程序(功能设备对象[FDO])和其它两个驱动程序(一个上层过滤器设备对象[FiDO],
一个PDO)。在图(a)中,你将看到执行复制堆栈单元的IoCopyCurrentIrpStackLocationToNext函数,堆栈单元、各个
参数,和完成例程之间的关系。在图(b)中,你将看到还是这样的关系,但使用的是IoSkipCurrentIrpStackLocation函数,第三
个和最后一个堆栈单元被跳过。【注:原文中的"第三 个和最后一个堆栈单元被跳过。"(the third and last stack location is fallow, but nobody gets confused by that fact.)的第三个和最后一个应该说的是同一个堆栈单元。】

7. IRP完成例程与STATUS_MORE_PROCESSING_REQUIRED
如果某一层的驱动希望下发的IRP在完成之时能够再次获得IRP的控制权,那么可以再完成例程中返回STATUS_MORE_PROCESSING_REQUIRED。代码看起来如下:

 1 NTSTATUS ForwardAndWait(PDEVICE_OBJECT fdo, PIRP Irp)
 2 {
 3     KEVENT event;
 4     KeInitializeEvent(&event, NotificationEvent, FALSE);
 5     IoCopyCurrentIrpStackLocationToNext(Irp);
 6     IoSetCompletionRoutine(Irp,
 7             (PIO_COMPLETION_ROUTINE) OnRequestComplete,
 8             (PVOID) &event,
 9             TRUE,
10             TRUE,
11             TRUE);
12     PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
13     IoCallDriver(pdx->LowerDeviceObject, Irp);
14     KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
15     return Irp->IoStatus.Status;
16 }
17
18 NTSTATUS OnRequestComplete(PDEVICE_OBJECT fdo, PIRP Irp, PKEVENT pev)
19 {
20     KeSetEvent(pev, 0, FALSE);
21     return STATUS_MORE_PROCESSING_REQUIRED;
22 }

 

一旦我们调用了IoCallDriver,我们就放弃了IRP的控制权,直到某些运行在任意线程上下文中的代码调用 IoCompleteRequest通知该IRP完成,IoCompleteRequest将调用我们的完成例程。通过在完成例程中返回 STATUS_MORE_PROCESSING_REQUIRED,我们停止了I/O堆栈的回卷处理。此时,上层过滤器驱动程序安装的任何完成例程都得不 到调用,并且I/O管理器将停止在该IRP上的工作。这种情形就象根本没有调用过IoCompleteRequest一样,当然,某些已经调用过的低级完 成例程除外。在这一时刻,该IRP将处于一个中间状态,但我们的ForwardAndWait例程将再次获得该IRP的所有权。

为什么要重新控制IRP呢?有一种情况是:这个IRP是当前的驱动动态分配的,转发给下层驱动之后,这个IRP总要在下面的驱动处理完之后把IRP分配用的空间回收吧?

Reference:《Programming the Microsoft Windows Driver Model



原文地址: 程序人生 >> IO_STACK_LOCATION与IRP的一点笔记
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

时间: 2024-10-13 11:28:06

[转&精]IO_STACK_LOCATION与IRP的一点笔记的相关文章

IO_STACK_LOCATION与IRP的一点笔记

IO_STACK_LOCATION和IRP算是驱动中两个很基础的东西,为了理解这两个东西,找了一点资料. 1. IRP可以看成是Win32窗口程序中的消息(Message),DEVICE_OBJECT可以看成是Win32窗口程序中的窗口(Window) 2. 任何内核模式程序在创建一个IRP时,同时还创建了一个与之关联的IO_STACK_LOCATION结构数组:数组中的每个堆栈单元都对应一个将处理该IRP的驱动程序.IRP的头部有一个当前IO_STACK_LOCATION的数组索引,同时也有一

阅读xtrabackup代码的一点笔记

xtrabackup binary最重要的两个过程是backup和prepare,对应的函数分别是xtrabackup_backup_func()和xtrabackup_prepare_func(),这里做一些阅读代码时的笔记. xtrabackup backup的线程模型: 1. 一个log拷贝线程: 2. n个ibd文件拷贝线程: 3. 一个io监控线程: 4. 通过suspend_start/suspend_end文件来标注是否启动终止线程: typedef struct { datafi

一点笔记

去年年底的时候买了一只Kindle Paperwhite,因为尺寸较小,方便携带,所以也比较适合看一些非技术类的图书.加上每天上下班路上的2个小时,等电梯时间,以及吃饭等座的一些闲暇时间,看了一些书,有时候突然觉得,只有这些小的碎时间才是最自由的,平常上班时间为了生活,晚上回来要休息积蓄能量.几个月下来也零零散散的看了一些书,有时候看着看着很想写点东西,就在手机上用OneNote记了作为读书笔记,本来想在朋友圈分享的,由于字数限制就写了这篇水文,下面就按照觉得有意思的顺序来逐个些吧,共九本书,后

关于在VB.NET中调用使用VC++编写的类库dll的一点笔记

前言 结对作业要求一出来,我就立刻想到了把“计算核心”封装成dll,然后使用vb.net编写UI调用dll的思路.然而在实现过程中却遇到了很多的问题. 我在这个过程中是负责使用vb.net编写UI并调用编写好的DLL进行计算的. 目标 使用c++把类封装到dll,并在vb.net中调用该dll,使用该dll中封装好的类.在查找资料的过程中,发现vb.net调用dll的方法主要有两种. (IDE:Visual Studio 2013 professional) 方法一:使用Declare语句 vb

cookie随便写的一点笔记(抄书的)

cookie是保存在客户端的文本,能够在一定程度上提高用户体验.Servlet API 中提供了Cookie类,可以创建Cookie对象,并通过响应中的addCookie方法,将cookie保存到客户端. Cookie的概念与使用:    cookie是保存在客户端的文本    Servlet API提供了Cookie类,可以将文本信息封装成Cookie对象    HttpServletResponse接口中提供了addCookie方法,将cookie添加到响应中    HttpServletR

关于比较的一点笔记

在Java中要进行两个对象的比较时,会用到关系运算符.通常关系运算符生成的是一个boolean值结果.它们评价的是运算对象值之间的关系.一般关系运算符包括小于(<).大于(>).小于等于(<=).大于等于(>=).等于(==)以及不等于(!=).等于和不等于适用于所有内建的数据类型,但其他比较不适用于boolean类型. 关系运算符==和!=适用于所有对象,但是会有一些有趣的现象,下面是测试代码 1 public class Equivalence { 2 public stati

[JAVA Programming] 关于JList的一点笔记

这次写JAVA课的大作业,首先不得不佩服所给的dictionary.txt文件的厉害之处啊,各种大小写.连字符还有各种词组的不同情况在自己测试的时候都中奖了,我该高兴么... 其实要求不高,大概就是一个词典的查询软件,提供了后台词典,只要完成其中的文件I/O,进行String的处理就可以了. 下面其实主要是一些算法问题,查找的话,既然有序(但是从某些角度说,'-'的值要比a-z小啊,但是在dictionary中的顺序却不是这样啊~~所以我暴力地进行了一次QuickSort...)果断O(logn

在使用Linq中 遇到问题的一点笔记

直接上代码: using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { /// <summary> /// 只作为快速的测试,没考虑编码规范的问题 /// </summary> class Program { static void Main(string[] args) { List<Student

C++中函数处理数组的一点笔记

// pointDemo0905.cpp : 定义控制台应用程序的入口点.// #include "stdafx.h" //写法1//int sum(int array[],int num);//写法2int sum(const int *array,int num); void revalue(int r,int *array,int num); int _tmain(int argc, _TCHAR* argv[]){ int adata[8]={1,2,3,4,5,6,7,8};