虚拟设备驱动程序的设计与实现

由于Windows对系统底层操作采取了屏蔽的策略，因而对用户而言，系统变得更为安全，但这却给众多的硬件或者系统软件开发人员带来了不小的困难，因为只要应用中涉及到底层的操作，开发人员就不得不深入到Windows的内核去编写属于系统级的虚拟设备驱动程序。Win 98与Win 95设备驱动程序的机理不尽相同，Win 98不仅支持与Windows NT 5.0兼容的WDM(Win32 Driver Mode)模式驱动程序，而且还支持与Win 95兼容的虚拟设备驱动程序VxD(Virtual Device Driver)。下面介绍了基于Windows 9x平台的虚拟环境、虚拟设备驱动程序VxD的基本原理和设计方法，并结合开发工具VToolsD给出了一个为可视电话音频卡配套的虚拟设备驱动程序VxD的设计实例。
1．Windows 9x的虚拟环境
Windows 9x作为一个完整的32位多任务操作系统，它不像Window 3.x那样依赖于MS-DOS，但为了保证软件的兼容性，Windows 9x除了支持Win16应用程序和Win32应用程序之外，还得支持MS-DOS应用程序的运行。Windows 9x是通过虚拟机VM(Virtual Machine)环境来确保其兼容和多任务特性的。
所谓Windows虚拟机（通常简称为Windows VM）就是指执行应用程序的虚拟环境，它包括MS-DOS VM和System VM两种虚拟机环境。在每一个MS-DOS VM中都只运行一个MS-DOS进程，而System VM能为所有的Windows应用程序和动态链接库DLL(Dynamic Link Libraries)提供运行环境。每个虚拟机都有独立的地址空间、寄存器状态、堆栈、局部描述符表、中断表状态和执行优先权。虽然Win16、Win32应用程序都运行在System VM环境下，但Win16应用程序共享同一地址空间，而Win32应用程序却有自己独立的地址空间。
在编写应用程序时，编程人员经常忽略虚拟环境和实环境之间的差异，一般认为虚拟环境也就是实环境。但是，在编写虚拟设备驱动程序VxD时却不能这样做，因为VxD的工作是向应用程序代码提供一个与硬件接口的环境，为每一个客户虚拟机管理虚设备的状态，透明地仲裁多个应用程序，同时对底层硬件进行访问。这就是所谓虚拟化的概念。
VxD在虚拟机管理器VMM(Virtual Machine Manager)的监控下运行，而VMM实际上是一个特殊的VxD。VMM执行与系统资源有关的工作，提供虚拟机环境（能产生、调度、卸载VM）、负责调度多线程占先时间片及管理虚拟内存等工作。VxD与VMM运行在其他任何虚拟机之外，VxD事实上就是实现虚拟机的软件的一部分。
与大多数操作系统一样，Windows也是采用层次式体系结构。VMM和VxDs构成了Win 95的ring0级的系统核心（应用程序运行在ring3级，ring1、ring2级未被使用），具有系统的最高优先权。Windows还提供一些以"drv"为后缀名的驱动程序，主要是指串行口的通信程序和并行口的打印机程序。这些程序与VxD不同，它们是运行在ring3级上的。图1可以使你更好地理解Windows的虚拟环境。
图
2．深入理解VMM和VxD
如前所述，VxD是Virtual Device Driver的缩写，但有人将它理解为虚拟任何驱动程序。实际上，VxD并非仅指那些虚拟化的某一具体硬件的设备驱动程序。比如某些VxD能够虚拟化设备，而某些VxD作为设备驱动程序却并不虚拟化设备，还有些VxD与设备并没有什么关系，它仅向其他的VxD或是应用程序提供服务。
VxD可以随VMM一起静态加载，也可以根据需要动态加载或卸载。正是由于VxD与VMM之间的紧密协作，才使得VxD具有了应用程序所不具备的能力，诸如可以不受限制地访问硬件设备、任意查看操作系统数据结构（如描述符表、页表等）、访问任何内存区域、捕获软件中断、捕获I/O端口操作和内存访问等，甚至还可以截取硬件中断。
尽管VxD使用32位平面存储模式(flat memory model)，但它的代码和数据仍使用分段管理，段有六种类型，即实模式初始化、保护模式初始化、可分页、不可分页、静态和只调试(debug only)，每种类型又有代码段和数据段之分，所以VxD共有12个段。实模式代码段和数据段为16位（分段模式），其他段则是32位（平面模式）。“实模式初始化”段包含了在Windows初始化过程的最初阶段VMM变为保护模式之前要执行的代码。静态加载的VxD此时可以查看Windows启动前的实模式环境，决定是否继续加载，并通知VMM。加载完毕后，VMM进入保护模式并执行保护模式初始化代码，同样将执行结果再通知VMM。初始化完成后，“实模式初始化”段和“保护模式初始化”段即被遗弃。VxD的大部分代码都在其他的某一段中，“可分页”段允许虚拟存储管理器(Virtual Memory Manager)进行分页管理，大多数的VxD代码都应当在“可分页”段。“不可分页”段的内容主要包括：VxD的主入口点、硬件中断处理函数、所访问的数据以及能被另一个VxD中断处理函数调用的异步服务。“静态”段仅用于可以动态加载的VxD，当VxD卸载后，静态代码段和数据段都保留在内存中。“只调试”段只是VMM在Soft-ICE for Win 95等调试环境下才将其载入。
VMM是通过VxD的设备描述符块DDB(Device Descriptor Block)来识别的。DDB向VMM提供了VxD的主入口点，还向应用程序和其他的VxD提供了入口点。VMM利用这个主入口点将VM及Windows自身的状态通知给VxD，然后VxD通过相应的工作来响应这些事件。由于VxD不仅仅服务于一个物理设备（比如多个串口）或仅与一个VM发生联系，所以VxD需要产生自己支持的数据结构(Supporting Data Structures)来保存每一个设备、每一个VM的配置和状态信息。VxD用一个或多个设备上下文结构来保存设备信息，如I/O端口基地址、中断向量等，VxD将自己的每个VM的状态信息保存在VMM的VM控制块中。
VMM提供的服务包括：事件服务、内存管理服务、兼容执行和保护模式执行的服务、登录表服务、调度程序服务、同步服务、调试服务、I/O捕获服务、处理错误和中断服务、VM中断和回调服务、配置管理程序服务以及其他杂项服务。
以上内容仅涉及到VxD设计的一小部分，作为VxD的开发人员必须掌握更多的知识。首先是操作系统的知识，如地址空间、执行上下文、资源加锁、进程间通信和异步事件处理等方面的知识；其次，对Intel处理器应有较深入的理解，包括寄存器、机器指令集、保护机制、分页机制，以及虚拟8086模式；最后，还必须熟悉VMM提供的各类服务和接口，熟悉Windows其他的系统VxD。
3．开发工具VToolsD简介
VToolsD是专门用于开发VxD程序的一种工具软件，它包括VxD框架代码生成器QuickVxD、C运行库、VMM/VxD服务库、VxD的C++类库、VxDLoad和VxDView等实用工具以及大量的C、C++例程。由VC++、BC++的32位编译器编译生成的VxD程序可以脱离VToolsD环境运行。
利用QuickVxD可以方便、快捷地生成VxD的框架，即生成后缀名为h、cpp和mak的三个文件。源文件包含了运行VxD的基本组件，其中包含控制消息处理、API入口点、以及VxD服务等函数框架，并且还定义了标志，设置了编译参数，声明了类，然后在C++环境下，向生成的各个处理函数体内添加自己的代码，最后使用编译器NMAKE生成标准的VxD程序。
由于VxD运行在ring0级，所以调试程序相当困难。我使用的调试工具是Soft-ICE for Win 95。
目前VToolsD的最新版本为3.0，它支持设备访问体系结构DAA(Device Access Architecture)，所编写的程序代码将可以在所有Windows平台（包括Win 95、Win 98以及Windows NT）上共享。当然也可以使用Microsoft公司的DDK(Device Developer Kit)来开发VxD，但DDK不能像VToolsD那样通过屏蔽系统及VxD的底层技术细节提供丰富的C运行库和C++类库，而是让开发人员充分享用面向对象编程方法的方便与快捷，因此仅就该点而言，使用DDK是不方便的。
4．VxD程序设计实例
我在开发可视电话音频卡的设计过程中，用VToolsD 2.03、VC++ 5.0为自制的PC/XT总线扩展卡开发了虚拟设备驱动程序Audcard.vxd。该卡每20ms申请一次中断，中断由应用程序动态载入系统的Audcard.vxd响应并加以处理。中断服务程序ISR(Interrupt Service Routine)结束后，调用函数Shell_PostMessage( )向应用程序窗口发送自定义消息。应用程序接受消息后，再通过函数DeviceIoControl( )与VxD的接口函数OnW32DeviceIoControl( )互传缓冲区数据。程序结束即可动态卸载VxD。下图表示在Win 95下VxD对硬件中断的处理过程。
图Win95下硬件中断的处理过程
当中断发生时，处理器转换为ring0级保护模式。Windows系统并不像DOS那样通过中断描述符表IDT(Interrupt Descriptor Table)直接指向中断处理过程，而是由IDT入口指向VMM中的程序。该程序将判断是否为中断调用，如果是，则把中断控制权交给虚拟可编程中断控制器VPICD(Virtual Programmable Interrupt Controller Device)，VPICD实际上是一个重要的VxD。VPICD再将其交给另一个注册了该中断的VxD（如Audcard.vxd）来处理。VxD程序是通过调用VPICD服务VPICD_Virtualize_IRQ来注册中断的。
虚拟设备驱动程序Audcard.vxd的部分源代码Audcard.h和Audcard.cpp在网上，网址为：www.pccomputing.com.cn。此应用程序使用了下列函数：CreateFile()动态加载VxD、CloseHandle()并动态卸载VxD、PreTranslateMessage()截获消息、DeviceIoControl()与VxD互传缓冲区数据。虚拟设备驱动程序Audcard.vxd经调试后工作正常，未发生过任何丢失数据或死机的现象。
下面是虚拟设备驱动程序Audcard.vxd的部分源代码Audcard.h和Audcard.cpp，限于篇幅，由QuickVxD自动生成的Audcard.mak未列出。
①Audcard.h
//AUDCARD.h - include file for VxD AUDCARD
#include
#define DEVICE_CLASS AudcardDevice
#define AUDCARD_DeviceID UNDEFINED_DEVICE_ID
#define AUDCARD_Init_Order UNDEFINED_INIT_ORDER#define AUDCARD_Major
#define AUDCARD_Minor 0
#define MY_IRQ 5 //定义5号中断
class MyHwInt:public VHardwareInt
{
public:
MyHwInt():VHardwareInt(MY_IRQ,0,0,0){}
virtual VOID OnHardwareInt(VMHANDLE);
};
class AudcardDevice : public VDevice
{
public:
virtual BOOL OnSysDynamicDeviceInit();
virtual BOOL OnSysDynamicDeviceExit();
virtual DWORD OnW32DeviceIoControl(PIOCTLPARAMS pDIOCParams);
MyHwInt* pMyIRQ;
};
class AudcardVM : public VVirtualMachine
{
public:
AudcardVM(VMHANDLE hVM);
};
class AudcardThread : public VThread
{
public:
AudcardThread(THREADHANDLE hThread);
};
②Audcard.cpp
//AUDCARD.cpp - main module for VxD AUDCARD
#define DEVICE_MAIN
#include "audcard.h"
Declare_Virtual_Device(AUDCARD)
#define WM_USER_POSTVXD 0x1000
//自定义消息
#undef DEVICE_MAIN
AudcardVM::AudcardVM(VMHANDLE hVM) : VVirtualMachine(hVM) {}
AudcardThread::AudcardThread(THREADHANDLE hThread) : VThread(hThread) {}
BOOL AudcardDevice::OnSysDynamicDeviceInit() //动态加载时初始化
{
......//硬件初始化
pMyIRQ=new MyHwInt();
if(pMyIRQ&&pMyIRQ->hook()) //挂接中断
{
pMyIRQ->physicalUnmask(); //允许中断
return TRUE;
}
else return FALSE;
}
BOOL AudcardDevice::OnSysDynamicDeviceExit()
//动态卸载过程
{
delete pMyIRQ;
return TRUE;
}
DWORD AudcardDevice::OnW32DeviceIoControl(PIOCTLPARAMS pDIOCParams)
//与Win32应用程序的接口函数
{
......
}
VOID MyHwInt::OnHardwareInt(VMHANDLE hVM)
{
...... // 中断处理
SHELL_PostMessage(AppWnd,WM_USER_POSTVXD ,0,0,0,NULL);
//向应用程序窗口发送消息
sendPhysicalEOI(); //通知VPICD中断结束
}