驱动杂记2:分层驱动,IRP ,I/O堆栈初步印象

   分层驱动是指两个或者两个以上的驱动程序,他们分别创建设备对象,并且形成一个由高到低的

设备对象栈。IRP请求一般会被传送到设备栈的最顶层设备对象,顶层设备对象可以选择直接结束IRP

请求,也可以选择将IRP请求向下层设备转发。如果是向下层设备转发,当IRP请求结束时,IRP会顺着

设备栈的反方向原路返回。当得知下层驱动程序已经结束IRP请求时,本层设备对象可以选择继续向上

返回,也可以重新选择将IRP再次传递给底层设备驱动。

     两层设备之间通常用IRP传递请求。IRP由I/O管理器创建发出,I/O管理器是用户态与内核态之间的桥梁,

I/O管理器捕获用户态请求,将其转换为IRP请求,发送给驱动程序。

        以CreateFile为例来理解(仅供参考),假设要打开一个文件"c:\filiename"这个名字在用户态下有意义,

它有一个symbol link(符号链接)"\??\filiename”,在内核模式下它的真实名字为"\Device\HarddiskVolume1\filiename"

用户态函数只能通过符号链接来打开某个设备,I/O管理器捕获到这个请求,调用对象管理器(Object Manager)

查找到文件的符号链接名(对象管理器中会得知用户态中c:对应的设备对象为\Device\HarddiskVolume1),

同时调用安全引用监视器检查该子系统是否有打开设备对象的权限。获取符号链接后,调用ZwCreateFile

完成打开设备的工作,并且得到一个文件句柄。ZwCreateFile通过系统服务调用内核函数NtCreateFile.在

NtCreateFile执行过程中,执行到nt!IoPorseDevice中调用t!IoGetAttachedDevice,通过PDO获得键盘设备栈最顶端的

设备对象,用得到的这个设备对象的StackSize(IO_STACK_LOCATION的大小)成员作为参数来调用函数

IoAllocateIrp创建IRP,调用nt!ObCreateObject创建文件对象,并初始化这个文件对象。调用nt!IoCallDriver将IRP发往驱动。

驱动处理完这个 IRP 之后,返回 nt!IopParseDevice 继续执行。

     初步了解了IRP的创建,我们来看一下这个IRP的结构。IRP包括两个部分,首部和辅助。首部包括指向IRP输入输出

缓冲区的指针,当前拥有IRP驱动的指针,IO_STACK_LOCATION的数组索引,同时也有一个指向该IO_STACK_LOCATION

的指针。索引是从1开始,没有0。紧接首部的是一个IO_STACK_LOCATION(I/O堆栈单元)数组,它的大小由设备栈中

设备数决定。IO_STACK_LOCATION的每个元素和每个设备一一对应。

 IRP的结构的大小是不固定的,大体结构如下:
--------------------
| IRP header |
--------------------
|IO_STACK_LOCATION |<-----lowest driver stack location #index1
--------------------
|IO_STACK_LOCATION |<-----next higher stack location #index2
--------------------
......

|IO_STACK_LOCATION |<-----topmost driver stack location #indexn        //类似栈,遵循先进后出

      设备对象收到IRP请求通过DriverObject成员找到驱动程序,驱动程序访问它对应的设备对象在IO_STACK_LOCATION

数组中元素检查参数,以决定如何操作(如MajorFunction对应的派遣函数)

typedef struct _IO_STACK_LOCATION {
  UCHAR  MajorFunction;//该字段定义了一个函数功能集,对应派遣函数

UCHAR  MinorFunction;//MinorFunction提供了MajorFunction更详细的信息
UCHAR  Flags;         //Flags提供了函数期望驱动执行的附加信息
CHAR  Control; //当内核驱动异步处理IRP时,驱动可以通过调用IoMarkIrpPending()将IRP标记为Pending状态,以排队IRP处理

union Parameters;//是几个子结构的联合,每个请求类型都有自己专用的参数,而每个子结构就是一种参数

PDEVICE_OBJECT DeviceObject;//与该堆栈单元对应的设备对象地址,由IoCallDriver函数填写

PFILE_OBJECT FileObject; //内核文件对象地址

PIO_COMPLETION_ROUTINE ComPletionRoutine; //IO完成例程地址,由与这个堆栈单元对应的驱动程序的上一层驱动设置

PVOID Context;  //作为参数传递给完成例程
}IO_STACK_LOCATION

  我们再来看IRP在分层驱动中的传递。当驱动程序准备向次低层驱动程序传递IRP时可以调用IoCallDriver例程,

它其中的一个工作是递减当前IO_STACK_LOCATION的索引,使之与下一层的驱动程序匹配。但该索引不会设置成0

,如果设置成0,系统将会崩溃。就是说,最底层的驱动程序不会调用IoCallDriver例程。

  假设有一个请求,I/O管理器为这个请求创建了IRP并分配了4个IO_STACK_LOCATION 单元。

NT I/O管理器将IRP头中的StackCount字段初始化为IRP中Stack Location的总数。IRP头中的

CurrentLocation字段初始化为StackCount+1(即在这个分配了4个IO_STACK_LOCATION

 单元的IRP中CurrentLocation被初始化为4+1=5),每一次通过IoCallDriver()调用驱动的派遣函数时,这个值减1。

当I/O管理器在调用驱动时,它总是将IRP的StackLocation指针指向下一个StackLocation;当被调用的驱动释放IRP时,

Stack Location真正被指回到前一个Stack Location。因此当过滤驱动的派遣函数运行时,I/O管理器使用Stack Location #4,

也就是分配的最后一个Stack Location。

好了,我们来看传递步骤

1.为下一个IO_STACK_LOCATION结构设置参数 可以通过IoGetNextIrpStackLocation获得下个结构的指针

   如果需要设置完成例程,调用IoCopyCurrentIrpStackLocationToNext宏

    如果不设置完成例程,调用IoSkipCurrentIrpStackLocation  宏(CurrentLocation+1)过滤驱动常用

2.根据需要设置完成例程

3调用IoCallDriver函数将IRP请求传递给下一层驱动。此时当前驱动不再拥有该IRP的访问权,如果还需要访问必须设置

完成例程。

通过以上整理,对分层驱动,IRP的运转机制,I/O堆栈有一个初步的概念上的了解,当然实际运用中会涉及到一些细节上

的问题,在以后继续的使用当中来完善这些知识点。


你可能感兴趣的:(工作,IO,object,manager,struct,header)