1 Windows 2000 I/O请求处理结构
如图1所示,Windows 2000是分态的操作系统。用户应用程序运行在用户态,操作系统代码(如系统服务和设备驱动程序)在态下运行。用户态程序只能调用Win32子系统提供的API来同设备交互,当请求传递到I/O管理器时,他进行必要的参数匹配和操作安全性检查,然后由这个请求构造出合适的IRP(IO Request Package,I/O请求包),并把此IRP传递到适当的驱动程序去,并给应用程序一个消息,通知这次I/O操作还没完成。驱动程序一般通过硬件抽象层来和硬件交互,从而完成I/O请求工作。驱动程序完成I/O操作后,他将调用一个特殊的内核服务例程来完成IRP。这时,I/O管理器把数据和结果返回给Win32和用户应用程序。
2 WDM驱动程序模型体系结构
Windows驱动程序模型重新定义驱动程序分层使用了如图2的层次结构。图中左边是一个设备对象堆栈。设备对象是系统为帮助软件管理硬件而创建的数据结构。一个物理硬件可以有多个这样的数据结构。处于堆栈层的设备对象称为物理设备对象PDO(Physical Device Object),代表了设备和总线之间的连接。在设备对象堆栈的中间的对象称为功能设备对象FDO(Functional Device Object),代表了设备的功能。在FDO的上面和下面还会有一些过滤器设备对象FIDO(Filter Device Object)。位于FDO上面的过滤器设备对象称为上层过滤器,位于FDO下面(但仍在PDO之上)的过滤器设备对象称为下层过滤器。
总线驱动程序负责枚举他的总线,这意味着发现总线上的全部设备和检测设备何时被添加或删除并为每个设备创建一个PDO。功能驱动程序知道如何控制设备的主要功能,他分层在总线驱动程序的上面。功能驱动程序创建一个功能设备对象,放在设备栈中。对总线上的所有设备,总线过滤驱动程序被加在总线驱动程序之上;设备过滤驱动程序仅对特定的设备添加。上层的过滤驱动程序在功能驱动程序之上,而下层过滤驱动程序在功能驱动程序之下。这种层次结构可以使I/O请求过程更加明了。I/O管理器发送的IRP,先被送到设备堆栈的上层过滤器驱动程序(FIDO),他可以根据要求决定IRP的处理方式,是沿着设备栈继续向下传,或者是做一些额外的处理。依次,每一层驱动程序都可以决定如何处理IRP。高层的驱动程序可以把请求划分成更简单的请求并传递给下层驱动程序。中间层次的驱动程序进一步处理请求,将一个IRP中的请求划分为若干个小的请求并传给下层驱动程序。,层的驱动程序与硬件打交道。因此一些IRP在到达总线程序之前,在设备栈传递过程中可能就被过滤掉了。 3 过滤器驱动程序
过滤驱动程序是一种中间驱动程序,他位于其他的驱动程序层次之间。过滤驱动程序可以监视、拦截和修改IRP流,在不影响其他驱动程序的前提下提供一些附加的功能。他的功能可分为:
(1)利用过滤器驱动程序修改现有功能驱动程序的行为而不必重新编写功能驱动程序。
(2)上层过滤器驱动程序在功能驱动程序之前看到IRP,可以很方便地为用户提供额外的特征。还可以修正功能驱动程序或硬件存在的毛病或缺陷。
(3)下层过滤器驱动程序在功能驱动程序要向总线驱动程序发送IRP时看到IRP。可以监视、拦截、修改功能驱动程序要执行的总线操作流,截获数据,进行必要的数据处理,再将处理过的数据传递下去,实现一定的数据处理功能。 (4)下层过滤器驱动程序可以实现驱动程序的总线无关性,使功能驱动程序完全独立于总线结构而不直接与设备对话。针对不同的总线编写不同的下层过滤器,每个下层过滤器对应一个总线类型。当功能驱动程序需要与硬件对话时,他只需向相应的下层过滤器驱动程序发送IRP即可。
4 过滤器驱动程序设计
过滤器驱动程序设计与功能驱动程序相似。这里限于篇幅主要讨论一下过滤器驱动程序设计中与功能驱动程序相区别的几个关键的技术要点。
4.1 DriverEntry例程
DriverEntry例程是驱动程序的人口点。当I/O管理器装入驱动程序时,他调用DriverEntry例程用来初始化驱动程序范围的数据结构和资源,包括输出该驱动程序的其他人口点,初始化该驱动程序使用的特定对象,并设置驱动程序系统资源。与功能驱动程序相区别的是:他必须为每种类型的IRP都安装派遣函数,而不仅仅只是其希望处理的IRP。
4.2 AddDevice例程
AddDevice函数的基本职责是创建一个设备对象并把他连接到以物理设备对象PDO为底的设备堆栈中,并负责设备对象初始化。与功能驱动程序相区别的是:过滤驱动程序创建的设备对象可能是2种,一种是不命名的过滤设备对象,过滤器工作时把这个无名的设备对象连接到以物理设备对象PDO为底的设备堆栈中。一种是为了和用户程序通信而创建的命名的设备对象并不连接到以物理设备对象PDO为底的设备堆栈中。命名可以采用2种方法:种方法是采用可显示的"硬编码"符号链接名,用户态程序必须把设备名硬编码到源代码中;另外一种方法是使用设备接口,每个设备接口是由一个全局惟一标志符GUID标志。设备注册为一个特定的设备接口就创建了一个符号链接。
相关步骤如下:
(1)调用IoCreateDevke创建过滤设备对象,并建立一个私有的设备扩展对象。
(2)寄存一个或多个设备接口,以便应用程序能知道设备的存在。另外,还可以给出设备名并创建符号连接。
(3)初始化设备扩展和设备对象的F1ag成员。
(4)调用IOAttachDevkeToDeviceStack函数把新设备对象放到堆栈上。
具体实现程序如下:
NTSTATUS AddDevice (PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT pdo)
{
PDEVICE_OBJECT fido=NULL;
//创建没有设备名的过滤设备对象
NTSTATUS status=IoCreateDevice (DriverObjeot,sizeof (DEVICE-EXTENSION),
NULL,FILE_DEVICE_UNKNOWN,0,FALSE,&fido);
if(!NT_SUCCESS(status)) return status;
//初始化设备扩展和设备对象的Flag成员
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fido->DeviceExtension;
pdx->DeviceObject=fido;
pdx->Pdo=pdo;
pdx->eDeviceType =FILTER;
//把没有设备名的设备对象放到堆栈上
PDEVICE- OBJECT fdo =
IoAttachDeviceToDeviceStack (fido,pdo);
pdx->TopDevObj=fdo;
fido->Flags ︱=pdo->F1ags&(DO_DIRECT-IO ︱DO-BUFFERED-IO ︱ DO_POWER_PAGABLE ︱DO_POWER_INRUSH);
………
//建立一个命名的设备对象创建符号链接
CreateSymbOlicLink (DriverObject,pdo);
return STATUS_SUCCESS;
}
4.3 派遣例程
派遣例程处理来自应用程序的打开、关闭、读、写等I/O请求命令。与功能驱动程序的区别是:过滤器驱动程序不能影响其他驱动程序接受IRP。对于未知的IRP或不处理的IRP过滤驱动程序的原则是必须沿设备栈传递下去。因此他必须为每种类型的IRP都安装派遣函数,而不仅仅只是其希望处理的IRP。对于希望处理的IRP必须指定特殊的派遣函数,直接用CompleteIRP来完成接收到的这类IRP,不往下层传送。
这里由DispatchDeviceControl处理来自应用程序的所有希望处理的I/O操作命令。通常采用给予所有自定义的I/O请求代码的SWITCH语句,而对于每个命令使用相应的处理函数。下面列出了主要的代码框架:
NTSTATUS DispatchDeviceControl (PDEVICE_OBJECT fido,PIRP Irp)
{
NTSTATUS status;
PDEVICE_EXTENSION pdx=(PDEVICE_EXTENSION)fido->DeviceExtension;
PlO_STACK_LOCATION IrpStack =
IoGetCurrentlrpStackLocation(1rp);
//取I/O控制命令代码
ULONG IoControlCode = IrpStack >
Parameters.DeviceloContr01.IoControlCode;
switch(IoControlCode)
{
case IOCTL-XXX:
…… //处理I/O控制命令代码
case IOCTL-XXX:
……
default:
……
break;
}
//完成接收到的IRP
IoCompleteRequest(Irp,IO_NO_INCREMENT);
……
return status;
}
对于不需要处理的IRP则交由DispatchAny例程处理,将IRP沿着设备栈直接传递下去:
NTSTATUS DispatchAny(PDEVICE_OBJECT fido,PIRP Irp)
{
PDEVICE_ EXTENSION pdx=(PDEVICE-EXTENSION)
fido->DeviceExtension
//使堆栈指针少前进一步。
IoSkipCurrentlrpStackLocation(hp);
Status=IoCallDriver(pdx->LowerDeviceObject,Irp);
……
return status;
4.4 Unload例程功能
在Unload例程中,驱动程序必须释放所有创建的对象和所有分配给驱动程序的资源。
5 结 语
笔者就采用在Windows提供的USB声卡驱动程序下方插入自己编写的下层过滤器驱动程序实现了对USB声卡输出的数据流的截获并进行语音信号处理,取得了不错的效果,现已投入实际应用。可见过滤器驱动程序作为一类特殊的驱动程序,它可以以较小的代价实现对驱动数据流的截获,修改、增加现有驱动常需的功能,具有很强的实用性。
免责声明: 凡注明来源本网的所有作品,均为本网合法拥有版权或有权使用的作品,欢迎转载,注明出处。非本网作品均来自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。