对于软件复用,Microsoft Windows在用户层提供了COM(Component Object Model),其实现了二进制层级的兼容,使Windows开发进入了组件时代。COM的概念之一进程内组件,在用户态使用动态联接库作为载体。所以不管 在COM之前或是之后,DLL都是Windows下实现软件复用的一个最主要的途径。只是单纯的DLL并未实现COM一样的二进制兼容的目的。但对于驱动 开发者而言,不同于用户态的代码,2000/XP并未有相应的像COM一样的机制,实现核心态的二进制兼容方式。实际上对于内核态也没有必要提供一个像 ole32.dll在用户态实现的COM Library这样的复杂机制(用户态COM组件遵循的简单的规则主要隐藏在COM Library的复杂之下)。
传统的DLL复用模式同样的适用于核心态代码的开发,加上为了简化同一类型设备开发工作等目的,Windows 2000/XP形成了Class/Port/Miniport这样的概念。我们知道对于Driver其提供的.sys文件实际与dll没有什么实质上的变 化。我们首先讨论一下Class/Port/Miniport的概念与这种机制带来的益处:
1、我们知道PC机硬件通常可分成许多大类,如存储类设备等等。各大类又分成许多小类。如存储类设备又可分为Cdrom、Tape、Harddisk等 等。对于每一类设备,提供一个class driver。这样的driver实现某一类型设备Driver与设备无关的公共的功能集合。对于Driver开发者而言,有了class driver的介入,仅仅需要实现一部分需要针对特定设备的功能,IO管理器需要的功能要不由class driver直接完成,要不就由class driver通过调用底层的模块来实现。class driver通常由Microsoft提供,通常实现的是设备无关的部分,这里的与设备无关主要是指不直接操作设备硬件。而这底层的模块通常称为 miniport driver,后者由port driver提供服务(port driver类似于用户态DLL,不过她还实现一些简化设备驱动编写的功能,例如隐藏对IRP的繁琐操作等等)。Class driver调用miniport driver的方式主要有Device IOCTL,class driver导出的例程(就是DLL的概念)或是通过回调函数等等。这样分离出共有的部分,即可以实现所谓的复用,既减少了系统资源的占用,又减轻了开发 工作量。例如对于存储类设备Microsoft分别提供了供了cdrom.sys,tape.sys与disk.sys三个class driver。这三个driver之中当然也存在一定的共同点,毕竟他们都属于存储类设备(底层硬件的区别通过miniport driver来区分)。Windows提供了classpnp.sys,三个driver将一些通用的例程置于这个sys中。这才更像是文章开头介绍的 DLL复用技术,而不是这里介绍的class/port或是miniport driver。我在此段开头定义class实际上特别提出了与设备无关的共有功能的集合。即class driver实现的是与设备硬件无关的部分,这样做的好处是对于用户态的客户程序来说,不管对于scsi还是ide的设备,我们都可以使用一样的io操作 来进行读写等操作,这里面的主要不同由class driver分发不同的请求至相应的port/miniport driver来实现对用户的透明。这才是最主要的class driver的目的。Microsoft还提供了一个miniclass的概念,在本文未加以叙述(具体请自行参考相关文档)。
2、那么class driver内部通常是如何工作的呢?class driver主要将系统定义的一些标准IO操作如IRP_MJ_READ等转化成特定类别设备的私有IOCTL,然后调用相应的miniport driver。如对于SCSI总线上的cdrom,cdrom会将IRP_MJ_READ转化成一个SCSI命令调用系统提供的miniport driver,我底下会用一个具体的例子说明。既然class driver只是调用miniport driver,那为什么又要引入port driver的概念呢?我们知道scsi总线控制器的类别很多,而如果每种控制器都从头开始设计一个driver,那么实现这样的一个driver则将是 一个多么困难的事情。所以Microsoft引入了port driver的概念。例如由SCSI port driver来实现所有SCSI共同的内容,miniport只实现特定的SCSI控制器或是SCSI设备等内容,共有的部分只要调用port driver提供的服务即可。如miniport driver aha154x.sys实现Adaptec AHA-154x系列SCSI控制器。而她的任务只是处理SRB((SCSI request block,含有Command descriptor block,即CDB,具体请参阅SCSI标准),其他的细节问题(如同步等等均由port driver scsiport.sys完成)。class/port/miniport模型在2000/XP中处处存在,如disk.sys是磁盘类driver,而 pciidex.sys是IDE系统的port driver等等。miniport通过调用port driver导出的函数等来实现其功能的,这儿其实又有dll复用的内容了。
需要指出的是class/port/miniport不是严格上的独立体,也就是说并不都意味着这些有严格的区分,严格的由三个.sys来分别实现相应的 功能。如atapi.sys则集合了port与miniport的功能,她用来服务基于ATAPI(使用SCSI命令)的IDE设备。2000/XP中的 网络设备、网络协议等网络驱动程序从严格的意义上讲也可以说是class/port/miniport的驱动模型,虽然她们有一套自己的术语,如NDIS Intermediate Drivers、NDIS Protocol Drivers与NDIS Miniport Drivers等等(用于实现OSI的7层模型)。我更愿意将ndis.sys理解成class/port的集合,而其余NDIS Miniport Drivers则是miniport driver。
学习class/port/miniport最好的途径就是通过学习源代码。上面提到的disk.sys/cdrom.sys/tape.sys /classpnp.sys/aha154x.sys在DDK中Microsoft都提供了源代码。class driver通常通过IOCTL调用miniport driver。port driver通常通过Driver相关的一块内存区域来维护其对miniport driver的信息的(如建立的DEVICE_OBJECT等等)。这块内存区域是port driver相关的(也即同一子类的class设备如disk.sys/cdrom.sys/tape.sys互不干扰),这儿我借用 TLS(Thread Local Storage)的概念,TLS是线程相关的。在《 解读Windows 2000/XP分层驱动模型》中我介绍了使客户(这里是miniport)可以使用IoAllocateDriverObjectExtension分配 一块内存,由目的driver(这里是scsiport.sys)的DRIVER_OBJECT的DriverExtenion的 ClientDriverExtension指向。不管是scsiport.sys或是ndis.sys都这样来组织miniport专有信息。
既然文头提到COM,我这儿插入一个题外话,COM使用CoInitialize(Ex)来初始化COM Execute Context(Apartment),实际上CoInitialize(Ex)也使用TEB(线程环境块)的ReservedForOle字段指向在堆 中分配的一块内存用于支持COM执行环境。在XP SP1中这个字段的偏移值为0xf80。这样的概念与IoAllocateDriverObjectExtension分配driver相关的内存应该是 一样的,只不过前者使用FS寄存器查当然线程的TEB中的Ole信息,而后者则使用IoGetDriverObjectExtension来获取 DRIVER_OBJECT中的DriverExtenion信息的ClientDriverExtension而已。
说了这么多class/port/miniport的概念,实在仍是非常的抽象。我们通过一个例子来说明可能会更好的理解。这里要用虚拟光驱这一类别的工 具来说明,我们知道虚拟光驱软件通常使用SCSI Miniport来实现,我们这儿通过cdrom.sys/scsiport.sys/fakecd.sys这样一个class/port /miniport模型来学习。其中class driver与port driver都是由OS提供的。fakecd.sys是我实现的一个SCSI miniport driver。具体请参阅《FakeCD For Windows 2000/XP》。《FakeCD For Windows 2000/XP》介绍的Copystar FANTOM DVDROM、 Daemon Tools、Virtual CD与fakecd.sys都一样,都是SCSI miniport port,都通过port driver scsiport.sys提供服务的。
下面我以我机子上(同时装有一个物理光驱、FakeCD与Daemon Tools)的情况来观察:
kd> !devstack cdrom0
!DevObj !DrvObj !DevExt ObjectName
ffaa5020 /Driver/redbook ffaa50d8
> ffaa69e0 /Driver/Cdrom ffaa6a98 CdRom0
81340d78 /Driver/ACPI 8135d978 00000063
813418e8 /Driver/atapi 813419a0 IdeDeviceP1T0L0-e
!DevNode 812fd700 :
DeviceInst is "IDE/CdRomMITSUMI_CD-ROM_SR243T___________________L02G____/5&18b54618&0&0.0.0"
ServiceName is "cdrom"
kd> !devstack cdrom1
!DevObj !DrvObj !DevExt ObjectName
ff8bfa60 /Driver/redbook ff8bfb18
> ff8bf030 /Driver/Cdrom ff8bf0e8 CdRom1
8133b030 /Driver/Stlth317 8133b0e8 Stlth3171Port0Path0Target0Lun0
!DevNode 8133b850 :
DeviceInst is "SCSI/CdRom&Ven_V386&Prod_STEALTH_DVD&Rev_1.0h/1&2afd7d61&0&000"
ServiceName is "cdrom"
kd> !devstack cdrom6
!DevObj !DrvObj !DevExt ObjectName
> ff5fbac8 /Driver/Cdrom ff5fbb80 CdRom6
ff4c9a38 /Driver/FakeCD ff4c9af0 FakeCD1Port3Path0Target0Lun0
!DevNode ff9c68a0 :
DeviceInst is "SCSI/CdRom&Ven_WebCrazy&Prod_WebCrazy_FakeCD&Rev_1.00/1&1843ccbc&0&000"
ServiceName is "cdrom"
这里windbg列出Cdrom0、Cdrom1与Cdrom6分别用于服务物理光驱与Daemon Tools与FakeCD的虚拟光驱。他们都是由/Driver/Cdrom(即class driver cdrom.sys)驱动建立的对象,且位于驱动栈的顶层(redbook只是一个Filter driver用于实现有关Audio的功能,这里不加以考虑),这样层现给IO管理器的只是一个“CDROM”,而不管底层具体是物理光驱(位于IDE总 线上),还是虚拟光驱(位于虚拟SCSI总线上)。这即是class driver的作用,实际上这儿与文件系统的交互也由class driver实现,所以FakeCD也不用考虑File System,也就是说对于ISO文件等的格式我在FakeCD中没有一行代码。这点很多朋友来信询问,在此一并予以回答。
共同实现port/miniport功能的atapi.sys的Cdrom0用于服务物理光驱(ATAPI IDE),这里也就比较容易理解了。对于虚拟SCSI总线的光驱如Cdrom1,Cdrom6,设备栈底层的miniport driver,由port driver scsiport.sys的提供服务,换句话说这儿port driver只是提供一个环境(正像ole32.dll的作用对于COM一样)。cdrom.sys转换标准IRP为SCSI的SRB(CDB)然后使用 IOCTL调用下层的Miniport完成IO请求。实际上miniport一般调用port driver提供的例程来生成Device对象,如scsiport.sys提供ScsiPortInitialize、所以port driver总知道自己管理的设备信息。实际上port driver在2000/XP下一般都会向miniport driver开发者隐藏使用Device Object,IRP等。而由miniport driver提供callback函数来完成操作,简化开发者的工作。
让我们继续看看Fakecd.sys驱动对象的内容:
kd> !drvobj fakecd 2
Driver object (ff3d5208) is for:
/Driver/FakeCD
DriverEntry: f9a50466
DriverStartIo: f94930c4 SCSIPORT!ScsiPortStartIo
DriverUnload: f949d3e0 SCSIPORT!ScsiPortUnload
Dispatch routines:
[00] IRP_MJ_CREATE f9490436 SCSIPORT!ScsiPortGlobalDispatch
[01] IRP_MJ_CREATE_NAMED_PIPE 804f986f nt!IopInvalidDeviceRequest
[02] IRP_MJ_CLOSE f9490436 SCSIPORT!ScsiPortGlobalDispatch
[03] IRP_MJ_READ 804f986f nt!IopInvalidDeviceRequest
[04] IRP_MJ_WRITE 804f986f nt!IopInvalidDeviceRequest
[05] IRP_MJ_QUERY_INFORMATION 804f986f nt!IopInvalidDeviceRequest
[06] IRP_MJ_SET_INFORMATION 804f986f nt!IopInvalidDeviceRequest
[07] IRP_MJ_QUERY_EA 804f986f nt!IopInvalidDeviceRequest
[08] IRP_MJ_SET_EA 804f986f nt!IopInvalidDeviceRequest
[09] IRP_MJ_FLUSH_BUFFERS 804f986f nt!IopInvalidDeviceRequest
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION 804f986f nt!IopInvalidDeviceRequest
[0b] IRP_MJ_SET_VOLUME_INFORMATION 804f986f nt!IopInvalidDeviceRequest
[0c] IRP_MJ_DIRECTORY_CONTROL 804f986f nt!IopInvalidDeviceRequest
[0d] IRP_MJ_FILE_SYSTEM_CONTROL 804f986f nt!IopInvalidDeviceRequest
[0e] IRP_MJ_DEVICE_CONTROL f9490436 SCSIPORT!ScsiPortGlobalDispatch
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL ffa408e8 +0xffa408e8
[10] IRP_MJ_SHUTDOWN 804f986f nt!IopInvalidDeviceRequest
[11] IRP_MJ_LOCK_CONTROL 804f986f nt!IopInvalidDeviceRequest
[12] IRP_MJ_CLEANUP 804f986f nt!IopInvalidDeviceRequest
[13] IRP_MJ_CREATE_MAILSLOT 804f986f nt!IopInvalidDeviceRequest
[14] IRP_MJ_QUERY_SECURITY 804f986f nt!IopInvalidDeviceRequest
[15] IRP_MJ_SET_SECURITY 804f986f nt!IopInvalidDeviceRequest
[16] IRP_MJ_POWER f9490436 SCSIPORT!ScsiPortGlobalDispatch
[17] IRP_MJ_SYSTEM_CONTROL f9490436 SCSIPORT!ScsiPortGlobalDispatch
[18] IRP_MJ_DEVICE_CHANGE 804f986f nt!IopInvalidDeviceRequest
[19] IRP_MJ_QUERY_QUOTA 804f986f nt!IopInvalidDeviceRequest
[1a] IRP_MJ_SET_QUOTA 804f986f nt!IopInvalidDeviceRequest
[1b] IRP_MJ_PNP f9a503ba fakecd!FakeCDPnp
非常明显对于SCSI miniport提供的driver入口,不管是StartIo,Unload还是IRP Dispatch函数都是由scsiport.sys提供的(为了提供充分的灵活,IRP_MJ_PNP入口被fakecd.sys miniport driver替换了用于实现动态的实现CDROM个数的增减)。
本文只是从应用的角度讨论了class/port/miniport的模型,对于这个模型的理解以及如何实现类似的机制实际上只是一个最最为基础的软件复 用的概念(当然加入了编写设备驱动的一些特色,如与硬件设备的耦合考虑等)。这样一个模型相对于用户态的COM实现二进制层级的复用技术相对简单的多。只 是因为他们位于核心态,编写容易Blue Screen,才被大家认识的不够,本文只是希望为你做一个引导而已,在我看来,实在是没有什么特别的内容。