创建IRP的相关内容

来源:互联网 发布:网络餐饮服务 编辑:程序博客网 时间:2024/06/07 23:49

篇一:

在驱动程序中,经常会调用其他的驱动程序;其中,手动构造IRP,然后将IRP传递到相应驱动程序的派遣函数中是一种比较简单的方法,下面就来介绍下手动创建IRP的几种不同的方法及其特点。

         创建IRP总共有4种方法。分别通过调用:IoBuildSynchronousFsdRequest、IoBuildAsynchronousFsdRequest、IoBuildDeviceIoControl和IoAllocateIrp这4个内核函数来完成。这其中,IoAllocateIrp是比较底层的内核函数,其余的三个内核函数是属于靠近上层的内核函数,而且这三个函数都是通过调用IoAllocateIrp实现的。

         这几个函数都是文档化的函数,原型都可以在DDK Documentation中查到,这里就不多说了,下面主要来说说它们的不同点:

1.      可创建的IRP类型

这四个函数可以创建的IRP的类型是不同的。IoBuildSynchronousFsdRequest用于创建同步的IRP请求,但是只可以创建以下类型的IRP:IRP_MJ_PNP,IRP_MJ_READ,IRP_MJ_WRITE,IRP_MJ_FLUSH_BUFFERS和IRP_MJ_SHUTDOWN;IoBuildAsynchronousFsdRequest可创建的IRP类型和IoBuildSynchronousFsdRequest一样(从名字就可以看出来),只是它是用来创建异步的IRP请求。IoBuildDeviceIoControl可以创建的IRP类型为:IRP_MJ_DEVICE_CONTROL和IRP_MJ_INTERNAL_DEVICE_CONTROL。而且IoBuildDeviceIoControl只能创建同步的IRP。在这三个函数中,都有一个ULONG的输入参数指定创建的IRP类型。IoAllocateIrp函数的使用比较灵活,他可以创建任意类型的IRP,但不是由参数指定,而是创建后自行填写,要求用户对IRP的结构有比较熟悉的理解。

2.      创建后IRP对象的删除

IoBuildSynchronousFsdRequest、IoBuildAsynchronousFsdRequest和IoBuildDeviceIoControl内核函数在创建完IRP后,不需要程序员负责删除IRP,操作系统会自动删除。而用IoAllocateIrp内核函数创建IRP时,需要程序员自己调用IoFreeIrp内核函数删除IRP对象。

3.      关联的事件

IoBuildSynchronousFsdRequest和IoBuildDeviceIoControl在创建IRP时,需要为它们准备好一个事件,这个事件会和IRP请求相关联,当IRP请求被结束时该事件触发。程序中要用KeWaitForSingleObject函数等待。IoBuildAsynchronousFsdRequest函数创建IRP时则不需要准备事件,不过可以通过IRP的UserEvent子域来通知IRP请求的结束。

当执行IoCompleteRequest内核函数时,操作系统会检查IRP的UserEvent子域是否为空。如果该子域为空,则它代表一个事件指针,这时IoCompleteRequest会设置这个事件。


篇2:

近来学习Windows内核方面的东西,觉得对I/O处理过程没有一个总体的概念。于是,就花了很长的时间搜集了很多这方面的知识总结了一下。

在Windows内核中的请求基本上是通过 I/ORequest Packet 完成的。前面说过,设备对象是唯一可以接受请求的实体。下面,我就来详细地说下IRP请求是怎么样一步一步完成的。

首先,我们就需要知道IRP是怎么产生。IRP是由I/O管理器发出的,I/O管理器是用户态与内核态之间的桥梁,当用户态进程发出I/O请求时,I/O管理器就捕获这些请求,将其转换为IRP请求,发送给驱动程序。I/O管理器无疑是非常重要的,具有核心地位。它负责所有I/O请求的调度和管理工作,根据请求的不同内容,选择相应的驱动程序对象,设备对象,并生成、发送、释放各种不同的IRP。整个I/O处理流程是在它的指挥下完成的。

一个IRP是从非分页内存中分配的可变大小的结构,它包括两部分:IRP首部和辅助请求参数数组,如图1所示。这两部分都是由I/O管理器建立的。

 

IRP简单结构图

图1 IRP简单结构图

IRP首部中包含了指向IRP输入输出缓冲区指针、当前拥有IRP的驱动指针等。

紧接着首部的是一个IO_STACK_LOCATION结构的数组。它的大小由设备栈中的设备数确定(设备栈的概念会在下文中阐述)。IO_STACK_LOCATION结构中保存了一个I/O请求的参数及代码、请求当前对应的设备指针、完成函数指针(IoCompletion)等。

那么,由I/O管理器产生的IRP请求发送到哪去了呢?这里我们就要来说说设备栈的概念了,操作系统用设备对象(device object)表示物理设备,每一个物理设备都有一个或多个设备对象与之相关联,设备对象提供了在设备上的所有操作。也有一些设备对象并不表示物理设备。一个唯软件驱动程序(software-only driver,处理I/O请求,但是不把这些请求传递给硬件)也必须创建表示它的操作的设备对象。设备常常由多个设备对象所表示,每一个设备对象在驱动程序栈(driver stack)中对应一个驱动程序来管理设备的I/O请求。一个设备的所有设备对象被组织成一个设备栈(device stack)。而且,IO_STACK_LOCATION数组中的每个元素和设备栈中的每个设备是一一对应的,一般情况下,只允许层次结构中的每个设备对象访问它自己对应的IO_STACK_LOCATION。无论何时,一个请求操作都在一个设备上被完成,I/O管理器把IRP请求传递给设备栈中顶部设备的驱动程序(IRP是传递给设备对象的,通过设备对象的DriverObject成员找到驱动程序)。驱动程序访问它对应的设备对象在IRP中IO_STACK_LOCATION数组中的元素检查参数,以决定要进行什么操作(通过检查结构中的MajorFunction字段,确定执行什么操作及如何解释Parameters共用体字段的内容)。驱动程序可以根据IO_STACK_LOCATION结构中的MajorFunction字段进行处理。每一个驱动或者处理IRP,或者把它传递给设备栈中下一个设备对象的驱动程序。

 

传递IRP请求到底层设备的驱动程序需要经过下面几个步骤:

1.      为下一个IO_STACK_LOCATION结构设置参数。可以有以下两种方式:

· 调用IoGetNextIrpStackLocation函数获得下个结构的指针,再对参数进行赋值;

· 调用IoCopyCurrentIrpStackLocationToNext函数(如果第2步中驱动设置了IoCompletion函数),或者调用IoSkipCurrentIrpStackLocation函数(如果第2步中驱动没有设置IoCompletion函数)把当前的参数传递给下一个。

2.       如果需要的话,调用IoSetCompletionRoutine函数设置IoCompletion函数进行后续处理。

3.       调用IoCallDriver函数将IRP请求传递给下一层驱动。这个函数会自动调整IRP栈指针,并且执行下一层驱动的派遣函数。

当驱动程序把IRP请求传递给下一层驱动之后,它就不再拥有对该请求的访问权,强行访问会导致系统崩溃。如果驱动程序在传递完之后还想再访问该请求,就必须要设置IoCompletion函数。IRP请求可以再其他驱动程序或者其他线程中完成或取消。

 

当某一驱动程序调用IoCompleteRequest函数时,I/O操作就完成了。这个函数使得IRP的堆栈指针向上移动一个位置,如图2所示:


IRP完成时栈指针的移动

图2 IRP完成时栈指针的移动

图2所示的当C驱动程序调用完IoCompleteRequest函数后I/O栈的情况。左边的实线箭头表明栈指针现在指向驱动B的参数和回调函数;虚线箭头是之前的情况。右边的空心箭头指明了IoCompletion函数被调用的顺序。

如果驱动程序把IRP请求传递给设备栈中的下层设备之前设置了IoCompletion函数,当I/O栈指针再次指回到该驱动程序时,I/O管理器就将调用该IoCompletion函数。

IoCompletion函数的返回值有两种:

(1)STATUS_CONTINUE_COMPLETION:告诉I/O管理器继续执行上层驱动程序的IoCompletion函数。

(2)STATUS_MORE_PROCESSING_REQUIRED:告诉I/O管理器停止执行上层驱动程序,并将栈指针停在当前位置。在当前驱动程序调用IoCompleteRequest函数后再继续执行上层驱动的IoCompletion函数。

当所有驱动都完成了它们相应的子请求时,I/O请求就结束了。I/O管理器从Irp‑>IoStatus.Status成员更新状态信息,从Irp‑>IoStatus.Information成员更新传送字节数。

写的比较仓促,如有不正之处,希望大家指教!

篇3:

代码及EzDriverInstaller下载地址 : http://www.rayfile.com/zh-cn/files/9840cf8f-c41f-11e1-b25b-0015c55db73d/

(编译环境:VS2008+DDK库(参考:Window XP驱动开发(十六) XP下新建驱动程序工程并编译的第二种方法))

 

我有一篇文章是介绍以文件句柄形式调用其它驱动程序的方法:

Window XP驱动开发(十五) 驱动程序调用驱动程序(以文件句柄形式)

现在介绍以设备指针调用其它驱动程序的方法。

 

 

1、通过设备指针调用其他驱动程序

前面介绍了如何使用ZwCreateFile内核函数打开设备,还介绍了如何用ZwReadFile内核函数读取设备。

这些操作和应用程序中的CreateFile和ReadFile 函数的使用很类似。其实,CreateFile和ReadFile这两个API函数分别调用了ZwCreateFile和ZwReadFile内核函数

ZwReadFile内核函数内部会创建IRP_MJ_READ类型的IRP, 然后通过这个IRP传送到相应驱动的派遣函数中。

本节介绍的驱动程序调用其他驱动程序的方法,不是借用ZwCreateFile和ZwReadFile等内核函数,而是“手动”构造各个IRP,

然后将IRP传递到相应的驱动程序的派遣函数里。

1、1  用IoGetDeviceObjectPointer获得设备指针

每个内核中的句柄都会和一个内核对象的指针联系起来。例如,进程对象的句柄和进程对象的指针关联,

线程对象的句柄和线程对象的指针关联,内核事件句柄和内核对象关联。

ZwCreateFile内核函数可以通过设备名打开设备句柄,这个设备句柄和一个文件对象的指针关联。

IoGetDeviceObjectPointer内核函数可以通过设备名获得文件对象指针,而不是获得设备句柄,其声明如下:

[cpp] view plain copy
print?
  1. IoGetDeviceObjectPointer(  
  2.     __in  PUNICODE_STRING ObjectName,  
  3.     __in  ACCESS_MASK DesiredAccess,  
  4.     __out PFILE_OBJECT *FileObject,  
  5.     __out PDEVICE_OBJECT *DeviceObject  
  6.     );  

第一个参数ObjectName:设备名,用UNICODE字符串表示;

第二个参数DesiredAccess :以什么样的权限得到设备句柄;

第三个参数FileObject:同时会返回一个和设备相关的文件对象指针;

第四个参数DeviceObejct:返回的设备对象指针

Windows内核会为每一个对象指针保存一个“引用计数”,当对象被创建时引用计数为1。如果想引用这个对象,计数会加1。

如果删除对象时,Windows先将引用计数减1,如果引用计数不是0,系统不会删除对象。

当调用IoGetDeviceObjectPointer内核函数后,设备对象的引用计数就会加1,当用完这个设备对象后,应用调用ObDereferenceObject内核函数,

使其引用计数减1。

[cpp] view plain copy
print?
  1. #define ObDereferenceObject(a)                                     \  
  2.         ObfDereferenceObject(a)  


当第一次调用IoGetDeviceObjectPointer内核函数时,会根据设备名打开设备,这时文件对象指针计数为1。此后如果再次调用

IoGetDeviceObjectPointer打开设备,就不是真正地打开设备了,而是只将引用计数加1。打开设备时,系统会创建一个

IRP_MJ_CREATE类型的IRP,并将这个IRP传递到驱动程序的派遣函数中。

每次调用ObDereferenceObject内核函数都会将“引用计数”减1,如果减至0就会关闭设备。关闭设备时,系统会创建一个IRP_MJ_CLOSE类型的IRP,

将将其传递到相应驱动的派遣函数中。

从上述内容可以看出IoGetDeviceObjectPointer和ObDereferenceObject内核函数完全正确可以代替ZwCreateFile和ZwCloseFile内核函数。另外,这种方法还能获

得设备对象指针关联的文件对象指针。

1、2 创建IRP传递给驱动的派遣函数

本节介绍如何手动创建IRP,并将 其传递给相应的程序程序。这样的好处是比ZwReadFile内核灵活。ZwReadFile内核函数是针对设备句柄操作的,而传递IRP是通过设备对象的指针操作。

(1)可以通过IoBuildSynchronousFsdRequest和IoBuildAsynchronousFsdRequest两个内核函数创建IRP,它们分别用来创建同步类型的IRP和异步类型的IRP。

这两个内核函数可以创建IRP_MJ_PNP、IRP_MJ_READ、IRP_MJ_WRITE、MJ_FLUSH_BUFFERS和IIRP_MJ_SHUTDOWN类型的IRP。

可以通过IoBuildDeviceIoControlRequest内核函数创建IRP_MJ_INTERNAL_DEVICE_CONTROL和IRP_MJ_DEVICE_CONTROL两个类型的IRP,

这两个内核函数只能创建同步类型的IRP。

另外,还可以使用IoAllocateIrp内核函数,它可以创建任意类型的IRP。IoBuildSynchronousFsdRequest、IoBuildAsynchronousFsdRequest、IoBuildDeviceIoControlRequest这三个内核函数都是属于靠近上层的内核函数。

而IoAllocateIrp是比较底层的内核函数,以下三个内核都是通过IoAllocateIrp实现的。

(2)创建完IRP后,还要构造IRP的I/O堆栈,每层I/O堆栈对应一个设备对象。由于示例程序DriverA是单层驱动程序,所以只需要构造IRP的第一层I/O堆栈。

(3)最后是通过IoCallDriver内核函数调用相应的驱动。IoCallDriver 内核函数会根据IRP的类型,找到相应的派遣函数。

总结一下,手动创建IRP有以下几个步骤:

(1)先得到设备的指针。一种方法是用IoGetDeviceObjectPointer内核函数得到设备对象的指针;

                                             另一种方法是通过ZwCreateFile内核函数先得到设备句柄,然后调用ObReferenceObjectByPointer内核函数通过设备句柄得到设备对象指针。

(2)手动创建IRP,有4个内核函数可以选择,它们是IoBuildSynchronousFsdRequest、IoBuildAsynchronousFsdRequest、IoBuildDeviceIoControlRequest和

         IoAllocateIrq,其中IoAllocateIrp内核函数是最灵活的,使用也最复杂。

(3)构造IRP的I/O堆栈。

(4)调用IoCallDriver内核函数,其内部会调用设备对象的派遣函数。

 

1、3  用IoBuildSynchronousFsdRequest创建IRP

函数声明如下:

[cpp] view plain copy
print?
  1. PIRP  
  2. IoBuildSynchronousFsdRequest(  
  3.     __in  ULONG MajorFunction,  
  4.     __in  PDEVICE_OBJECT DeviceObject,  
  5.     __inout_opt PVOID Buffer,  
  6.     __in_opt ULONG Length,  
  7.     __in_opt PLARGE_INTEGER StartingOffset,  
  8.     __in  PKEVENT Event,  
  9.     __out PIO_STATUS_BLOCK IoStatusBlock  
  10.     );  

第一个参数MajorFunction:这个参数是创建的IRP的主类型,IoBuldSynchronousFsdRequest函数只支持IRP_MJ_PNP、IRP_MJ_READ、IRP_MJ_WRITE、MJ_FLUSH_BUFFERS和IIRP_MJ_SHUTDOWN。

第二个参数DeviceObject:这个参数是设备对象指针,IRP将会传递给这个设备对象。

第三个参数Buffer:对于IRP_MJ_READ和IRP_MJ_WRITE,Buffer指的是输入和输出缓冲区

第四个参数Length:这个参数是缓冲区的大小

第五个参数StartingOffset:这个参数是偏移量;

第六个参数Event:这个参数是同步事件,这个创建同步类型的IRP的关键,后面会有介绍。

使用IoBuildSynchronousFsdRequest内核函数创建同步类型的IRP,关键在于第六个参数Event 。

在调用IoBuildSynchronousFsdRequest之前,需要准备一个事件,这个事件会和IRP请求关联,当IRP请求被结束时该事件被触发。

IoBuildSynchronousFsdRequest和IoBuildAsynchronousFsdRequest内核函数之间的区别就是是否提供事件。

下面的代码演示了如何使用IoBuildSynchronousFsdRequest内核函数创建同步类型IRP(在代码中的DriverB工程中):

[cpp] view plain copy
print?
  1. NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,  
  2.                                  IN PIRP pIrp)   
  3. {  
  4.     KdPrint(("DriverB:Enter B HelloDDKRead\n"));  
  5.     NTSTATUS ntStatus = STATUS_SUCCESS;  
  6.   
  7.     UNICODE_STRING DeviceName;  
  8.     RtlInitUnicodeString( &DeviceName, L"\\Device\\MyDDKDeviceA" );  
  9.   
  10.     PDEVICE_OBJECT DeviceObject = NULL;  
  11.     PFILE_OBJECT FileObject = NULL;  
  12.     //得到设备对象句柄,计数器加1  
  13.     //如果是第一次调用IoGetDeviceObjectPointer,会打开设备,相当于调用ZwCreateFile  
  14.     ntStatus = IoGetDeviceObjectPointer(&DeviceName,FILE_ALL_ACCESS,&FileObject,&DeviceObject);  
  15.   
  16.     KdPrint(("DriverB:FileObject:%x\n",FileObject));  
  17.     KdPrint(("DriverB:DeviceObject:%x\n",DeviceObject));  
  18.   
  19.     // 判断是否成功打开设备  
  20.     if (!NT_SUCCESS(ntStatus))  
  21.     {  
  22.         KdPrint(("DriverB:IoGetDeviceObjectPointer() 0x%x\n", ntStatus ));  
  23.   
  24.         ntStatus = STATUS_UNSUCCESSFUL;  
  25.         // 设置IRP的完成状态  
  26.         pIrp->IoStatus.Status = ntStatus;  
  27.         // 设置IRP操作的字节数  
  28.         pIrp->IoStatus.Information = 0;  // bytes xfered  
  29.         // 将IRP请求结束  
  30.         IoCompleteRequest( pIrp, IO_NO_INCREMENT );  
  31.         KdPrint(("DriverB:Leave B HelloDDKRead\n"));  
  32.   
  33.         return ntStatus;  
  34.     }  
  35.   
  36.     KEVENT event;  
  37.     // 初始化一个同步对象  
  38.     KeInitializeEvent(&event,NotificationEvent,FALSE);  
  39.     IO_STATUS_BLOCK status_block;  
  40.     // 将32位整数转为64位的整数  
  41.     LARGE_INTEGER offsert = RtlConvertLongToLargeInteger(0);  
  42.   
  43.     //创建同步IRP  
  44.     PIRP pNewIrp = IoBuildSynchronousFsdRequest(IRP_MJ_READ,  
  45.                                                 DeviceObject,  
  46.                                                 NULL,0,  
  47.                                                 &offsert,&event,&status_block);  
  48.     KdPrint(("DriverB:pNewIrp:%x\n",pNewIrp));  
  49.   
  50.     // 得到下一层的I/O堆栈  
  51.     PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(pNewIrp);  
  52.     // 设置I/O堆栈的文件对象指针  
  53.     stack->FileObject = FileObject;  
  54.   
  55.     //调用DriverA,会一直调用到DriverA的派遣函数  
  56.     NTSTATUS status = IoCallDriver(DeviceObject,pNewIrp);  
  57.     // 判断操作是否被挂起  
  58.     if (status == STATUS_PENDING)   
  59.     {  
  60.   
  61.         //如果DriverA的派遣函数没有完成IRP,则等待IRP完成  
  62.        status = KeWaitForSingleObject(  
  63.                             &event,  
  64.                             Executive,  
  65.                             KernelMode,  
  66.                             FALSE, // Not alertable  
  67.                             NULL);  
  68.         status = status_block.Status;  
  69.     }  
  70.   
  71.     //将引用计数减1,如果此时计数器减为0,  
  72.     //则将关闭设备,相当于调用ZwClose  
  73.     ObDereferenceObject( FileObject );  
  74.   
  75.   
  76.     ntStatus = STATUS_SUCCESS;  
  77.     // 设置IRP的完成状态  
  78.     pIrp->IoStatus.Status = ntStatus;  
  79.     // 设置IRP的操作字节数  
  80.     pIrp->IoStatus.Information = 0;  // bytes xfered  
  81.     IoCompleteRequest( pIrp, IO_NO_INCREMENT );  
  82.     KdPrint(("DriverB:Leave B HelloDDKRead\n"));  
  83.     return ntStatus;  
  84. }  


测试方法:

(1) 标准驱动DriverA的设计与文章(Window XP驱动开发(十五) 驱动程序调用驱动程序(以文件句柄形式))一样,请参考代码。

(2) DriverB 的设计请参考代码。

(3) 安装HelloDDKA.sys、HelloDDKB.sys(安装方法与Window XP驱动开发(十五) 驱动程序调用驱动程序(以文件句柄形式)一样)

 通过DebugView看到的打印信息如下:

 

1、4  用IoBuildAsynchronousFsdRequest创建IRP

这个内核函数比IoBuildSynchronousFsdRequest内核函数少一个事件参数。

 

1、5 用IoAllocate创建IRP