windows内核开发学习笔记十八:IRP 处理的标准模式

在 Windows 内核中的请求基本上是通过 I/O Request Packet 完成的。

I/O manager ---> Dispatch routine ---> StartIo routine ---> ISR ---> DPC routine ---> I/O manager

一、建立一个 IRP

IRP 的生存期从调用 I/O manager function 建立 IRP 开始,你可以使用下面 4 个 function 来建立一个新的 IRP:

  • IoBuildAsynchronousFsdRequest(): 建立一个 IRP 不希望等待。这个函数只适合用于某类的 IRP
  • IoBuildSynchronousFsdRequest(): 建立一个 IRP 需要等待完成。
  • IoBuildDeviceIoControlRequest(): 建立一个同步的 IRP_MJ_DEVICE_CONTROL 或者 IRP_MJ_INTERNAL_DEVICE_CONTROL
  • IoAllcateIrp(): 建立一个 asynchronous 的 IRP

建立 synchronous IRP 
使用 IoBuildSynchronousFsdRequest() 或 IoBuildDeviceIoControlRequest() 来建立同步的 IRP,同步 IRP 是属于创建者线程。 由它有一个物主,由此有下面的一系列结果:

  • 假如物主线程终止,I/O manager 自动取消属于该线程的同步 IRP 的 pending
  • 由于创建线程拥有这个同步 IRP 的缘故,你不能在任意线程 context 里创建同步 IRP,当物主线程终止后不能请求 I/O manager 取消 IRP。
  • 调用 IoCompleteRequest(),I/O manager 自动清同步 IRP,并且置你必须提供的 event 置 signaled 状态。
  • 在 I/O manager 置 event object signaled 状态后,event object 仍存在时你必须小心地处理 event object。

必须在 PASSIVE_LEVEL 里调用这两个函数,特别是不能在 APC_LEVEL 级别上。因为:在获得 fast mutex 后进入到 APC_LEVEL,然后 I/O manager不能提交 special APC routine 去处理所有 complete 处理。

PIRP Irp = IoBuildSycnhronousFsdRequest(...);
ExAcqurireFastMutex(...);
NTSTATUS status = IoCallDriver(...);
if (status == STATUS_PENDING)
{
        KeWaitForSingleObject(...);             // 错误:不要这样做
}
ExReleaseFastMutex(...);

在上面的代码里,使用 KeWaitForSingleObject() 等待会进入死锁:当完成执行 IoCompleteRequest(),这个 APC routine 运行将设置 event, 
因为已经在 APC_LEVEL 级别上,APC routine 不能运行去设置 event signled。 
假如你需要发送一个 synchronous IRP 到其它 driver,考虑下面的选择:

  • 使用定期的 kernel mutex 来代替 fast mutex,kernel mutex 将返回到 PASSIVE_LEVEL 级别上,并不会抑制 special APC 执行。
  • 使用 KeEnterCriticalRegion() 来抑制所有除了 special APC 外,然后使用 ExAcquireFastMutexUnsafe() 来获得 mutex。
  • 使用 asynchronous IRP(代替 synchronous IRP),完成后 signaled event。

这两个函数所建立的 IRP 为下表所示:

support function                                IRP 类型
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IoBuildSynchronousFsdRequest()                     IRP_MJ_READ
                                                                          IRP_MJ_WRITE
                                                                          IRP_MJ_FLUSH_BUFFERS
                                                                          IRP_MJ_SHUTDOWN
                                                                          IRP_MJ_PNP
                                                                          IRP_MJ_POWER(仅用于 IRP_MN_POWER_SEQUENCE)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IoBuildDeviceControlRequest()                         IRP_MJ_DEVICE_CONTROL
                                                                          IRP_MJ_INTERNAL_DEVICE_CONTROL

建立异步 IRP
IoBuildAsynchronousFsdRequest() 和 IoAllocateIrp() 这两上函数建立异步的 IRP,这些可以建立 IRP 如下表所示:

support function                                IRP 类型
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IoBuildAsynchronousFsdRequest()                 IRP_MJ_READ
                                                                         IRP_MJ_WRITE
                                                                         IRP_MJ_FLUSH_BUFFERS
                                                                         IRP_MJ_SHUTDOWN
                                                                         IRP_MJ_PNP
                                                                         IRP_MJ_POWER(仅用于 IRP_MN_POWER_SEQUENCE)
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
IoAllocateIrp()                                                  任何(但必须初始化 Marjor function 表)

异步 IRP 不属于创建者线程,当 IRP 完成后,I/O manager 不调用 APC 以及不清理 IRP。考虑下面的问题:

  • 当线程终止后,I/O manager 不会取消任何异步 IRP 的 pending 可以在任意线程 context 里创建
  • 由于 IRP 完成后 I/O manager 不清理 IRP,你必须提供一个complete routine 去释放 buffer 以及调用 IoFreeIrp() 释放 IRP 所使用的内存。 当长时期没发生操作时,你可能需要提供一个 cancel routine。
  • 由于不需要等待异步 IRP 的完成,你可以创建 IRP 在 IRQL <= DISPATCH_LEVEL 级别上,当获得 fast mutex 发送异步 IRP 是可以的。

二、dispatch routine

当建立一个 IRP 后,可以使用 IoGetNextIrpStackLocation() 来获得 first stack location,然后需要对 stack location 进行初始化。 
如果是使用 IoAllcateIrp() 建立的需要填写相关的 MajorFunction 表。

PEDEVICE_OBJECT DeviceObject;
PIO_STACK_LOCATION stack = IoGetNextIrpStackLoction(Irp);
stack->MajorFunction = IRP_MJ_Xxx;
// ... stack 初始化代码
NTSTATUS status = IoCallDriver(DeviceObject, Irp);

IoGetNextIrpStackLocation() 是一个宏用来获得当前的 stack location,它的定义如下:
#define IoGetNextIrpStackLocation(Irp)          ((Irp)->Tail.Overlay.CurrentStackLocation - 1)

IoCallDriver完成的内容

IoCallDriver() 看起来像下面:

NTSTATUS IoCallDriver(PDEVICE_OBJECT DeviceObject,
                PIRP Irp)
{
        IoSetNextIrpStackLocation(Irp);
        PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
        Stack->DeviceObject = DeviceObject;
        ULONG fcn = Stack->MajorFunction;
        PDRVIER_OBJECT Driver = DeviceObject->DriverObject;
        return (*Driver->MajorFunction[fcn]))(DeviceObject, Irp);
}

IoCallDriver() 简单地调用 stack 指针对应的 driver 的 dispatch routine。

location device objects 
除了使用 IoAttachDeviceToDeviceStack() 外,可以使用另外的两个方法:IoGetDeviceObjectPointer() 和 IoGetAttachedDeviceReference()

NTSTATUS IoGetDeviceObjectPointer(
    IN PUNICODE_STRING  ObjectName,
    IN ACCESS_MASK  DesiredAccess,
    OUT PFILE_OBJECT  *FileObject,
    OUT PDEVICE_OBJECT  *DeviceObject
    );

假如你知道 device object 的名字,那么你可以使用这个函数 IoGetDeviceObjectPointer() 得到 device object,像下面的用法:

PUNICODE_STRING devname;
ASSESS_MASK access;
PDEVICE_OBJECT DeviceObject;
PFILE_OBJECT FileObject;
NTSTATUS status;

ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
status = IoGetDeviceObjectPointer(devname, access, &FileObject, &DeviceObject);

这个函数返回两个指针:一个指向 FILE_OBJECT, 一个指向 DEVICE_OBJECT。

PIRP Irp = IoXxx(...);
PIO_STACK_LOCATION Stack = IoGetNextIrpStackLocation(Irp);
ObReferenceObject(FileObject);
Stack->FileObject = FileObject;
IoCallDriver(DeviceObject, Irp);
ObDereferenceObject(FileObject);

IoGetDeviceObjectPointer() 执行一些步骤来定位返回的两个指针:

  • 使用 ZwOpenFile() 打开一个命名的 device object,它将引发 object manager 建立一个 file object 和发送 IRP_MJ_CREATE 到目标设备,ZwOpenFile() 返回 file handle
  • 调用 ObReferenceObjectByHandle() 得到代表 file handle 的 FILE_OBJECT 结构地址,这个地址以 FileObject 返回。
  • 调用 IoGetRelatedDeviceObject() 得到被 FileObject 引用的 DEVICE_OBJECT 结构地址,这个地址以 DeviceObject 返回。
  • 调用 ZwClose() 关闭 Handle

三、 Dispatch routine 职责

一个 Dispatch routine 的原型,看起来像下面:

NTSTATUS DispatchXxx(PDEVICE_OBJECT fdo, PRIP Irp)
{
        PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
        PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;

...
        return STATUS_Xxx;
}

  • 通常需要访问当前 stack location 去检测参数或都检查 minor 功能号
  • 通常也需要访问你建立的 device extension 结构(在 AddDevice() 时候初始化的)。
  • 最后要返回到 IoCallDriver() 调用上。传送一个 NTSTATUS 值。

IRP 的完成 
      完成一个 IRP 必须填允 IRP 的 IoStatus 域内的 Status 与 Information 值,然后调用 IoCompleteRequest() 函数。 这个 Status 值是在 NTSTATUS.h 文件里定义的 status code 之一。而 Information 值依赖于 IRP 的类型而定,大多时候当 IRP 失败后Information 设置为 0 ,当 IRP 引发数据的传送操作,通常设置 Information 值为传送的字节数。

windows内核开发学习笔记十八:IRP 处理的标准模式相关推荐

  1. windows内核开发学习笔记十五:IRP结构

    windows内核开发学习笔记十五:IRP结构   IRP(I/O Request Package)在windows内核中,有一种系统组件--IRP,即输入输出请求包.当上层应用程序需要访问底层输入输 ...

  2. windows内核开发学习笔记十七:IRP 和 IO_STACK_LOCATION 的交互

    windows内核开发学习笔记十七:IRP 和 IO_STACK_LOCATION 的交互 前面两篇学习笔记分别介绍了IRP和IO_STACK_LOCATION,整个设备栈来处理这个IRP,但是每个设 ...

  3. Polyworks脚本开发学习笔记(十八)-用SDK开发Polyworks插件

    Polyworks脚本开发学习笔记(十八)-用SDK开发Polyworks插件 插件是由PolyWorks加载的动态链接库(DLL文件),然后查询Polyworks模块,以确定它们具有哪些功能,提供给 ...

  4. windows内核开发学习笔记四十四:注册表存储结构-储巢

    上一篇文章学习了注册表的逻辑结构,接下来我这篇文章来学习注册表的存储结构.注册表实际存储是由一组储巢构成,每个储巢包含了一个由键和值构成的层次结构.下面表是windows的各个储巢的注册表路径和文件路 ...

  5. Nginx 模块开发(学习笔记十八)

    1. Nginx 介绍 Nginx是俄罗斯人编写的十分轻量级的HTTP服务器,它的发音为"engine X", 是一个高性能的HTTP和反向代理服务器,同时也是一个IMAP/POP ...

  6. Windows驱动开发学习笔记(七)—— 多核同步内核重载

    Windows驱动开发学习笔记(七)-- 多核同步 基础知识 并发与同步 分析 InterlockedIncrement 原子操作相关API 内核文件 多核同步 临界区 示例一:错误的临界区 示例二: ...

  7. Windows驱动开发学习笔记(三)—— 内核空间内核模块

    Windows驱动开发学习笔记(三)-- 内核空间&内核模块 内核空间 实验 第一步:编译如下代码 第二步:将 .sys 文件拷贝到虚拟机中 第三步:部署 .sys 文件并运行 第四步:创建一 ...

  8. Windows驱动开发学习笔记(二)—— 驱动调试内核编程基础

    Windows驱动开发学习笔记(二)-- 驱动调试&内核编程基础 基础知识 驱动调试 PDB(Program Debug Database) WinDbg 加载 PDB 实验:调试 .sys ...

  9. Windows驱动开发学习笔记(四)—— 3环与0环通信(常规方式)

    Windows驱动开发学习笔记(四)-- 3环与0环通信(常规方式) 设备对象 创建设备对象 设置数据交互方式 创建符号链接 IRP与派遣函数 IRP的类型 其它类型的IRP 派遣函数 派遣函数注册位 ...

最新文章

  1. Java数据结构简述
  2. 微软Visual Studio 14 CTP 2 发布
  3. LeetCode MySQL 1355. 活动参与者(any函数)
  4. Kinect开发学习笔记之(四)提取颜色数据并用OpenCV显示
  5. 微软-IT-解决方案-统一沟通-发布会
  6. 【SQL】日期型函数
  7. 【PHP内核剖析】一、PHP基本架构
  8. unity摄影机depth模式_[蛮牛教程] Unity3D 浅析-Camera(摄像机)
  9. web项目01-----项目需求分析,需求文档
  10. 600度近视眼恢复方法_600度的近视眼,恢复视力要注意
  11. index.php被修改,WordPress博客程序index.php 主页文件频繁被修改解决记录(已解决) - 勤勤学长...
  12. 程序员怎样更优雅的接私活赚外快
  13. [javascript|基本概念|Underfined]学习笔记
  14. 970万,看美女是如何炼成巨贪的!
  15. Nessus之——Nessus的整理
  16. 他们为什么离开微软? 创业热情驱动
  17. 基于Python的决策树分类器与剪枝
  18. C语言程序的文件格式
  19. call(zoom)_如何解决您的Zoom Call问题
  20. 实现人物关系图谱,人物影响力图

热门文章

  1. 电子签名第三方服务平台,如何保障电子合同安全性的?
  2. 基于MXL90614设计温度传感器
  3. 你关心的2023年PMP的考试时间和地点在这里
  4. 算法:URL短地址压缩算法-短网址映射。
  5. 【PPT】PPT设计丨1.实用功能丨
  6. html 注释 实例,超详细的HTML !–…– 注释标签使用实例
  7. 主流的大数据BI软件有哪些?
  8. 个人项目——二柱子程序升级版
  9. MACD指标为什么不灵了?试试QMACD
  10. abp zero mysql_ABP从入门到精通(2):aspnet-zero-core 使用MySql数据库