Windows Driver Model

视窗驱动程式模型Windows Driver Model,简称WDM),是微软视窗作业系统驱动程式设计架构,包括对于各项装置(Device)的支援,如键盘(Keyboard)、滑鼠(Mouse)、网路卡(Network Driver Interface Specification,Ndis)、通用序列汇流排(Universal Serial Bus,USB)等。WDM可支援Windows 98, Windows 98 Second Edition, Windows Me, Windows 2000, Windows XP以及Windows Server 2003在x86平台上的建制工作。这个架构分成好几个管理层面:

  • 核心(Kernel)
  • 物件管理(Object Manager)
  • 执行(Executive)
  • 输入输出管理(I/O Manager)
  • 记忆体管理(Memory Manager)
  • 行程服务(Process Service)
  • 执行时期函式库(Run-time Library)
  • 电源管理(Power Manager)
  • 随插即用子系统(Plug and Play Subsystem)
  • 视窗管理指令(Windows Management Instrumentation,WMI)
  • 核心流(Kernel Streaming)
  • 硬体抽象层(Hardware Abstraction Layer)

种类

驱动程式的种类有汇流排驱动程式(bus driver)、功能驱动程式(function driver)、筛选驱动程式(filter driver)三种:

  • 汇流排驱动程式(bus drivers)
    • 虚拟汇流排驱动程式(Virtual bus drivers):呼叫IoInvalidateDeviceRelations通知PnP Manager。
  • 功能驱动程式(function drivers)
    • 微埠型驱动程式(miniport drivers):属于USB, Audio, SCSI以及network的转换器(adapters)。
    • 虚拟装置驱动程式(Virtual device drivers)
    • 类型驱动程式(Class drivers)
  • 筛选性驱动程式(filter drivers)
    • 上层筛选性驱动程式(upper-filter drivers)
    • 下层筛选性驱动程式(lower-filter drivers)

程式进入点

如同许多语言从main函式开始,WDM驱动程式的载入(Loading)乃至于动作,以及其卸载(Unload)行为,都有其步骤与规则。WDM驱动程式可以动态式的载入与卸载,当侦测到装置(Device)插入的时候,依据“PnP Manager”会自动地载入相对应的装置驱动程式,然后成为“Driver Object”,并呼叫DriverEntry函式。所有的WDM驱动程式,都必须拥有这个DriverEntry常式(routine),而且“此一名称不可改变”的,所有的驱动程式也是从这里开始执行的,I/O Manager首先呼叫驱动程式的DriverEntry()。DriverEntry在DDK Compiler编译出来的输出符号表是“DriverEntry@8”,原因是微软的C编译器把stdcall函示(例如VcDCall)的名称加上“记号”,加上一个@符号,在附上参数的总位元组个数,8指此function的所有参数所占的byte数。

DriverEntry函式有两个参数,其中第一个参数PDRIVER_OBJECT DriverObject是指向该驱动程式对应的物件指标;PUNICODE_STRING RegistryPath,驱动程式的服务主要键码,这个参数的使用时机并不多。以下是一个简单而标准的DriverEntry基本实作:

 NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING pRegistryString)
 {
   //PDEVICE_OBJECT DriverObject;
   UNICODE_STRING deviceName;
   RtlInitUnicodeString( &deviceName, DEVICE_NAME );
       
   status = IoCreateDevice( DriverObject, 0, &deviceName, 
      FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, true, &pDeviceObj );
 
   UNICODE_STRING linkName;
   RtlInitUnicodeString( &linkName, LINK_NAME );
   status = IoCreateSymbolicLink( &linkName, &deviceName );
 
   DriverObject->DriverUnload = DriverUnload;
   DriverObject->MajorFunction[IRP_MJ_CREATE] =
   DriverObject->MajorFunction[IRP_MJ_CLOSE] =
   DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = xxDriverDispatch;
   DriverObject->DriverUnload = xxUnload;
 
   return STATUS_SUCCESS;
 }

IRQL

DriverEntry运作在IRQL = PASSIVE_LEVEL等级,所以可以使用分页内存。WDM有数个经常实作的PASSIVE_LEVEL等级如下:

  • DriverEntry
  • Dispatch Function:DispatchXxx
  • Unload:UnloadXxx
  • AddDevice: XxxAddDevice
  • Reinitialize: XxxReinitialize

另外还有几个DISPATCH_LEVEL等级的函式如下:

  • StartIo
  • AdapterControl
  • ControllerControl
  • IoTimer
  • Dpc(延迟程序呼叫,Deferred Procedure Call)

派送行程

DriverEntry函式完成物件的初始化与系统注册之后,接下来的重点便会落在Dispatch Function身上。每当I/O Manager得到一个请求时(例如按键,移动滑鼠),它使用请求的函数代码(IoControlCode)呼叫驱动程序中几个Dispatch行程。

 NTSTATUS xxDispatch (IN PDEVICE_OBJECT DeviceObject,IN PIRP pIrp)
 { 
    NTSTATUS ntStatus = STATUS_SUCCESS;
    ULONG IoControlCodes = 0;             
    PIO_STACK_LOCATION IrpStack=NULL;   
 
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;
 
    IrpStack = IoGetCurrentIrpStackLocation(pIrp);    
 
    switch (IrpStack->MajorFunction)
    { 
        case IRP_MJ_CREATE:
            break;
        case IRP_MJ_CLOSE:
            break;
        case IRP_MJ_DEVICE_CONTROL:
            IoControlCodes=IrpStack->Parameters.DeviceIoControl.IoControlCode;
            switch (IoControlCodes)
            { 
                case IOCTL_1:
                    break;
                case IOCTL_2:
                    break;
                default:
                    pIrp->IoStatus.Status = STATUS_INVALID_PARAMETER;
                    break;
            }
            break;
        default:
            break;
    }
    ntStatus=pIrp->IoStatus.Status;
 
    IoCompleteRequest(pIrp,IO_NO_INCREMENT);
 
    return ntStatus;
 }

卸载

Unload负责在驱动程式被停止前做一些必要的处理动作,如释放资源,记录最后状态等。

 VOID DriverUnLoad (IN PDRIVER_OBJECT DriverObject)
 { 
   if (DeviceObject) {
   IoUnregisterFileSystem(DeviceObject);
   IoDeleteDevice(DeviceObject);
   //DeviceObject = 0;
 }

编译

驱动程式的编译需要使用DDK(Device Driver Kit)中的build指令,它是一道命令行程序,一般会在后面加上参数:-ceZ。例如:

 C:\driver sample>build.exe -ceZ

DDK可用于建立用于 Windows 2000、Windows XP、Microsoft Windows Server 2003、Vista的建置环境,但在Windows作业系统并非预设的功能,必须另行安装。安装完成后你会看到Build Enviroment,free是指release版,check则是debug版

  • Windows XP checked 64 Bit Build Environment
  • Windows XP checked Build Environment
  • Windows XP free 64 Bit Build Environment
  • Windows XP free Build Environment

build指令一开始呼叫Build.exe编译连结器,从系统“环境变数”(Environment Variable)Include中得到引用文件的地址,然后呼叫Visual C++的编译链接器Nmake.exe进行实际的编译链接工作。在编译过程中遇到的错误,遇到的警告,会记录到buildxxx.log,buildxxx.wrn,buildxxx.err等文件中。

安装

安装过程分成两个步骤:

  • 首先将编译成的.sys文件复制到Windows NT的System32\\Drivers\\目录下;
  • 接著在Registry的HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\下建立和.sys文件同名的键,然后在之下建立名为Start、Type、ErrorControl的三种REG_DWORD类型的键值。

范例

Microsoft DDK提供了大量的WDM范例(samples)参考,这些范例随著DDK的安装,会进驻我们的系统之中(WINDDK\xx00\src)。一般人不大可能从轮子造起一个新的驱动程式,大部份要靠“既有的范例”来改良一个新的驱动程式。

  • 1394
  • Audio
  • AVStream
  • filesys
  • general
  • hid
  • input
  • ir
  • kernel

批评

  • WDM学习曲线过长。
  • 电源管理(power management)事件与随插即用(Plug-and-play)差异太大。导致系统的睡眠(sleep)与清醒(wake up)状态容易产生问题。
  • I/O cancellation几乎不可能达成。
  • 每支驱动程式动辄要撰写数千行的程式码。
  • 不支援纯使用者模式(pure user-mode)驱动程式。

相关条目

外部链接

参考

  • Walter Oney,《Windows Driver Model》