一、驱动程序层次结构
在《Windows驱动开发详解》的第四章简单介绍了一下驱动程序的层次结构,但介绍得不清不楚,反复看了几遍,仍然是一分清楚,九分糊涂。为此,花了几个小时来查阅相关资料,最后分别参考《Windows驱动开发详解》和《Windows操作系统原理第2版》,才算有了个初步的认识。
要想详细解释驱动程序的层次结构,以我现在的水平可能还没那个能力,但或许能通过文字的形式让自己多一分认识。
1.再看DriverEntry和HelloDDKDispatchRoutine函数
要想理解驱动程序的层次结构,还得再来看看第一个驱动程序的大致流程。我们知道,驱动程序的入口函数是DriverEntry,而在DriverEntry函数中,对DRIVER_OBJECT结构体进行了初始化。其中MajorFunction成员是用来设置IRP对应的派遣函数,这样使用该驱动程序进行不同的I/O请求时,就会相应相应的派遣函数。下面是DriverEntry函数中对MajorFunction成员的初始化设置:
pDriverObject->MajorFunction[IRP_MJ_CREATE] = ( PDRIVER_DISPATCH ) HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = ( PDRIVER_DISPATCH ) HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = ( PDRIVER_DISPATCH ) HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_READ] = ( PDRIVER_DISPATCH ) HelloDDKDispatchRoutine;
通过以上的初始化,在不同的处理不同的IRP时就会响应相应的派遣函数,接着我们就可以来看看这个传说中的派遣函数是什么样的了:
NTSTATUS HelloDDKDispatchRoutine( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp )
{
KdPrint( ( "Enter HelloDDKDispatchRoutine!/n" ) );
NTSTATUS status = STATUS_SUCCESS;
//完成IRP
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;
IoCompleteRequest( pIrp, IO_NO_INCREMENT );
KdPrint( ( "Leave HelloDDKDispatchRoutine!/n" ) );
return status;
}
函数体很简单,而在这个函数中,有一个很重要的结构体,那就是函数参数中的PIRP结构体。PIRP是一个很复杂的结构体,在以上这个派遣函数中,只用到了其中的一个结构体成员。那这个结构体中到底包含了些什么数据呢?这个很重要!
2.PIRP结构体
参考《Windows操作系统原理 9.2.4》中的解释,该结构体主要包含IRP请求类型、用户缓冲区首地址、用户请求数据的长度等信息,除此之外还包括这个IRP请求的处理结构,比如以上代码中的“ pIrp->IoStatus.Status = status;”。
PIRP结构体有十多个成员,可以把该结构体分为两个部分:固定部分和一个I/O堆栈单元。其中固定部分主要包含一些IRP请求的基本信息,对于这一部分暂且略过。重中之重是I/O堆栈单元,因为我需要通过了解这一部分来弄清驱动程序的层次结构。
3.IO_STACK_LOCATION结构体(I/O堆栈单元)
I/O堆栈单元由IO_STACK_LOCATION定义,每一个堆栈单元都对应一个设备对象。我们知道,在一个驱动程序中,可以创建一个或多个设备对象,而这些设备对象都对应着一个IO_STACK_LOCATION结构体,而在驱动程序中的多个设备对象,而这些设备对象之间的关系为水平层次关系。好,到此可以理解驱动程序中的水平层次结构了,但还有一个垂直层次的结构需要理解。
还得再说说IO_STACK_LOCATION这个结构体,从wdm.h中看了下这个结构体的定义,不看不知道,居然有几十个成员。还是到《Windows操作系统原理》中看看几个重要成员的作用吧:
typedef struct _IO_STACK_LOCATION {
UCHAR MajorFunction;
UCHAR MinorFunction;
UCHAR Flags;
UCHAR Control;
union Parameters;
PDEVICE_OBJECT DeviceObject;
PFILE_OBJECT FileObject;
PIO_COMPLETION_ROUTINE CompletionRoutine;
PVOID Context;
通过以上的整理,发现之前所说的几十个成员都是Parameters联合中的,这就好说了,现在一个一个的来了解这些成员的作用,还是回到《Windows操作系统原理》中。
MajorFuncion是该IRP的主要功能,它指出了I/O请求的操作类型。
MinorFunction是该IRP的副功能,这个参数在《Windows驱动开发详解》中没作详解的解释,我也就先不作了解。
parameters是多个结构体的联合,再到wdm.h中看了看,忽然明白了什么,原来这个联合中的每一个结构体,都包含了每一个IRP类型的数据,可以看看IRP_MJ_CREATE这个IRP相应的结构体定义:
struct {
PIO_SECURITY_CONTEXT SecurityContext;
ULONG Options;
USHORT POINTER_ALIGNMENT FileAttributes;
USHORT ShareAccess;
ULONG POINTER_ALIGNMENT EaLength;
} Create;
这个结构体中具体的数据就先不管了,至少已经知道parameters中存放的是些什么数据。
DeviceObject是当前IRP对应的设备对象的地址。
FileObject是指向与一个I/O请求有关的文件对象地址。对这个参数没什么认识,先略过。
CompletionRoutine是一个I/O完成例程的地址。该地址是由与这个堆栈单元对应的驱动程序的更上一层驱动程序设置的,驱动程序不要直接设置这个域。这段文字是直接抄《Windows操作系统原理》上的,不太理解。
Context。是一个任意的与上下文相关的值,将作为参数传递给完成例程。
在《Windows操作系统原理》中没有对Flags和Control这两个参数作介绍。
通过以上的整理,对于PIRP的数据结构虽然不能清楚认识,但至少也有了一定的理解。之所以要熟PIRP的数据结构,是因为要了解IO_STACK_LOCATION这个结构体,而了解IO_STACK_LOCATION结构体的目的是为了明白驱动程序的层次结构。
4.入正题,了解驱动程序的层次结构
前面已经说到,每个驱动程序中的多个设备对象的关系是水平层次关系,而在了解了IO_STACK_LOCATION结构体后,就要说说驱动程序中的垂直层次结构了。
在认识垂直层次结构之前,还得先了解几个概念:
FiDO(Filter Device Object) 过滤器设备对象
FDO(Functional Device Object) 功能设备对象
PDO(Physical Device Object) 物理设备对象
要了解垂直层次结构,情况就变得复杂多了,这涉及到Windows操作系统原理,我对这方面的认识很少。
“设备的创建顺序是,先创建底层PDO,再创建高层FDO,这也是设备堆栈的生长方向,即从底层设备到高层设备。在PDO和FDO中夹杂着各种过滤驱动。每层的设备对象由不同的驱动程序所创建,或者说每层的设备对应着不同的驱动程序。有的驱动程序是系统自带的,有的是需要程序员编写。底层设备对象寻找上一层的设备对象,是依靠底层设备对象的AttachedDevice来寻找的,如果某一设备的AttachedDevice为空,说明已经到了设备堆栈的顶部。”
以上是引用《Windows驱动开发详解》中第四章中的一段话,对于这段话,我对其中的过滤驱动不太了解,但已经知道了PDO、FDO和FiDO之间的关系。其中PDO处于最底层,FDO在PDO的上面,而在FDO的上面和下面分别有不同的PiDO,来看看《Windows驱动开发详解》中对于这种垂直层次关系的图示:
5.对层次结构的总结
在对PDO、FDO和FiDO有了一些了解后,又再翻看了一下《Windows操作系统原理》中的相关内容,最后对驱动程序又有了一些了解。
一个硬件设备至少有两个驱动程序,一个是PDO驱动,即总线驱动程序;一个是FDO驱动,即功能设备驱动。总线驱动程序负责硬件与计算机的连接;功能设备驱动负责初始化I/O操作,处理I/O操作完成时所产生的中断事件,而HelloDDK就属于功能设备驱动。而FiDO驱动又分为底层过滤器驱动和高层过滤器驱动,也许在以后对文件过滤驱动的学习中会明白这一概念。