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函数,该函数知道如何参考下一层驱动程序的堆栈单元。设备堆栈的最低一级驱动程序并不需要完成例程,因为它们必须直接完成请求。然而,请求的发起者有时确实需要一个完成例程,但通常没有自己的堆栈单元。这就是为什么每一级驱动程序都使用下一级驱动程序的堆栈单元保存自己完成例程指针的原因。

 VOID
IoSetCompletionRoutine(
__in PIRP Irp,
__in_opt PIO_COMPLETION_ROUTINE CompletionRoutine,
__in_opt __drv_aliasesMem PVOID Context,
__in BOOLEAN InvokeOnSuccess,
__in BOOLEAN InvokeOnError,
__in BOOLEAN InvokeOnCancel
)
{
PIO_STACK_LOCATION irpSp;
ASSERT( (InvokeOnSuccess || InvokeOnError || InvokeOnCancel) ? (CompletionRoutine != NULL) : TRUE );
irpSp = IoGetNextIrpStackLocation(Irp);
irpSp->CompletionRoutine = CompletionRoutine;
irpSp->Context = Context;
irpSp->Control = ; if (InvokeOnSuccess) {
irpSp->Control = SL_INVOKE_ON_SUCCESS;
} if (InvokeOnError) {
irpSp->Control |= SL_INVOKE_ON_ERROR;
} if (InvokeOnCancel) {
irpSp->Control |= SL_INVOKE_ON_CANCEL;
}
}
 

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

4. 完成例程的框架

 NTSTATUS CompletionRoutine(PDEVICE_OBJECT device, PIRP Irp, PVOID context)
{
if (Irp->PendingReturned)
{
IoMarkIrpPending(Irp);
}
// ... return STATUS_SUCCESS /* or some other status code */ ;
}
 

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

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

 NTSTATUS ForwardAndForget(PDEVICE_OBJECT fdo, PIRP Irp)
{
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(pdx->LowerDeviceObject, Irp);
}
 

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

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

 VOID
IoCopyCurrentIrpStackLocationToNext(
__inout PIRP Irp
)
{
PIO_STACK_LOCATION irpSp;
PIO_STACK_LOCATION nextIrpSp;
irpSp = IoGetCurrentIrpStackLocation(Irp);
nextIrpSp = IoGetNextIrpStackLocation(Irp);
RtlCopyMemory( nextIrpSp, irpSp, FIELD_OFFSET(IO_STACK_LOCATION, CompletionRoutine));
nextIrpSp->Control = ;
}
 

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

 VOID
IoSkipCurrentIrpStackLocation (
__inout PIRP Irp
)
{
ASSERT(Irp->CurrentLocation <= Irp->StackCount);
Irp->CurrentLocation++;
Irp->Tail.Overlay.CurrentStackLocation++;
}
 

原来我在这里是有疑问的:一个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。代码看起来如下:

 NTSTATUS ForwardAndWait(PDEVICE_OBJECT fdo, PIRP Irp)
{
KEVENT event;
KeInitializeEvent(&event, NotificationEvent, FALSE);
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp,
(PIO_COMPLETION_ROUTINE) OnRequestComplete,
(PVOID) &event,
TRUE,
TRUE,
TRUE);
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
IoCallDriver(pdx->LowerDeviceObject, Irp);
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
return Irp->IoStatus.Status;
} NTSTATUS OnRequestComplete(PDEVICE_OBJECT fdo, PIRP Irp, PKEVENT pev)
{
KeSetEvent(pev, , FALSE);
return STATUS_MORE_PROCESSING_REQUIRED;
}
 

一旦我们调用了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) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

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

  1. 关于最小生成树,拓扑排序、强连通分量、割点、2-SAT的一点笔记

    关于最小生成树,拓扑排序.强连通分量.割点.2-SAT的一点笔记 前言:近期在复习这些东西,就xjb写一点吧.当然以前也写过,但这次偏重不太一样 MST 最小瓶颈路:u到v最大权值最小的路径.在最小生 ...

  2. 关于SS的一点笔记

    过年的时候抽了点时间了解了下ss的协议.整理了一点笔记,一直没有时间发.今天发一下,免得忘了. SS的结构本身比较简单,他的基本结构如下: ss通常分为client和server两部分 client是 ...

  3. pyhon的yileld的一点笔记

    yield感觉很神秘,感觉也不好理解,学习pyhon最后终归是要学习这个东西,研究了一段时间,把自己的笔记写下来 说简单点就是遇到yield就停止往下执行代码,也包括不执行yield这条语句,然后返回 ...

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

    前言 结对作业要求一出来,我就立刻想到了把“计算核心”封装成dll,然后使用vb.net编写UI调用dll的思路.然而在实现过程中却遇到了很多的问题. 我在这个过程中是负责使用vb.net编写UI并调 ...

  5. 阅读xtrabackup代码的一点笔记

    xtrabackup binary最重要的两个过程是backup和prepare,对应的函数分别是xtrabackup_backup_func()和xtrabackup_prepare_func(), ...

  6. [书籍精读]《React Native精解与实战》精读笔记分享

    写在前面 书籍介绍:本书由架构师撰写,包含ReactNative框架底层原理,以及与iOS.Android混合开发案例,精选了大量实例代码,方便读者快速学习.主要内容分为两大部分,第1部分" ...

  7. c#委托----我的一点笔记

    public partial class ucBloodLabs { public delegate void ShowBloodEvent(); public ShowBloodEvent Show ...

  8. 看的oracle数据库视频 记的一点笔记

    3个默认的用户    sys          //网络管理员  权限由上到下降低 [最后加上 as sysdba]    system  //本地管理员    scott     //普通用户  默 ...

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

    cookie是保存在客户端的文本,能够在一定程度上提高用户体验.Servlet API 中提供了Cookie类,可以创建Cookie对象,并通过响应中的addCookie方法,将cookie保存到客户 ...

随机推荐

  1. find 查询文件,执行文件

    使用find查询文件批量处理文件: Find . -mtime +2 -name “*.log.*” -exec rm -f {} \; Find参数: Find : 查找 . : 当前目录 -mti ...

  2. Spring学习-01

    一.Srping 一个轻量级DI.IOC.AOP的容器框架 DI:依赖注入 IOC:控制反转 AOP:面向切面 二.构造器注入 Constructor-arg 属性:index/name/type/r ...

  3. kvm+webvirtmgr在centos7上的部署

    #!/bin/bash #+++++++++++++++++++++++++++++++++++++++++++++++++++++++安装配置kvm并创建虚拟机+++++++++++++++++++ ...

  4. (30)3 ways to make better decisions — by thinking like a computer

    https://www.ted.com/talks/tom_griffiths_3_ways_to_make_better_decisions_by_thinking_like_a_computer0 ...

  5. mysql 截取替换某个字符串

    SELECT m.content,o.order_price,o.id,m.id FROM scp_home_msg m INNER JOIN scp_order o ON m.link_id=o.i ...

  6. ntile函数

    ntile函数可以对序号进行分组处理,将有序分区中的行分发到指定数目的组中. 各个组有编号,编号从一开始. 对于每一个行,ntile 将返回此行所属的组的编号.这就相当于将查询出来的记录集放到指定长度 ...

  7. java web 开发手册

    W3School离线手册(2017.03)               提取密码: b2fo JavaScript高级程序设计第三版             提取密码: cscv CSS4.2.4 参 ...

  8. vue组件通信新姿势

    在vue项目实际开发中我们经常会使用props和emit来进行子父组件的传值通信,父组件向子组件传递数据是通过prop传递的, 子组件传递数据给父组件是通过$emit触发事件来做到的.例如: Vue. ...

  9. jquery validate 校验使用总结

    一.jquery.validator表单验证id和name问题 因为后台是struts,表单提交,所有输入框的值保存在name=对象.名字中,而jquery.validator表单验证用的是name, ...

  10. 28.实现 strStr() 函数

    28.实现 strStr() 函数 给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始).如果不存在, ...