详解IRP文档格式.docx
《详解IRP文档格式.docx》由会员分享,可在线阅读,更多相关《详解IRP文档格式.docx(66页珍藏版)》请在冰点文库上搜索。
则可能来自于用户模式或者内核模式。
其实不论请求来自那里,对于被请求的驱动程序,所
需要的信息是一样的。
所有的
IRP都包含有下“表一”中的两个部分:
一个用来描述主要
I/O请求的头部
一组用来描述下级请求(有时就被称作子请求,
sub-requests)的参数
GeneratedbyFoxitPDFCreator.FoxitSoftware
Forevaluationonly.
IRPHeader
Parametersforsub-request
Parametersforsub-request
表一:
IRP的结构
其中,头部的数据尺寸是固定,且对所有
IRP都相同的。
而下级请求数组的尺寸则依
赖于会有多少个驱动要来操作这一请求。
IRP头部的内容:
一个
IRP通常会被某个由一些驱动程序组成的栈来执行。
每个
IRP的头部信息,都各自包
含着每个要操作这个
IRP的驱动程序会要使用的数据。
当一个给定的驱动正在处理一个
时,这个驱动就被定义为此
IRP的当前拥有者
(thecurrentowner)。
每一个
IRP的头部,都包含有以下的指针
(Pointer):
指向针对这一
IRP的用以读取输入以及写回输出的缓冲区
指向当前拥有这一
IRP的驱动程序的内存区域
指向一个由当前拥有这一
IRP的驱动提供的例程。
这一例程将由系统在
IRP被取
消时[后]调用。
指向当前的子请求的参数
另外,除了这些指针,
IRP头部还包含有其它数据,用来描述请求的状态和其它内在信息
(微
软未开放的信息
)。
IRP参数组:
在
IRP头部之后是子请求数组。
IRP可以有不只一个子请求,这是由于
IRP通常会是
被由多个[一些]驱动程序组成的栈来操作。
IRP都被分配了一个固定数量的,这样的
子请求,通常是对应于设备栈中的每一个驱动程序有一个。
这个编号恰好与相应栈最顶端设
备的
StackSize相匹配[一致],虽然在栈中间的驱动其实可以被分配的小一点。
还有,如果
一个驱动程序需要把请求发送到一个不同的设备栈处理,这个驱动程序就必须重新分配[创
建]一个新的
IRP。
每一个子请求都表示一个对应的
I/O栈的位置
(一个类型为
IO_STACK_LOCATION的结构),
并且特定的
IRP通常[典型的
]为每一个其被发送到的目的设备栈上的每个驱动仅维持
[包含
]
一个这样的栈位置信息。
IRP头部的一个域中还包含有标识当前正在被使用的
I/O栈位置
信息。
这个域的值被称作
IRP栈指针
(IRPstackpointer)或者当前栈位置
(currentstack
location)。
对于特定的
IO_STACK_LOCATION结构,会包含有以下内容:
l对于特定
IRP的主要以及次要的方法代码
l针对这些代码的参数
l一个指向相应驱动的设备对象(
deviceobject)的指针
l一个指向
IoCompletion例程的指针,如果驱动有过设定
l一个指向和这一请求相关联的文件对象的指针
l一些可变标志以及上下文
[关联]区域
特定的
IO_STACK_LOCATION结构并不包含有指针标识
[指向]输入和输出位置,这些(输
入、输出)指针本身就由
IRP自身所包含。
所有的子请求操作都使用同一缓冲区进行。
第二重定义:
IRP作为一个线程无关的调用栈
进行一个设备的
I/O操作通常[典型的
]需要调用这一设备相关的不止一个驱动。
每一个和这
一设备相关的驱动都会创建一个设备对象
(deviceobject),并且这些设备对象会垂直压入(排
列进)一个设备栈
(devicestack)中。
IRP会在设备栈中从上到下的一个个被传递过去(进去)。
对于栈中的每一个驱动,
IRP都会用(包含)一个指针标识一个栈位置。
由于驱动可以异步
的处理请求,因此
IRP就像是一个线程无关的调用栈一样,就像表二中所表示的:
ThreadStack
Parameter1forA
Parameter2forA
Returnaddresstoinitiator
…
Parameter1forB
Parameter2forB
ReturnaddresstoA
Parameter1forC
Parameter2forC
ReturnaddresstoB
Parameter1forAIoCompletionroutinefor
initiatorParameter2forA
Parameter1forBParameter2forB
Parameter1forCParameter2forC
IoCompletionroutineforA
IoCompletionroutineforB
表二:
IRP作为线程无关的调用栈
在表二中,左侧,线程栈表示了驱动程序
A,B和
C的参数和返回值地址是如何在调用栈
(call
stack)中被组织的;
而右侧,图表展示了这些参数和返回值地址是如何在一个
IRP中对应到
I/O栈位置以及
IoCompletion例程上的。
IRP操作所具有的异步特质对于操作系统和
Windows驱动模型
(WDM,WindowsDriver
Model)是非常关键的。
在一个同步的,单线程的
I/O结构设计中,发送了请求的应用程序以
及传递请求的驱动程序都要在更低层的组件完成请求任务之前处于等待中。
这样的设计对于
运用系统资源是很没有效率的,会降低整个系统的性能。
IRP的结构提供了一种可被继承的异步方式的设计,使得应用程序能够将一个或多个
I/O请
求排入队列,而不必等待。
I/O请求被处理的过程中,这个应用程序被解放出来可以进行
其他的计算,或者向队列里排入更多的
由于所有被用来处理请求的信息都被以
的形式封装起来了,所以请求线程的调用栈可以从
I/O请求中分离出去。
将
IRP传递到下一级的驱动程序
IRP传递到下一级驱动程序
(又被称作为转发
IRP)是指
IRP等价于一个子例程调用。
当驱
动转发一个
IRP,它(驱动程序)必须向
IRP参数组增加下一个
I/O栈位置,告知这一
栈的指针,然后调用下一驱动的分发例程(dispatchroutine)。
基本说来,就是驱动向下调用
IRP栈(callingdowntheIRPstack)。
传递一个
IRP,驱动通常(典型的
)会采取以下几个步骤:
1.
建立下一个
I/O栈位置的参数。
驱动程序可以采取:
调用
IoGetNextIrpStackLocation例程来得到一个指针指向下一个
I/O栈位置,
然后将请求参数组复制到那个得到的位置。
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/OManager)调用
IoCompletion例程时,这个驱动就能够在
IoCompletion例程执行期
间重新获得对这一
IRP的所有权。
如此,IoCompletion例程就能够访问
IRP中的域。
若是驱动的分发例程也还须在
IRP被后面的驱动处理完成之后再处理它,这个
IoCompletion
例程必须返回
STATUS_MORE_PROCESSING_REQUIRED,以将
IRP的所有权返回给分发
例程。
如此一来,
I/O管理器会停止
IRP的处理,将最终完成
IRP的任务留给分发例程。
分
发例程能够在之后调用
ICompleteRequest来完成这个
IRP,或者还能将这个
IRP标记为等候
进一步处理。
完成(齐整)一个
IRP
当输入、输出操作(
I/O)完成时,完成这个
I/O操作的驱动会调用
IoCompleteRequest例程。
这个例程将
IRP栈指针移到指向
IRP栈中的前一个(更上面)的位置,如表三所示。
stack
pointer
completion
Parameter1forACallbackforinitiatorParameter2forA
Parameter1forBParameter2forBIoCompletionroutineforA
Parameter1forCParameter2forCIoCompletionroutineforB
CurrentI/Ostacklocation
IoStatus.Information
IoStatus.Status
表三:
IRP的完成过程及栈指针
表三表示出了在驱动
C调用
IoCompleteRequest后的当前栈位置。
左侧的实线箭头表示出栈
指针现在指向了驱动
B的参数组和回调函数。
点虚线箭头表示出前一个栈位置。
右侧的中
空箭头表示出
IoCompletion例程被调用的顺序。
为了解释上的方便,本文把
IRP中
I/O栈位置的顺序表示成“倒置”的,就是说,是
从
A到
C的反向顺序,而不是从
C到
A。
用这样的倒置图表是为了更直观用“向下”将调
用在栈中沿向下的方向进行表现出来。
如果一个驱动在设备栈中向下传递
IRP时设定了
IoCompletion例程,I/O管理器就会在
栈指针再次指向这一驱动的这个
I/O栈位置时调用此例程。
IoCompletion例程就
表现成为,当
IRP在设备栈中传递时,操作
IRP的那些驱动的返回地址。
IoCompletion例程能够返回两个状态值中的任一个:
STATUS_CONTINUE_COMPLETION–继续向上完成特定
I/O管理器提升
IRP栈指针,并且调用上一个驱动的
STATUS_MORE_PROCESSING_REQUIRED–中断向上完成特定
IRP的过程,并
且把
IRP栈指针留在当前位置。
返回这一状态的驱动通常会在过后调用
IoCompleteRequest例程来重新开始向上完成特定
IRP的过程。
当每一个驱动都完成了它对应的子请求,
I/O请求就完成了。
I/O管理器从
Irp->
IoStatus.Status
域取回请求的状态信息,并且从
IoStatus.Information域取回传输的字节数。
同步(化)I/O响应
虽然
Windows操作系统是为异步
I/O设计的,大多数应用程序还是发送同步
驱动
程序也能够即发送同步又发送异步请求,同时也能响应不论是同步还是异步请求。
要判断一个请求是异步完成的还是同步的,驱动需要检查由
IoCallDriver返回的状态,如下
面的例程序所示:
TKEVEN;
ntve.
t,enev(&
ntveeEizaltiKeIni,ntvenEioaticifotN);
FALS.
);
rp(IxtNeToonticaLocktapSIrntreuryCIoCo.
p,Ire(inutRoontilempCoIoSe.
netioupRIrchat.
t,enev.
E,RU.
ERU.
;
sstatu=t,ecbjeOicev(DerivDrllCaIo);
rp.
/
//Chkecrfosouonhrncsyorsouonhrncsyan.letiompc.
ifusatst(==)NGDIEN_PUSATST.
//eodCtoedlanhusnorochynasenspoes.
//edttmioorf.ow.
nretur;
usats.
驱动程序初始化一个事件,设置
I/O栈位置,设置一个
IoCompletion例程,再调用
IoCallDriver
去转发
由
IoCallDriver返回来的状态标示了后面(底下)的驱动是否在同步操作这个
IRP,还是异步的。
如果请求是被异步方式操作的,
IoCallDriver返回
STATUS_PENDING。
如果后面的驱动以同步方式响应,
IoCallDriver会返回一个由更后面(下面)一驱动返回的
完成状态信息。
如同例程序所示,驱动只是简单的返回同一个完成状态。
当一个
IRP被同步的完成,则驱动由它的分发例程返回这个
IRP的完成状态。
在它上面的
那些驱动可以通过下列两种方法得到这个状态:
l在分发例程中,由
IoCallDriver返回的值获得。
l在
IoCompletion例程中,由
IRP的
IoStatus.Status域获得。
I/O管理器调用某个驱动的
IoCompletion例程,这个驱动就拥有了相应(特定)的
IRP,
从而能够访问到
IoStatus.Status域。
如果驱动没有设置
IoCompletion例程,它就不会在调用
IoCallDriver后拥有相应的
IRP,因此决不能访问
表四展现了驱动程序或者应用程序能够获得
IRP状态的两种方法。
为了解释的方便,表中
将那些
IoCompletion例程以及他们所调用(被调用?
)的参数组放在了同一个
而不是一个跟低的位置。
Status=IoCallDriver(?
;
Status=Irp->
IoStatus.Status;
STATUS_ERROR
STATUS_RETRY
Parameter1forAParameter2forA
IoCompletionroutinefor
initiator
STATUS_SUCCESS
表四:
IoCallDriver例程返回的状态,以及对
IoCompletion例程的可用性
在表四的左侧,
IoCallDriver例程返回了由后面(下面?
)一个驱动报告上来的完成状态。
在右侧,IoCompletion例程由
IoStatus.Status域读出那个状态。
若是
IRP以同步方式
完成,每一个驱动的
IoCompletion例程都会在
IoCallDriver之前返回,从而使得
例程比分发例程更早得到状态的值。
表四所示驱动
C返回
STATUS_SUCCESS,驱动
B返回
STATUS_RETRY,而驱动
A返回
STATUS_ERROR。
IRP的最终状态只能够由初始的请求发出者得到,其他的驱动只能
够读到由后面一个驱动返回的状态。
异步(化)IO响应
驱动程序应该在当它无法以一个时间区间
(Timelymanner)以同步方式完成一个
IO请求时,
由分发例程返回
STATUS_PENDING状态。
理解何时该返回
STATUS_PENDING对很多驱动
开发者都是一个问题。
一个驱动程序,因该在以下的情况返回
STATUS_PENDING:
l当它的分发例程有可能会在一个
IRP被完成以前就返回
l它会在另外一个线程中完成特定的
l它的分发线程在返回之前无法判断某个
IRP是否完成
这个驱动程序必须在它释放对特定
IRP的控制,以及在它返回
STATUS_PENDING之前,调
用
IoMarkIrpPending宏。
IoMarkIrpPending会在当前的
IO栈位置的
Control域,置
SL_PENDING_RETURNED位(bit)。
每当一个
IO栈位置被完成,
IO管理器都会将此位的值
复制给特定
IRP头部的
PendingReturned域,如表五所示:
pointer
PendingReturned
ControlforA
ControlforB
IoMarkIrpPending(Irp);
SL_PENDING_RETURNED
ControlforC
表五:
准备待处理(
Pending)位
在表五中,驱动
C对
IoMarkIrpPending宏的调用将驱动
C的
IO栈位置中的
Control域上的
SL_PENDING_RETURNED置位。
当驱动
C完成了对