WDM(Windows Driver Mode1)是Microsoft公司全新的Windows驱动程序模式,支持即插即用(PNP)、电源管理和WMI技术,它的运行平台是Windows 98/2000/XP/2003操作系统。这种具有跨平台性的设备驱动模型可以大大简化驱动程序的开发工作,为用户的PNP设备驱动完成了大量的底层工作。这也使得WDM对各种老设备,特别是那些不具有即插即用特性硬件的支持明显的不足。在科研和工控中,常用的数据采集卡通常都是基于PC总线的不能为PNP提供硬件支持的ISA设备。为这类硬件编写非WDM驱动程序只能局部支持PNP特性,而且需要做大量额外的工作如:必须检测硬件,为硬件创建设备对象(用于代表硬件),配置并初始化硬件使其正常工作,这些工作非常复杂。利用WDM可以大大简化这些工作。
1 WDM的结构
WDM实际上是一个编写驱动程序的规范。其驱动程序结构的特点和WINDOWS程序设计的消息驱动机制很相像,采用IRP驱动机制。WINDOWS 98和WINDOWS 2000处理IRP的方式一样,本质却完全不同。
Windows2000的内存管理采用了虚拟内存的概念,系统表现为具有一个比物理内存大得多的虚拟内存空间。每个进程都被赋予它自己的虚拟地址空间,这个地址空间划分成固定大小的页(x86、32位处理器的页大小为4KB),一个页可以驻留在物理内存(非分页内存),或者被交换到硬盘上(分页内存)。当进程中的一个线程正在运行时,该线程可以访问只属于它的进程的内存,属于所有其他进程的内存则隐藏着,并且不能被正在运行的线程访问。每个进程有独立的内存空间,不会被其他程序访问,保护了数据的完整性。这样,有一部分内存实际上不是在物理内存中,而是在硬盘上。当访问并不存在于实际物理内存的地址时,内存管理器引发DISPATCH_ LEVEL级别的页故障中断,调用硬盘驱动程序把故障页读入内存。所以,在DISPATCH LEVEL级别上运行代码时,访问非分页内存是一个基本原则,否则进程将被阻塞,因为这个页故障中断被屏蔽了。这时,在当前的Windows系统中,就会发生蓝屏。虚拟内存方便了应用程序l的开发,却给ISA设备在分配资源时带来一些复杂的问题。因为x86处理器的内存页大小为4KB,所以在为ISA设备进行内存映射时每段就不要超过4KB。否则当访问超出4KB地内存时就会出现上面所说的蓝屏。
WDM驱动程序面对的主要内容是一系列内核驱动对象。这些对象包括驱动对象、设备对象,还有一系列的资源抽象对象如中断对象、适配器对象(处理DMA操作)、内核模式派发器对象、控制器对象、推迟过程对象、定时器对象、设备队列对象、回调对象等。其中驱动对象由I/O管理器负责创建和管理。WDM驱动程序利用IRP和这些对象完成硬件设备的抽象化,并对应用程序提供统一操作接口。这就是所谓的WDM规范。
WDM驱动程序的结构很简单。它的主体是一个入口函数DriverEntry。DriverEntry的个参数是指针,指向一个刚被初始化的驱动程序对象,该对象代表驱动程序。WDM驱动程序的DriverEntry例程完成这个对象的初始化并返回。WDM驱动程序的DriverEntry例程的主要工作是把各种函数指针填入驱动程序对象。这些指针对操作系统指明了驱动程序容器中各种子例程的位置。它包括下面这些指针成员:
·DriverUnload指向驱动程序的清除例程。I/O管理器会在卸载驱动程序前调用该例程。通常WDM驱动程序的DriverEntry例程一般不分配任何资源,所以DriverUnload例程也没有什么清除工作要做。
·DriverStartIo,如果驱动程序使用标准的串行IRP,则必须使用这个函数,使它指向驱动程序的StarIo例程。
·MajorFunction是一个指针数组,它主要处理各种用户的I/O请求IRP。默认情总况下,用户的所有I/O请求都会由I/O管理器返回失败。驱动程序为要处理的IRP指定相应的派遣函数。
2 为ISA设备分配资源
虽然WDM驱动程序可以简化很多工作,但是给ISA设备编写WDM驱动程序也有困难。这个困难是ISA设备所需资源的分配问题。根据PNP的规范,支持PNP的设备如PCI总线设备有相应的寄存器标识自己和自己可以接受的资源,如中断、端口范围、内存范围等,并且有相应的逻辑支持配置资源。这使得操作系统启动过程中可以动态地规划调整各个设备的资源而不需要用户的干预。为了兼容非PNP设备,INF文件支持几个命令为设备分配资源。这些信息初提供给NPN管理器,并且被PNP管理器用来与系统其它部分协商以完成非PNP设备的自动资源分配。LOGCONFIG为设备制定一个可选的资源设定。还有相应的IRQCONFIG、IOCONFIG、DMACONFIG、MEMCONFIG子命令,它们为设备指定具体的可选资源。语法如下:
[<log-config-section-name>]
ConfigPririty=<priority-value>
[DMAConfig=<dma-list>]
[IOConfig=<io-range-list]
[IRQConfig=<irq-list>]
[MemConfig=<mem-range-list>]
其中CONFIGPRIOITY命令指定这个LOGCONFIG项的优先级。其余子命令表示设备选用的硬件资源。下面是一个例子:
[CX2590.Install]
……;其它命令
LogConfig=CX2590_DMA;指定配置项的名称
[CX2590_DMA];配置项的名称
ConfigPriority=NORMAL;配置的优先级
IOConfig=4@300-3ff%3ff(3ff::);指定IO范围
IRQConfig=4,5,9,10,11 ;指定可选的中断
DMAConfig=0,1,2,3 ;指定可选的DMA
在INF文件中加入LOGCONFIG命令可以解决ISA总线设备资源的自动分配问题,就可以为ISA总线设备编写WDM驱动程序。
3 一个ISA设备WDM驱动程序的实现
使用Numega公司的开发工具DriverStudio 2.01可以大大地简化驱动程序的开发过程。DriverStudio开发通用内核模式的开发包是DriverWorks。DriverWorks是一个面向对象的工具包。它封装了比较底层的繁复操作,提供给开发者一个简洁的界面。同时,DriverWorks和Visual C++有很多的接口:DriverWorks专门为Visual C++提供了一个专门开发WDM驱动程序的WIZARD。在WIZARD指导下,开发者可以很容易地生成一个驱动程序的框架。开发者要作的工作就是根据特定硬件编写相应的代码。
笔者开发的高速旋转机械监控与故障诊断系统采用的是北京大恒公司的具有FIFO(队列形式实现的缓存)的8路数据采集卡AC 1810。它的特点是由硬件自动完成采用操作:当FIFO半满的时候,系统产生中断通知用户取走数据;同时,硬件继续自动采样FIFO的另一关。驱动程序的主要工作包括设备I/O读操作和中断服务操作。下面是WIZARD生成的驱动对换和设备对象的定义(只取主要部分):
// 驱动对象
class AC_1810 : public Kdriver
{
SAFE_DESTRUCTORS
public:
virtual NTSTATUS
DriverEntry(PUNICOE-STRING
RegistryPath);
//驱动程序的入口函数
virtual NTSTATUS
AddDevice(PDEVICE_OBJECT Pdo);
//完成设备对象和驱动对象的连接
……
};
//设备对象
class AC_1810Device : public KpnpDevice
{
// Constructors
public;
AC-1810Device(PDEVICE_OBJECT Pdo,
ULONG Unit);//构造函数完成资源配置
……
public:
AC_1810Device(PDEVICE_OBJECT Pdo,
ULONG Unit);//构造函数完成资源配置
……
public:
BOOLEAN Isr_Irq(void);// IRQ中断服务例程
Virtual NTSTATUS Create(KIrp I);
//标准IRP处理函数
virtual NTSTATUS Close(KIrp I);
// COMMENT_ONLY
virtual NTSTATUS Read(KIrp I);
// COMMENT_ONLY
virtual VOID StarIo(KIrp I);
// 开始I/O传输
VOID CancelQueuedIrp(KIrp I);
// 判断传输IPR是否被取消
VOID Invalidate(void);
// 释放资源例程
virtual NTSTATUS DefaultPnp(KIrp I);
// 处理默认的PNP操作
virtual NTSTATUS DefaultPower(KIrp I);
// 管理电源
void SerialRead(KIrp I);
//完成实际的I/O操作
NTSTATUS IOCTL_SETUP_Handler(KIrp I);
//采样参数设定
NTSTATUS IOCTL_REW_Handler(KIrp I);
// 开始/停止采样
……
protected:
KioRange m_IoPotrRange0
// 管理I/O资源
Kinterrupt m_Irq;
// 管理中断资源
……
};
在实际操作过程中,采用中断读数的方法。在驱动程序中设置了两个缓冲区,一个前台缓冲区,一个后台缓冲区(用作后备缓冲区),系统总是先使用前台缓冲区。这样可以保证采样数据序列的时间顺序。
具体实现如下:
// ISR例程,完成数据从FIFO中读出
BOOLEAN AC_1810Device::Isr_Irq(void)
{
//是否触发不断?未触发则返回,判断两个缓冲区情况,都满则返回
……
// 前台缓冲区未满,使用前台缓冲区
if(m_pBuffer->numused < MAX_READ_BUF){
for(int i=0;i<BLOCK_SIZE;i++){
m_pBuffer_>buff[m_pBuffer->numused+i]=READ_FIFO;
}
m_pBuffer->numused +=BLOCK_SIZE;
}
else
//完成其它情况的判断
……
return TRUE;// 中断成功返回
}
当应用程序使用标准Win32 API对设备进行读操作的时候,I/O管理器通知驱动程序并触发对Read函数的调用。对于通常的串行设备,在Read函数的排队IRP请求,此时系统就可以触发StartIO例程,并且保证这个过程是串行处理的。
// 读例程,处理IRP_MJ_READ
NTSTATUS AC_1810 Device::Read(KIrp I)
{
// 检查输入的合法性
if (I.ReadSize ()<BLOCK_SIZE){
// 不合法返回错误代码
I.Information()=0;
Return I.PnpComplete(this,STATUS_INVALID_PARAMETER);
}
// 读0字节,永远成功
if (I.ReadSize() = =0){
I.Information () = 0;
return
I.PnpComplete(this,STATUS_SUCCESS);
}
// 排队这个IRP触发StarIO,完成数据传输
return QueueIrp(I,LinkTo(CancelQueuedIrp));
}
// StartIo例程,完成I/O操作
VOID AC_1810 Device::StartIo(KIrp I)
{
// 检测这个IRP是否被取消
if(!I.TestAndSetCancelRoutine(LinkTo(CancelQueuedIrp),NULL,CurrentIrp()) )
{
return;//取消则返回
}
switch (I.MajorFunction()) {
case IRP_MJ_READ:
//这个函数按逻辑完成读操作
SerialRead(I);
Breadk
……
// 开始处理下一个IRP
PnpNextIrp(I);
break;
}
}
ISA卡的WDM驱动程序的主体部分就完成了。它具有PNP功能,可以方便地安装卸载。在中断到来的时候,系统使用两个缓冲区完成数据的传输,可以避免数据丢失。
以上驱动程序是使用Numega公司的DriverSutdio 2.01版,结合Visual C++ 6.0,Microsoft Windows 2000 sp1 DDk开发调试通过,并且成功地应用到实验室开发的高速旋转机械实时状态监控与故障诊断系统中,该系统界面友好高度可靠。同时由于加入了PNP的支持,使得该系统的硬件安装卸载非常简便。这种技术可取代传统的以DOS为的工业用监控与故障诊断系统,具有广阔的应用前景。
免责声明: 凡注明来源本网的所有作品,均为本网合法拥有版权或有权使用的作品,欢迎转载,注明出处。非本网作品均来自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。