操作 IRP: 所有驱动开发人员都要知道的知识 (Draft)
翻译:Puffel
2007-8-3
概要
在微软的 Windows 操作系统家族中,都通过发送 I/O 请求包(IRP, I/o Request Packets)来进行
和驱动程序的通讯。用来封装 IRP 的数据结构不仅仅用来描述一个 I/O 操作的请求本身的内
容,还要用来维护这一请求在一系列驱动程序中传递的过程中的相关状态信息
....
。实现这一数
据结构其实是为了两重目的,也就是说 IRP 可以被定义[理解]为:
l 一个放置 I/O 请求的容器
或者
l 一个与线程无关的调用栈
从以上这两个角度来考虑 IRP,将有助于驱动开发人员理解作为驱动,程序应该如何去正确
响应发来的 I/O 请求。
本文中关于例程(routines)和问题的讨论,请参考最新的微软 Windows 驱动开发包(WDK,
Microsoft Windows Driver Kit)。
第一重定义:
IRP
是一个放置
I/O
请求的容器
操作系统以 IRP 的形式进行绝大多数发给驱动程序的 I/O 操作请求。因为下列特点,使得 IRP
非常适合于这一目的:
l IRP 设计来可以被异步操作
l IRP 可以被在完成前取消
l IRP 设计来适合于需要多驱动协同完成的输入输出操作
IRP 数据结构封装了一个被请求
...
的驱动所需要的,用来回复这一请求的[全部]信息。而请求
则可能来自于用户模式或者内核模式。其实不论请求来自那里,对于被请求的驱动程序,所
需要的信息是一样的。
所有的 IRP 都包含有下“表一”中的两个部分:
l 一个用来描述主要 I/O 请求的头部
l 一组用来描述下级请求(有时就被称作子请求,sub-requests)的参数
Generated by Foxit PDF Creator © Foxit Software
http://www.foxitsoftware.com For evaluation only.
IRP Header
Parameters for sub-request
.
.
.
Parameters for sub-request
Parameters for sub-request
表一: IRP 的结构
其中,头部的数据尺寸是固定,且对所有 IRP 都相同的。而下级请求数组的尺寸则依
赖于会有多少个驱动要来操作这一请求。
IRP
头部的内容:
一个 IRP 通常会被某个由一些驱动程序组成的栈来执行。每个 IRP 的头部信息,都各自包
含着每个要操作这个 IRP 的驱动程序会要使用的数据。当一个给定的驱动正在处理一个 IRP
时,这个驱动就被定义为此 IRP 的当前拥有者(the current owner)。
每一个 IRP 的头部,都包含有以下的指针(Pointer):
l 指向针对这一 IRP 的用以读取输入以及写回输出的缓冲区
l 指向当前拥有这一 IRP 的驱动程序的内存区域
l 指向一个由当前拥有这一 IRP 的驱动提供的例程。这一例程将由系统在 IRP 被取
消时[后]调用。
l 指向当前的子请求的参数
另外,除了这些指针,IRP 头部还包含有其它数据,用来描述请求的状态和其它内在信息(
微
软未开放的信息
)。
IRP
参数组:
在 IRP 头部之后是子请求数组。一个 IRP 可以有不只一个子请求,这是由于 IRP 通常会是
被由多个[一些]驱动程序组成的栈来操作。每一个 IRP 都被分配了一个固定数量的,这样的
子请求,通常是对应于设备栈中的每一个驱动程序有一个。这个编号恰好与相应栈最顶端设
备的 StackSize 相匹配[一致],虽然在栈中间的驱动其实可以被分配的小一点。还有,如果
一个驱动程序需要把请求发送到一个不同的设备栈处理,这个驱动程序就必须重新分配[创
建]一个新的 IRP。
每一个子请求都表示一个对应的 I/O 栈的位置(一个类型为 IO_STACK_LOCATION 的结构),
并且特定的 IRP 通常[典型的]为每一个其被发送到的目的设备栈上的每个驱动仅维持[包含]
一个这样的栈位置信息。在 IRP 头部的一个域中还包含有标识当前正在被使用的 I/O 栈位置
信息。这个域的值被称作 IRP 栈指针(IRP stack pointer)或者当前栈位置(current stack
Generated by Foxit PDF Creator © Foxit Software
http://www.foxitsoftware.com For evaluation only.
location)。
对于特定的 IO_STACK_LOCATION 结构,会包含有以下内容:
l 对于特定 IRP 的主要以及次要的方法代码
l 针对这些代码的参数
l 一个指向相应驱动的设备对象(device object)的指针
l 一个指向 IoCompletion 例程的指针,如果驱动有过设定
l 一个指向和这一请求相关联的文件对象的指针
l 一些可变标志以及上下文[关联]区域
特定的 IO_STACK_LOCATION 结构并不包含有指针标识[指向]输入和输出位置,这些(输
入、输出)指针本身就由 IRP 自身所包含。所有的子请求操作都使用同一缓冲区进行。
第二重定义:
IRP
作为一个线程无关的调用栈
进行一个设备的 I/O 操作通常[典型的]需要调用这一设备相关的不止一个驱动。每一个和这
一设备相关的驱动都会创建一个设备对象(device object),并且这些设备对象会垂直压入(排
列进)一个设备栈(device stack)中。IRP 会在设备栈中从上到下的一个个被传递过去(进去)。
对于栈中的每一个驱动,IRP 都会用(包含)一个指针标识一个栈位置。由于驱动可以异步
的处理请求,因此 IRP 就像是一个线程无关的调用栈一样,就像表二中所表示的:
Parameter1 for A
IoCompletion routine for
initiator
Parameter2 for A
Parameter1 for B Parameter2 for B
Parameter1 for C Parameter2 for C
IoCompletion routine for A
IoCompletion routine for B
IRP Header
Thread Stack
Parameter1 for A
Parameter2 for A
Return address to initiator
…
Parameter1 for B
Parameter2 for B
Return address to A
…
Parameter1 for C
Parameter2 for C
Return address to B
表二:IRP 作为线程无关的调用栈
在表二中,左侧,线程栈表示了驱动程序 A,B 和 C 的参数和返回值地址是如何在调用栈(call
stack)中被组织的;而右侧,图表展示了这些参数和返回值地址是如何在一个 IRP 中对应到
I/O 栈位置以及 IoCompletion 例程上的。
IRP 操作所具有的异步特质对于操作系统和 Windows 驱动模型(WDM, Windows Driver
Model)是非常关键的。在一个同步的,单线程的 I/O 结构设计中,发送了请求的应用程序以
及传递请求的驱动程序都要在更低层的组件完成请求任务之前处于等待中。这样的设计对于
运用系统资源是很没有效率的,会降低整个系统的性能。
Generated by Foxit PDF Creator © Foxit Software
http://www.foxitsoftware.com For evaluation only.
IRP 的结构提供了一种可被继承的异步方式的设计,使得应用程序能够将一个或多个 I/O 请
求排入队列,而不必等待。在 I/O 请求被处理的过程中,这个应用程序被解放出来可以进行
其他的计算,或者向队列里排入更多的 I/O 请求。由于所有被用来处理请求的信息都被以 IRP
的形式封装起来了,所以请求线程的调用栈可以从 I/O 请求中分离出去。
将 IRP 传递到下一级的驱动程序
将 IRP 传递到下一级驱动程序(又被称作为转发 IRP)是指 IRP 等价于一个子例程调用
。当驱
动转发一个 IRP,它(驱动程序)必须向 IRP 参数组增加下一个 I/O 栈位置,告知这一 IRP
栈的指针,然后调用下一驱动的分发例程(dispatch routine)。基本说来,就是驱动向下调用
IRP 栈(calling down the IRP stack)。
传递一个 IRP,驱动通常(典型的)会采取以下几个步骤:
1. 建立下一个 I/O 栈位置的参数。驱动程序可以采取:
l 调用 IoGetNextIrpStackLocation 例程来得到一个指针指向下一个 I/O 栈位置,
然后将请求参数组
复制到那个得到的位置。
l 调用 IoCopyCurrentIrpStackLocationToNext 例程(如果驱动按第二步设置了
IoCompletion 例程),或者 IoSkioCurrentIrpStackLocation 例程(如果没有在第二
步设置 IoCompletion 例程)来传递当前位置所使用的同样的参数组
。
注意:驱动程序不能够使用 RtlCopyMemory 例程来复制当前的参数组。这个
例 程 把 指 针 复 制 到 当 前 驱 动的 IoCompletion 例 程 , 而 且 这 样 会 导 致
IoCompletion 例程被调用不止一次(重入?)。
2. 如果需要的话,调用 IoSetCompletionRoutine 例程,为后期处理(post-porcessing)设
置一个 IoCompletion 例程。如果驱动设置了 IpCompletion 例程,那么他在上一步(第
一步)中必须使用 IoCopyCurrentIrpStackLocationToNext。
3. 通过调用 IoCallDriver 例程将请求传递到下一个驱动。这个例程会自动通告 IRP 栈
指针,并且调用下一个驱动的分发例程。
在驱动程序将 IRP 传递个下一个驱动之后,就不再拥有这个 IRP,并且不能试图再去访问这
个它。否则会导致系统崩溃。那个 IRP 会被其它的驱动或者线程释放或完成。如果驱动需
要访问一个已经在栈里传下去的 IRP,这个驱动必须实现(设置)IoCompletion 例程。当 I/O
管理器(I/O Manager)调用 IoCompletion 例程时,这个驱动就能够在 IoCompletion 例程执行期
间重新获得对这一 IRP 的所有权。如此,IoCompletion 例程就能够访问 IRP 中的域。
若是驱动的分发例程也还须在 IRP 被后面的驱动处理完成之后再处理它,这个 IoCompletion
例程必须返回 STATUS_MORE_PROCESSING_REQUIRED,以将 IRP 的所有权返回给分发
例程。如此一来,I/O 管理器会停止 IRP 的处理,将最终完成 IRP 的任务留给分发例程。分
发例程能够在之后调用 ICompleteRequest 来完成这个 IRP,或者还能将这个 IRP 标记为等候
进一步处理。
Generated by Foxit PDF Creator © Foxit Software
http://www.foxitsoftware.com For evaluation only.
完成(齐整)一个
IRP
当输入、输出操作(I/O)完成时,完成这个 I/O 操作的驱动会调用 IoCompleteRequest 例程。
这个例程将 IRP 栈指针移到指向 IRP 栈中的前一个(更上面)的位置,如表三所示。
Parameter1 for A Callback for initiatorParameter2 for A
Parameter1 for B Parameter2 for B
Parameter1 for C Parameter2 for C
IoCompletion routine for A
IoCompletion routine for B
IRP Header
Current I/O stack location
IoStatus.Information
IoStatus.Status
IRP
stack
pointer
IRP
completion
表三:IRP 的完成过程及栈指针
表三表示出了在驱动 C 调用 IoCompleteRequest 后的当前栈位置。左侧的实线箭头表示出栈
指针现在指向了驱动 B 的参数组和回调函数。点虚线箭头表示出前一个栈位置。右侧的中
空箭头表示出 IoCompletion 例程被调用的顺序。
注意:为了解释上的方便,本文把 IRP 中 I/O 栈位置的顺序表示成“倒置”的,就是说,是
从 A 到 C 的反向顺序,而不是从 C 到 A。用这样的倒置图表是为了更直观用“向下”将调
用在栈中沿向下的方向进行表现出来。
如果一个驱动在设备栈中向下传递 IRP 时设定了 IoCompletion 例程,I/O 管理器就会在 IRP
栈指针再次指向这一驱动的这个 I/O 栈位置时调用此例程。如此一来,IoCompletion 例程就
表现成为,当 IRP 在设备栈中传递时,操作 IRP 的那些驱动的返回地址。
一个 IoCompletion 例程能够返回两个状态值中的任一个:
l STATUS_CONTINUE_COMPLETION – 继续向上完成特定 IRP。I/O 管理器提升
IRP 栈指针,并且调用上一个驱动的 IoCompletion 例程。
l STATUS_MORE_PROCESSING_REQUIRED – 中断向上完成特定 IRP 的过程,并
且把 IRP 栈指针留在当前位置。返回这一状态的驱动通常会在过后调用
IoCompleteRequest 例程来重新开始向上完成特定 IRP 的过程。
当每一个驱动都完成了它对应的子请求,I/O 请求就完成了。I/O 管理器从 Irp->IoStatus.Status
域取回请求的状态信息,并且从 Irp->IoStatus.Information 域取回传输的字节数。
同步(化)
I/O
响应
虽然 Windows 操作系统是为异步 I/O 设计的,大多数应用程序还是发送同步 I/O 请求。驱动
程序也能够即发送同步又发送异步请求,同时也能响应不论是同步还是异步请求。
Generated by Foxit PDF Creator © Foxit Software
http://www.foxitsoftware.com For evaluation only.
评论0